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