1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Marvell 88e6xxx Ethernet switch PHY and PPU support |
4 | * |
5 | * Copyright (c) 2008 Marvell Semiconductor |
6 | * |
7 | * Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch> |
8 | */ |
9 | |
10 | #include <linux/mdio.h> |
11 | #include <linux/module.h> |
12 | |
13 | #include "chip.h" |
14 | #include "phy.h" |
15 | |
16 | int mv88e6165_phy_read(struct mv88e6xxx_chip *chip, struct mii_bus *bus, |
17 | int addr, int reg, u16 *val) |
18 | { |
19 | return mv88e6xxx_read(chip, addr, reg, val); |
20 | } |
21 | |
22 | int mv88e6165_phy_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus, |
23 | int addr, int reg, u16 val) |
24 | { |
25 | return mv88e6xxx_write(chip, addr, reg, val); |
26 | } |
27 | |
28 | int mv88e6xxx_phy_read(struct mv88e6xxx_chip *chip, int phy, int reg, u16 *val) |
29 | { |
30 | int addr = phy; /* PHY devices addresses start at 0x0 */ |
31 | struct mii_bus *bus; |
32 | |
33 | bus = mv88e6xxx_default_mdio_bus(chip); |
34 | if (!bus) |
35 | return -EOPNOTSUPP; |
36 | |
37 | if (!chip->info->ops->phy_read) |
38 | return -EOPNOTSUPP; |
39 | |
40 | return chip->info->ops->phy_read(chip, bus, addr, reg, val); |
41 | } |
42 | |
43 | int mv88e6xxx_phy_write(struct mv88e6xxx_chip *chip, int phy, int reg, u16 val) |
44 | { |
45 | int addr = phy; /* PHY devices addresses start at 0x0 */ |
46 | struct mii_bus *bus; |
47 | |
48 | bus = mv88e6xxx_default_mdio_bus(chip); |
49 | if (!bus) |
50 | return -EOPNOTSUPP; |
51 | |
52 | if (!chip->info->ops->phy_write) |
53 | return -EOPNOTSUPP; |
54 | |
55 | return chip->info->ops->phy_write(chip, bus, addr, reg, val); |
56 | } |
57 | |
58 | int mv88e6xxx_phy_read_c45(struct mv88e6xxx_chip *chip, int phy, int devad, |
59 | int reg, u16 *val) |
60 | { |
61 | int addr = phy; /* PHY devices addresses start at 0x0 */ |
62 | struct mii_bus *bus; |
63 | |
64 | bus = mv88e6xxx_default_mdio_bus(chip); |
65 | if (!bus) |
66 | return -EOPNOTSUPP; |
67 | |
68 | if (!chip->info->ops->phy_read_c45) |
69 | return -EOPNOTSUPP; |
70 | |
71 | return chip->info->ops->phy_read_c45(chip, bus, addr, devad, reg, val); |
72 | } |
73 | |
74 | int mv88e6xxx_phy_write_c45(struct mv88e6xxx_chip *chip, int phy, int devad, |
75 | int reg, u16 val) |
76 | { |
77 | int addr = phy; /* PHY devices addresses start at 0x0 */ |
78 | struct mii_bus *bus; |
79 | |
80 | bus = mv88e6xxx_default_mdio_bus(chip); |
81 | if (!bus) |
82 | return -EOPNOTSUPP; |
83 | |
84 | if (!chip->info->ops->phy_write_c45) |
85 | return -EOPNOTSUPP; |
86 | |
87 | return chip->info->ops->phy_write_c45(chip, bus, addr, devad, reg, val); |
88 | } |
89 | |
90 | static int mv88e6xxx_phy_page_get(struct mv88e6xxx_chip *chip, int phy, u8 page) |
91 | { |
92 | return mv88e6xxx_phy_write(chip, phy, MV88E6XXX_PHY_PAGE, val: page); |
93 | } |
94 | |
95 | static void mv88e6xxx_phy_page_put(struct mv88e6xxx_chip *chip, int phy) |
96 | { |
97 | int err; |
98 | |
99 | /* Restore PHY page Copper 0x0 for access via the registered |
100 | * MDIO bus |
101 | */ |
102 | err = mv88e6xxx_phy_write(chip, phy, MV88E6XXX_PHY_PAGE, |
103 | MV88E6XXX_PHY_PAGE_COPPER); |
104 | if (unlikely(err)) { |
105 | dev_err(chip->dev, |
106 | "failed to restore PHY %d page Copper (%d)\n" , |
107 | phy, err); |
108 | } |
109 | } |
110 | |
111 | int mv88e6xxx_phy_page_read(struct mv88e6xxx_chip *chip, int phy, |
112 | u8 page, int reg, u16 *val) |
113 | { |
114 | int err; |
115 | |
116 | /* There is no paging for registers 22 */ |
117 | if (reg == MV88E6XXX_PHY_PAGE) |
118 | return -EINVAL; |
119 | |
120 | err = mv88e6xxx_phy_page_get(chip, phy, page); |
121 | if (!err) { |
122 | err = mv88e6xxx_phy_read(chip, phy, reg, val); |
123 | mv88e6xxx_phy_page_put(chip, phy); |
124 | } |
125 | |
126 | return err; |
127 | } |
128 | |
129 | int mv88e6xxx_phy_page_write(struct mv88e6xxx_chip *chip, int phy, |
130 | u8 page, int reg, u16 val) |
131 | { |
132 | int err; |
133 | |
134 | /* There is no paging for registers 22 */ |
135 | if (reg == MV88E6XXX_PHY_PAGE) |
136 | return -EINVAL; |
137 | |
138 | err = mv88e6xxx_phy_page_get(chip, phy, page); |
139 | if (!err) { |
140 | err = mv88e6xxx_phy_write(chip, phy, MV88E6XXX_PHY_PAGE, val: page); |
141 | if (!err) |
142 | err = mv88e6xxx_phy_write(chip, phy, reg, val); |
143 | |
144 | mv88e6xxx_phy_page_put(chip, phy); |
145 | } |
146 | |
147 | return err; |
148 | } |
149 | |
150 | static int mv88e6xxx_phy_ppu_disable(struct mv88e6xxx_chip *chip) |
151 | { |
152 | if (!chip->info->ops->ppu_disable) |
153 | return 0; |
154 | |
155 | return chip->info->ops->ppu_disable(chip); |
156 | } |
157 | |
158 | static int mv88e6xxx_phy_ppu_enable(struct mv88e6xxx_chip *chip) |
159 | { |
160 | if (!chip->info->ops->ppu_enable) |
161 | return 0; |
162 | |
163 | return chip->info->ops->ppu_enable(chip); |
164 | } |
165 | |
166 | static void mv88e6xxx_phy_ppu_reenable_work(struct work_struct *ugly) |
167 | { |
168 | struct mv88e6xxx_chip *chip; |
169 | |
170 | chip = container_of(ugly, struct mv88e6xxx_chip, ppu_work); |
171 | |
172 | mv88e6xxx_reg_lock(chip); |
173 | |
174 | if (mutex_trylock(lock: &chip->ppu_mutex)) { |
175 | if (mv88e6xxx_phy_ppu_enable(chip) == 0) |
176 | chip->ppu_disabled = 0; |
177 | mutex_unlock(lock: &chip->ppu_mutex); |
178 | } |
179 | |
180 | mv88e6xxx_reg_unlock(chip); |
181 | } |
182 | |
183 | static void mv88e6xxx_phy_ppu_reenable_timer(struct timer_list *t) |
184 | { |
185 | struct mv88e6xxx_chip *chip = from_timer(chip, t, ppu_timer); |
186 | |
187 | schedule_work(work: &chip->ppu_work); |
188 | } |
189 | |
190 | static int mv88e6xxx_phy_ppu_access_get(struct mv88e6xxx_chip *chip) |
191 | { |
192 | int ret; |
193 | |
194 | mutex_lock(&chip->ppu_mutex); |
195 | |
196 | /* If the PHY polling unit is enabled, disable it so that |
197 | * we can access the PHY registers. If it was already |
198 | * disabled, cancel the timer that is going to re-enable |
199 | * it. |
200 | */ |
201 | if (!chip->ppu_disabled) { |
202 | ret = mv88e6xxx_phy_ppu_disable(chip); |
203 | if (ret < 0) { |
204 | mutex_unlock(lock: &chip->ppu_mutex); |
205 | return ret; |
206 | } |
207 | chip->ppu_disabled = 1; |
208 | } else { |
209 | del_timer(timer: &chip->ppu_timer); |
210 | ret = 0; |
211 | } |
212 | |
213 | return ret; |
214 | } |
215 | |
216 | static void mv88e6xxx_phy_ppu_access_put(struct mv88e6xxx_chip *chip) |
217 | { |
218 | /* Schedule a timer to re-enable the PHY polling unit. */ |
219 | mod_timer(timer: &chip->ppu_timer, expires: jiffies + msecs_to_jiffies(m: 10)); |
220 | mutex_unlock(lock: &chip->ppu_mutex); |
221 | } |
222 | |
223 | static void mv88e6xxx_phy_ppu_state_init(struct mv88e6xxx_chip *chip) |
224 | { |
225 | mutex_init(&chip->ppu_mutex); |
226 | INIT_WORK(&chip->ppu_work, mv88e6xxx_phy_ppu_reenable_work); |
227 | timer_setup(&chip->ppu_timer, mv88e6xxx_phy_ppu_reenable_timer, 0); |
228 | } |
229 | |
230 | static void mv88e6xxx_phy_ppu_state_destroy(struct mv88e6xxx_chip *chip) |
231 | { |
232 | del_timer_sync(timer: &chip->ppu_timer); |
233 | } |
234 | |
235 | int mv88e6185_phy_ppu_read(struct mv88e6xxx_chip *chip, struct mii_bus *bus, |
236 | int addr, int reg, u16 *val) |
237 | { |
238 | int err; |
239 | |
240 | err = mv88e6xxx_phy_ppu_access_get(chip); |
241 | if (!err) { |
242 | err = mv88e6xxx_read(chip, addr, reg, val); |
243 | mv88e6xxx_phy_ppu_access_put(chip); |
244 | } |
245 | |
246 | return err; |
247 | } |
248 | |
249 | int mv88e6185_phy_ppu_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus, |
250 | int addr, int reg, u16 val) |
251 | { |
252 | int err; |
253 | |
254 | err = mv88e6xxx_phy_ppu_access_get(chip); |
255 | if (!err) { |
256 | err = mv88e6xxx_write(chip, addr, reg, val); |
257 | mv88e6xxx_phy_ppu_access_put(chip); |
258 | } |
259 | |
260 | return err; |
261 | } |
262 | |
263 | void mv88e6xxx_phy_init(struct mv88e6xxx_chip *chip) |
264 | { |
265 | if (chip->info->ops->ppu_enable && chip->info->ops->ppu_disable) |
266 | mv88e6xxx_phy_ppu_state_init(chip); |
267 | } |
268 | |
269 | void mv88e6xxx_phy_destroy(struct mv88e6xxx_chip *chip) |
270 | { |
271 | if (chip->info->ops->ppu_enable && chip->info->ops->ppu_disable) |
272 | mv88e6xxx_phy_ppu_state_destroy(chip); |
273 | } |
274 | |
275 | int mv88e6xxx_phy_setup(struct mv88e6xxx_chip *chip) |
276 | { |
277 | return mv88e6xxx_phy_ppu_enable(chip); |
278 | } |
279 | |