root/drivers/net/phy/air_phy_lib.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Airoha Ethernet PHY common library
 *
 * Copyright (C) 2026 Airoha Technology Corp.
 * Copyright (C) 2026 Collabora Ltd.
 *                    Louis-Alexis Eyraud <louisalexis.eyraud@collabora.com>
 */

#include <linux/export.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/wordpart.h>

#include "air_phy_lib.h"

static int __air_buckpbus_reg_read(struct phy_device *phydev,
                                   u32 pbus_address, u32 *pbus_data)
{
        int pbus_data_low, pbus_data_high;
        int ret;

        ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH,
                          upper_16_bits(pbus_address));
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW,
                          lower_16_bits(pbus_address));
        if (ret < 0)
                return ret;

        pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH);
        if (pbus_data_high < 0)
                return pbus_data_high;

        pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW);
        if (pbus_data_low < 0)
                return pbus_data_low;

        *pbus_data = pbus_data_low | (pbus_data_high << 16);
        return 0;
}

static int __air_buckpbus_reg_write(struct phy_device *phydev,
                                    u32 pbus_address, u32 pbus_data)
{
        int ret;

        ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,
                          upper_16_bits(pbus_address));
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,
                          lower_16_bits(pbus_address));
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH,
                          upper_16_bits(pbus_data));
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW,
                          lower_16_bits(pbus_data));
        if (ret < 0)
                return ret;

        return 0;
}

static int __air_buckpbus_reg_modify(struct phy_device *phydev,
                                     u32 pbus_address, u32 mask, u32 set)
{
        int pbus_data_low, pbus_data_high;
        u32 pbus_data_old, pbus_data_new;
        int ret;

        ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH,
                          upper_16_bits(pbus_address));
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW,
                          lower_16_bits(pbus_address));
        if (ret < 0)
                return ret;

        pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH);
        if (pbus_data_high < 0)
                return pbus_data_high;

        pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW);
        if (pbus_data_low < 0)
                return pbus_data_low;

        pbus_data_old = pbus_data_low | (pbus_data_high << 16);
        pbus_data_new = (pbus_data_old & ~mask) | set;
        if (pbus_data_new == pbus_data_old)
                return 0;

        ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,
                          upper_16_bits(pbus_address));
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,
                          lower_16_bits(pbus_address));
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH,
                          upper_16_bits(pbus_data_new));
        if (ret < 0)
                return ret;

        ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW,
                          lower_16_bits(pbus_data_new));
        if (ret < 0)
                return ret;

        return 0;
}

int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address,
                              u32 *pbus_data)
{
        int saved_page;
        int ret = 0;

        saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);

        if (saved_page >= 0) {
                ret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data);
                if (ret < 0)
                        phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
                                   pbus_address, ret);
        }

        return phy_restore_page(phydev, saved_page, ret);
}
EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_read);

int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,
                               u32 pbus_data)
{
        int saved_page;
        int ret = 0;

        saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);

        if (saved_page >= 0) {
                ret = __air_buckpbus_reg_write(phydev, pbus_address,
                                               pbus_data);
                if (ret < 0)
                        phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
                                   pbus_address, ret);
        }

        return phy_restore_page(phydev, saved_page, ret);
}
EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_write);

int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,
                                u32 mask, u32 set)
{
        int saved_page;
        int ret = 0;

        saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);

        if (saved_page >= 0) {
                ret = __air_buckpbus_reg_modify(phydev, pbus_address, mask,
                                                set);
                if (ret < 0)
                        phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
                                   pbus_address, ret);
        }

        return phy_restore_page(phydev, saved_page, ret);
}
EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_modify);

int air_phy_read_page(struct phy_device *phydev)
{
        return __phy_read(phydev, AIR_EXT_PAGE_ACCESS);
}
EXPORT_SYMBOL_GPL(air_phy_read_page);

int air_phy_write_page(struct phy_device *phydev, int page)
{
        return __phy_write(phydev, AIR_EXT_PAGE_ACCESS, page);
}
EXPORT_SYMBOL_GPL(air_phy_write_page);

MODULE_DESCRIPTION("Airoha PHY Library");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Louis-Alexis Eyraud");