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