| // 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"); |