xref: /linux/drivers/net/phy/air_phy_lib.c (revision e08f0ea6daf2e5ffdb38844460bfd2db3b091015)
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