1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Airoha Ethernet PHY common library 4 * 5 * Copyright (C) 2026 Airoha Technology Corp. 6 * Copyright (C) 2026 Collabora Ltd. 7 * Louis-Alexis Eyraud <louisalexis.eyraud@collabora.com> 8 */ 9 10 #include <linux/export.h> 11 #include <linux/module.h> 12 #include <linux/phy.h> 13 #include <linux/wordpart.h> 14 15 #include "air_phy_lib.h" 16 17 #define AIR_EXT_PAGE_ACCESS 0x1f 18 19 static int __air_buckpbus_reg_read(struct phy_device *phydev, 20 u32 pbus_address, u32 *pbus_data) 21 { 22 int pbus_data_low, pbus_data_high; 23 int ret; 24 25 ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); 26 if (ret < 0) 27 return ret; 28 29 ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH, 30 upper_16_bits(pbus_address)); 31 if (ret < 0) 32 return ret; 33 34 ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW, 35 lower_16_bits(pbus_address)); 36 if (ret < 0) 37 return ret; 38 39 pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH); 40 if (pbus_data_high < 0) 41 return pbus_data_high; 42 43 pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW); 44 if (pbus_data_low < 0) 45 return pbus_data_low; 46 47 *pbus_data = pbus_data_low | (pbus_data_high << 16); 48 return 0; 49 } 50 51 static int __air_buckpbus_reg_write(struct phy_device *phydev, 52 u32 pbus_address, u32 pbus_data) 53 { 54 int ret; 55 56 ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); 57 if (ret < 0) 58 return ret; 59 60 ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, 61 upper_16_bits(pbus_address)); 62 if (ret < 0) 63 return ret; 64 65 ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, 66 lower_16_bits(pbus_address)); 67 if (ret < 0) 68 return ret; 69 70 ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, 71 upper_16_bits(pbus_data)); 72 if (ret < 0) 73 return ret; 74 75 ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, 76 lower_16_bits(pbus_data)); 77 if (ret < 0) 78 return ret; 79 80 return 0; 81 } 82 83 static int __air_buckpbus_reg_modify(struct phy_device *phydev, 84 u32 pbus_address, u32 mask, u32 set) 85 { 86 int pbus_data_low, pbus_data_high; 87 u32 pbus_data_old, pbus_data_new; 88 int ret; 89 90 ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); 91 if (ret < 0) 92 return ret; 93 94 ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH, 95 upper_16_bits(pbus_address)); 96 if (ret < 0) 97 return ret; 98 99 ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW, 100 lower_16_bits(pbus_address)); 101 if (ret < 0) 102 return ret; 103 104 pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH); 105 if (pbus_data_high < 0) 106 return pbus_data_high; 107 108 pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW); 109 if (pbus_data_low < 0) 110 return pbus_data_low; 111 112 pbus_data_old = pbus_data_low | (pbus_data_high << 16); 113 pbus_data_new = (pbus_data_old & ~mask) | set; 114 if (pbus_data_new == pbus_data_old) 115 return 0; 116 117 ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, 118 upper_16_bits(pbus_address)); 119 if (ret < 0) 120 return ret; 121 122 ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, 123 lower_16_bits(pbus_address)); 124 if (ret < 0) 125 return ret; 126 127 ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, 128 upper_16_bits(pbus_data_new)); 129 if (ret < 0) 130 return ret; 131 132 ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, 133 lower_16_bits(pbus_data_new)); 134 if (ret < 0) 135 return ret; 136 137 return 0; 138 } 139 140 int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address, 141 u32 *pbus_data) 142 { 143 int saved_page; 144 int ret = 0; 145 146 saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); 147 148 if (saved_page >= 0) { 149 ret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data); 150 if (ret < 0) 151 phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, 152 pbus_address, ret); 153 } 154 155 return phy_restore_page(phydev, saved_page, ret); 156 } 157 EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_read); 158 159 int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address, 160 u32 pbus_data) 161 { 162 int saved_page; 163 int ret = 0; 164 165 saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); 166 167 if (saved_page >= 0) { 168 ret = __air_buckpbus_reg_write(phydev, pbus_address, 169 pbus_data); 170 if (ret < 0) 171 phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, 172 pbus_address, ret); 173 } 174 175 return phy_restore_page(phydev, saved_page, ret); 176 } 177 EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_write); 178 179 int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address, 180 u32 mask, u32 set) 181 { 182 int saved_page; 183 int ret = 0; 184 185 saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); 186 187 if (saved_page >= 0) { 188 ret = __air_buckpbus_reg_modify(phydev, pbus_address, mask, 189 set); 190 if (ret < 0) 191 phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, 192 pbus_address, ret); 193 } 194 195 return phy_restore_page(phydev, saved_page, ret); 196 } 197 EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_modify); 198 199 int air_phy_read_page(struct phy_device *phydev) 200 { 201 return __phy_read(phydev, AIR_EXT_PAGE_ACCESS); 202 } 203 EXPORT_SYMBOL_GPL(air_phy_read_page); 204 205 int air_phy_write_page(struct phy_device *phydev, int page) 206 { 207 return __phy_write(phydev, AIR_EXT_PAGE_ACCESS, page); 208 } 209 EXPORT_SYMBOL_GPL(air_phy_write_page); 210 211 MODULE_DESCRIPTION("Airoha PHY Library"); 212 MODULE_LICENSE("GPL"); 213 MODULE_AUTHOR("Louis-Alexis Eyraud"); 214