diff options
Diffstat (limited to 'drivers/net/phy/at803x.c')
-rw-r--r-- | drivers/net/phy/at803x.c | 195 |
1 files changed, 151 insertions, 44 deletions
diff --git a/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c index 6c622aedbae1..fdc1b418fa6a 100644 --- a/drivers/net/phy/at803x.c +++ b/drivers/net/phy/at803x.c | |||
@@ -16,9 +16,13 @@ | |||
16 | #include <linux/string.h> | 16 | #include <linux/string.h> |
17 | #include <linux/netdevice.h> | 17 | #include <linux/netdevice.h> |
18 | #include <linux/etherdevice.h> | 18 | #include <linux/etherdevice.h> |
19 | #include <linux/of_gpio.h> | ||
20 | #include <linux/gpio/consumer.h> | ||
19 | 21 | ||
20 | #define AT803X_INTR_ENABLE 0x12 | 22 | #define AT803X_INTR_ENABLE 0x12 |
21 | #define AT803X_INTR_STATUS 0x13 | 23 | #define AT803X_INTR_STATUS 0x13 |
24 | #define AT803X_SMART_SPEED 0x14 | ||
25 | #define AT803X_LED_CONTROL 0x18 | ||
22 | #define AT803X_WOL_ENABLE 0x01 | 26 | #define AT803X_WOL_ENABLE 0x01 |
23 | #define AT803X_DEVICE_ADDR 0x03 | 27 | #define AT803X_DEVICE_ADDR 0x03 |
24 | #define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C | 28 | #define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C |
@@ -35,10 +39,52 @@ | |||
35 | #define AT803X_DEBUG_SYSTEM_MODE_CTRL 0x05 | 39 | #define AT803X_DEBUG_SYSTEM_MODE_CTRL 0x05 |
36 | #define AT803X_DEBUG_RGMII_TX_CLK_DLY BIT(8) | 40 | #define AT803X_DEBUG_RGMII_TX_CLK_DLY BIT(8) |
37 | 41 | ||
42 | #define ATH8030_PHY_ID 0x004dd076 | ||
43 | #define ATH8031_PHY_ID 0x004dd074 | ||
44 | #define ATH8035_PHY_ID 0x004dd072 | ||
45 | |||
38 | MODULE_DESCRIPTION("Atheros 803x PHY driver"); | 46 | MODULE_DESCRIPTION("Atheros 803x PHY driver"); |
39 | MODULE_AUTHOR("Matus Ujhelyi"); | 47 | MODULE_AUTHOR("Matus Ujhelyi"); |
40 | MODULE_LICENSE("GPL"); | 48 | MODULE_LICENSE("GPL"); |
41 | 49 | ||
50 | struct at803x_priv { | ||
51 | bool phy_reset:1; | ||
52 | struct gpio_desc *gpiod_reset; | ||
53 | }; | ||
54 | |||
55 | struct at803x_context { | ||
56 | u16 bmcr; | ||
57 | u16 advertise; | ||
58 | u16 control1000; | ||
59 | u16 int_enable; | ||
60 | u16 smart_speed; | ||
61 | u16 led_control; | ||
62 | }; | ||
63 | |||
64 | /* save relevant PHY registers to private copy */ | ||
65 | static void at803x_context_save(struct phy_device *phydev, | ||
66 | struct at803x_context *context) | ||
67 | { | ||
68 | context->bmcr = phy_read(phydev, MII_BMCR); | ||
69 | context->advertise = phy_read(phydev, MII_ADVERTISE); | ||
70 | context->control1000 = phy_read(phydev, MII_CTRL1000); | ||
71 | context->int_enable = phy_read(phydev, AT803X_INTR_ENABLE); | ||
72 | context->smart_speed = phy_read(phydev, AT803X_SMART_SPEED); | ||
73 | context->led_control = phy_read(phydev, AT803X_LED_CONTROL); | ||
74 | } | ||
75 | |||
76 | /* restore relevant PHY registers from private copy */ | ||
77 | static void at803x_context_restore(struct phy_device *phydev, | ||
78 | const struct at803x_context *context) | ||
79 | { | ||
80 | phy_write(phydev, MII_BMCR, context->bmcr); | ||
81 | phy_write(phydev, MII_ADVERTISE, context->advertise); | ||
82 | phy_write(phydev, MII_CTRL1000, context->control1000); | ||
83 | phy_write(phydev, AT803X_INTR_ENABLE, context->int_enable); | ||
84 | phy_write(phydev, AT803X_SMART_SPEED, context->smart_speed); | ||
85 | phy_write(phydev, AT803X_LED_CONTROL, context->led_control); | ||
86 | } | ||
87 | |||
42 | static int at803x_set_wol(struct phy_device *phydev, | 88 | static int at803x_set_wol(struct phy_device *phydev, |
43 | struct ethtool_wolinfo *wol) | 89 | struct ethtool_wolinfo *wol) |
44 | { | 90 | { |
@@ -142,6 +188,26 @@ static int at803x_resume(struct phy_device *phydev) | |||
142 | return 0; | 188 | return 0; |
143 | } | 189 | } |
144 | 190 | ||
191 | static int at803x_probe(struct phy_device *phydev) | ||
192 | { | ||
193 | struct device *dev = &phydev->dev; | ||
194 | struct at803x_priv *priv; | ||
195 | |||
196 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | ||
197 | if (!priv) | ||
198 | return -ENOMEM; | ||
199 | |||
200 | priv->gpiod_reset = devm_gpiod_get(dev, "reset"); | ||
201 | if (IS_ERR(priv->gpiod_reset)) | ||
202 | priv->gpiod_reset = NULL; | ||
203 | else | ||
204 | gpiod_direction_output(priv->gpiod_reset, 1); | ||
205 | |||
206 | phydev->priv = priv; | ||
207 | |||
208 | return 0; | ||
209 | } | ||
210 | |||
145 | static int at803x_config_init(struct phy_device *phydev) | 211 | static int at803x_config_init(struct phy_device *phydev) |
146 | { | 212 | { |
147 | int ret; | 213 | int ret; |
@@ -189,58 +255,99 @@ static int at803x_config_intr(struct phy_device *phydev) | |||
189 | return err; | 255 | return err; |
190 | } | 256 | } |
191 | 257 | ||
258 | static void at803x_link_change_notify(struct phy_device *phydev) | ||
259 | { | ||
260 | struct at803x_priv *priv = phydev->priv; | ||
261 | |||
262 | /* | ||
263 | * Conduct a hardware reset for AT8030 every time a link loss is | ||
264 | * signalled. This is necessary to circumvent a hardware bug that | ||
265 | * occurs when the cable is unplugged while TX packets are pending | ||
266 | * in the FIFO. In such cases, the FIFO enters an error mode it | ||
267 | * cannot recover from by software. | ||
268 | */ | ||
269 | if (phydev->drv->phy_id == ATH8030_PHY_ID) { | ||
270 | if (phydev->state == PHY_NOLINK) { | ||
271 | if (priv->gpiod_reset && !priv->phy_reset) { | ||
272 | struct at803x_context context; | ||
273 | |||
274 | at803x_context_save(phydev, &context); | ||
275 | |||
276 | gpiod_set_value(priv->gpiod_reset, 0); | ||
277 | msleep(1); | ||
278 | gpiod_set_value(priv->gpiod_reset, 1); | ||
279 | msleep(1); | ||
280 | |||
281 | at803x_context_restore(phydev, &context); | ||
282 | |||
283 | dev_dbg(&phydev->dev, "%s(): phy was reset\n", | ||
284 | __func__); | ||
285 | priv->phy_reset = true; | ||
286 | } | ||
287 | } else { | ||
288 | priv->phy_reset = false; | ||
289 | } | ||
290 | } | ||
291 | } | ||
292 | |||
192 | static struct phy_driver at803x_driver[] = { | 293 | static struct phy_driver at803x_driver[] = { |
193 | { | 294 | { |
194 | /* ATHEROS 8035 */ | 295 | /* ATHEROS 8035 */ |
195 | .phy_id = 0x004dd072, | 296 | .phy_id = ATH8035_PHY_ID, |
196 | .name = "Atheros 8035 ethernet", | 297 | .name = "Atheros 8035 ethernet", |
197 | .phy_id_mask = 0xffffffef, | 298 | .phy_id_mask = 0xffffffef, |
198 | .config_init = at803x_config_init, | 299 | .probe = at803x_probe, |
199 | .set_wol = at803x_set_wol, | 300 | .config_init = at803x_config_init, |
200 | .get_wol = at803x_get_wol, | 301 | .link_change_notify = at803x_link_change_notify, |
201 | .suspend = at803x_suspend, | 302 | .set_wol = at803x_set_wol, |
202 | .resume = at803x_resume, | 303 | .get_wol = at803x_get_wol, |
203 | .features = PHY_GBIT_FEATURES, | 304 | .suspend = at803x_suspend, |
204 | .flags = PHY_HAS_INTERRUPT, | 305 | .resume = at803x_resume, |
205 | .config_aneg = genphy_config_aneg, | 306 | .features = PHY_GBIT_FEATURES, |
206 | .read_status = genphy_read_status, | 307 | .flags = PHY_HAS_INTERRUPT, |
207 | .driver = { | 308 | .config_aneg = genphy_config_aneg, |
309 | .read_status = genphy_read_status, | ||
310 | .driver = { | ||
208 | .owner = THIS_MODULE, | 311 | .owner = THIS_MODULE, |
209 | }, | 312 | }, |
210 | }, { | 313 | }, { |
211 | /* ATHEROS 8030 */ | 314 | /* ATHEROS 8030 */ |
212 | .phy_id = 0x004dd076, | 315 | .phy_id = ATH8030_PHY_ID, |
213 | .name = "Atheros 8030 ethernet", | 316 | .name = "Atheros 8030 ethernet", |
214 | .phy_id_mask = 0xffffffef, | 317 | .phy_id_mask = 0xffffffef, |
215 | .config_init = at803x_config_init, | 318 | .probe = at803x_probe, |
216 | .set_wol = at803x_set_wol, | 319 | .config_init = at803x_config_init, |
217 | .get_wol = at803x_get_wol, | 320 | .link_change_notify = at803x_link_change_notify, |
218 | .suspend = at803x_suspend, | 321 | .set_wol = at803x_set_wol, |
219 | .resume = at803x_resume, | 322 | .get_wol = at803x_get_wol, |
220 | .features = PHY_GBIT_FEATURES, | 323 | .suspend = at803x_suspend, |
221 | .flags = PHY_HAS_INTERRUPT, | 324 | .resume = at803x_resume, |
222 | .config_aneg = genphy_config_aneg, | 325 | .features = PHY_GBIT_FEATURES, |
223 | .read_status = genphy_read_status, | 326 | .flags = PHY_HAS_INTERRUPT, |
224 | .driver = { | 327 | .config_aneg = genphy_config_aneg, |
328 | .read_status = genphy_read_status, | ||
329 | .driver = { | ||
225 | .owner = THIS_MODULE, | 330 | .owner = THIS_MODULE, |
226 | }, | 331 | }, |
227 | }, { | 332 | }, { |
228 | /* ATHEROS 8031 */ | 333 | /* ATHEROS 8031 */ |
229 | .phy_id = 0x004dd074, | 334 | .phy_id = ATH8031_PHY_ID, |
230 | .name = "Atheros 8031 ethernet", | 335 | .name = "Atheros 8031 ethernet", |
231 | .phy_id_mask = 0xffffffef, | 336 | .phy_id_mask = 0xffffffef, |
232 | .config_init = at803x_config_init, | 337 | .probe = at803x_probe, |
233 | .set_wol = at803x_set_wol, | 338 | .config_init = at803x_config_init, |
234 | .get_wol = at803x_get_wol, | 339 | .link_change_notify = at803x_link_change_notify, |
235 | .suspend = at803x_suspend, | 340 | .set_wol = at803x_set_wol, |
236 | .resume = at803x_resume, | 341 | .get_wol = at803x_get_wol, |
237 | .features = PHY_GBIT_FEATURES, | 342 | .suspend = at803x_suspend, |
238 | .flags = PHY_HAS_INTERRUPT, | 343 | .resume = at803x_resume, |
239 | .config_aneg = genphy_config_aneg, | 344 | .features = PHY_GBIT_FEATURES, |
240 | .read_status = genphy_read_status, | 345 | .flags = PHY_HAS_INTERRUPT, |
241 | .ack_interrupt = &at803x_ack_interrupt, | 346 | .config_aneg = genphy_config_aneg, |
242 | .config_intr = &at803x_config_intr, | 347 | .read_status = genphy_read_status, |
243 | .driver = { | 348 | .ack_interrupt = &at803x_ack_interrupt, |
349 | .config_intr = &at803x_config_intr, | ||
350 | .driver = { | ||
244 | .owner = THIS_MODULE, | 351 | .owner = THIS_MODULE, |
245 | }, | 352 | }, |
246 | } }; | 353 | } }; |
@@ -260,9 +367,9 @@ module_init(atheros_init); | |||
260 | module_exit(atheros_exit); | 367 | module_exit(atheros_exit); |
261 | 368 | ||
262 | static struct mdio_device_id __maybe_unused atheros_tbl[] = { | 369 | static struct mdio_device_id __maybe_unused atheros_tbl[] = { |
263 | { 0x004dd076, 0xffffffef }, | 370 | { ATH8030_PHY_ID, 0xffffffef }, |
264 | { 0x004dd074, 0xffffffef }, | 371 | { ATH8031_PHY_ID, 0xffffffef }, |
265 | { 0x004dd072, 0xffffffef }, | 372 | { ATH8035_PHY_ID, 0xffffffef }, |
266 | { } | 373 | { } |
267 | }; | 374 | }; |
268 | 375 | ||