blob: bc04fba05b86ec828ace3c0d5a725062a5bdd488 [file] [log] [blame]
Matus Ujhelyi0ca71112012-10-14 19:07:16 +00001/*
2 * drivers/net/phy/at803x.c
3 *
4 * Driver for Atheros 803x PHY
5 *
6 * Author: Matus Ujhelyi <ujhelyi.m@gmail.com>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version.
12 */
13
14#include <linux/phy.h>
15#include <linux/module.h>
16#include <linux/string.h>
17#include <linux/netdevice.h>
18#include <linux/etherdevice.h>
19
20#define AT803X_INTR_ENABLE 0x12
21#define AT803X_INTR_STATUS 0x13
22#define AT803X_WOL_ENABLE 0x01
23#define AT803X_DEVICE_ADDR 0x03
24#define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C
25#define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B
26#define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A
27#define AT803X_MMD_ACCESS_CONTROL 0x0D
28#define AT803X_MMD_ACCESS_CONTROL_DATA 0x0E
29#define AT803X_FUNC_DATA 0x4003
Zhao Qiang77a99392014-03-28 15:39:41 +080030#define AT803X_INER 0x0012
31#define AT803X_INER_INIT 0xec00
32#define AT803X_INSR 0x0013
Mugunthan V N1ca6d1b2013-06-03 20:10:06 +000033#define AT803X_DEBUG_ADDR 0x1D
34#define AT803X_DEBUG_DATA 0x1E
35#define AT803X_DEBUG_SYSTEM_MODE_CTRL 0x05
36#define AT803X_DEBUG_RGMII_TX_CLK_DLY BIT(8)
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000037
Daniel Mackbd8ca172014-06-18 11:01:42 +020038#define ATH8030_PHY_ID 0x004dd076
39#define ATH8031_PHY_ID 0x004dd074
40#define ATH8035_PHY_ID 0x004dd072
41
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000042MODULE_DESCRIPTION("Atheros 803x PHY driver");
43MODULE_AUTHOR("Matus Ujhelyi");
44MODULE_LICENSE("GPL");
45
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000046static int at803x_set_wol(struct phy_device *phydev,
47 struct ethtool_wolinfo *wol)
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000048{
49 struct net_device *ndev = phydev->attached_dev;
50 const u8 *mac;
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000051 int ret;
52 u32 value;
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000053 unsigned int i, offsets[] = {
54 AT803X_LOC_MAC_ADDR_32_47_OFFSET,
55 AT803X_LOC_MAC_ADDR_16_31_OFFSET,
56 AT803X_LOC_MAC_ADDR_0_15_OFFSET,
57 };
58
59 if (!ndev)
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000060 return -ENODEV;
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000061
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000062 if (wol->wolopts & WAKE_MAGIC) {
63 mac = (const u8 *) ndev->dev_addr;
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000064
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000065 if (!is_valid_ether_addr(mac))
66 return -EFAULT;
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000067
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000068 for (i = 0; i < 3; i++) {
69 phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000070 AT803X_DEVICE_ADDR);
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000071 phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000072 offsets[i]);
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000073 phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000074 AT803X_FUNC_DATA);
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000075 phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000076 mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000077 }
78
79 value = phy_read(phydev, AT803X_INTR_ENABLE);
80 value |= AT803X_WOL_ENABLE;
81 ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
82 if (ret)
83 return ret;
84 value = phy_read(phydev, AT803X_INTR_STATUS);
85 } else {
86 value = phy_read(phydev, AT803X_INTR_ENABLE);
87 value &= (~AT803X_WOL_ENABLE);
88 ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
89 if (ret)
90 return ret;
91 value = phy_read(phydev, AT803X_INTR_STATUS);
Matus Ujhelyi0ca71112012-10-14 19:07:16 +000092 }
Mugunthan V Nea13c9e2013-06-03 20:10:05 +000093
94 return ret;
95}
96
97static void at803x_get_wol(struct phy_device *phydev,
98 struct ethtool_wolinfo *wol)
99{
100 u32 value;
101
102 wol->supported = WAKE_MAGIC;
103 wol->wolopts = 0;
104
105 value = phy_read(phydev, AT803X_INTR_ENABLE);
106 if (value & AT803X_WOL_ENABLE)
107 wol->wolopts |= WAKE_MAGIC;
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000108}
109
Daniel Mack6229ed12013-09-21 16:53:02 +0200110static int at803x_suspend(struct phy_device *phydev)
111{
112 int value;
113 int wol_enabled;
114
115 mutex_lock(&phydev->lock);
116
117 value = phy_read(phydev, AT803X_INTR_ENABLE);
118 wol_enabled = value & AT803X_WOL_ENABLE;
119
120 value = phy_read(phydev, MII_BMCR);
121
122 if (wol_enabled)
123 value |= BMCR_ISOLATE;
124 else
125 value |= BMCR_PDOWN;
126
127 phy_write(phydev, MII_BMCR, value);
128
129 mutex_unlock(&phydev->lock);
130
131 return 0;
132}
133
134static int at803x_resume(struct phy_device *phydev)
135{
136 int value;
137
138 mutex_lock(&phydev->lock);
139
140 value = phy_read(phydev, MII_BMCR);
141 value &= ~(BMCR_PDOWN | BMCR_ISOLATE);
142 phy_write(phydev, MII_BMCR, value);
143
144 mutex_unlock(&phydev->lock);
145
146 return 0;
147}
148
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000149static int at803x_config_init(struct phy_device *phydev)
150{
Mugunthan V N1ca6d1b2013-06-03 20:10:06 +0000151 int ret;
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000152
Daniel Mack6ff01db2014-04-16 17:19:13 +0200153 ret = genphy_config_init(phydev);
154 if (ret < 0)
155 return ret;
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000156
Mugunthan V N1ca6d1b2013-06-03 20:10:06 +0000157 if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
158 ret = phy_write(phydev, AT803X_DEBUG_ADDR,
159 AT803X_DEBUG_SYSTEM_MODE_CTRL);
160 if (ret)
161 return ret;
162 ret = phy_write(phydev, AT803X_DEBUG_DATA,
163 AT803X_DEBUG_RGMII_TX_CLK_DLY);
164 if (ret)
165 return ret;
166 }
167
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000168 return 0;
169}
170
Zhao Qiang77a99392014-03-28 15:39:41 +0800171static int at803x_ack_interrupt(struct phy_device *phydev)
172{
173 int err;
174
175 err = phy_read(phydev, AT803X_INSR);
176
177 return (err < 0) ? err : 0;
178}
179
180static int at803x_config_intr(struct phy_device *phydev)
181{
182 int err;
183 int value;
184
185 value = phy_read(phydev, AT803X_INER);
186
187 if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
188 err = phy_write(phydev, AT803X_INER,
189 value | AT803X_INER_INIT);
190 else
191 err = phy_write(phydev, AT803X_INER, 0);
192
193 return err;
194}
195
Mugunthan V N317420a2013-06-03 20:10:04 +0000196static struct phy_driver at803x_driver[] = {
197{
198 /* ATHEROS 8035 */
Daniel Mackbd8ca172014-06-18 11:01:42 +0200199 .phy_id = ATH8035_PHY_ID,
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000200 .name = "Atheros 8035 ethernet",
201 .phy_id_mask = 0xffffffef,
202 .config_init = at803x_config_init,
Mugunthan V Nea13c9e2013-06-03 20:10:05 +0000203 .set_wol = at803x_set_wol,
204 .get_wol = at803x_get_wol,
Daniel Mack6229ed12013-09-21 16:53:02 +0200205 .suspend = at803x_suspend,
206 .resume = at803x_resume,
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000207 .features = PHY_GBIT_FEATURES,
208 .flags = PHY_HAS_INTERRUPT,
Daniel Mack0197ffe2013-09-21 16:53:01 +0200209 .config_aneg = genphy_config_aneg,
210 .read_status = genphy_read_status,
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000211 .driver = {
212 .owner = THIS_MODULE,
213 },
Mugunthan V N317420a2013-06-03 20:10:04 +0000214}, {
215 /* ATHEROS 8030 */
Daniel Mackbd8ca172014-06-18 11:01:42 +0200216 .phy_id = ATH8030_PHY_ID,
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000217 .name = "Atheros 8030 ethernet",
218 .phy_id_mask = 0xffffffef,
219 .config_init = at803x_config_init,
Mugunthan V Nea13c9e2013-06-03 20:10:05 +0000220 .set_wol = at803x_set_wol,
221 .get_wol = at803x_get_wol,
Daniel Mack6229ed12013-09-21 16:53:02 +0200222 .suspend = at803x_suspend,
223 .resume = at803x_resume,
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000224 .features = PHY_GBIT_FEATURES,
225 .flags = PHY_HAS_INTERRUPT,
Daniel Mack0197ffe2013-09-21 16:53:01 +0200226 .config_aneg = genphy_config_aneg,
227 .read_status = genphy_read_status,
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000228 .driver = {
229 .owner = THIS_MODULE,
230 },
Mugunthan V N05d7cce2013-06-03 20:10:07 +0000231}, {
232 /* ATHEROS 8031 */
Daniel Mackbd8ca172014-06-18 11:01:42 +0200233 .phy_id = ATH8031_PHY_ID,
Mugunthan V N05d7cce2013-06-03 20:10:07 +0000234 .name = "Atheros 8031 ethernet",
235 .phy_id_mask = 0xffffffef,
236 .config_init = at803x_config_init,
237 .set_wol = at803x_set_wol,
238 .get_wol = at803x_get_wol,
Daniel Mack6229ed12013-09-21 16:53:02 +0200239 .suspend = at803x_suspend,
240 .resume = at803x_resume,
Mugunthan V N05d7cce2013-06-03 20:10:07 +0000241 .features = PHY_GBIT_FEATURES,
242 .flags = PHY_HAS_INTERRUPT,
Daniel Mack0197ffe2013-09-21 16:53:01 +0200243 .config_aneg = genphy_config_aneg,
244 .read_status = genphy_read_status,
Zhao Qiang77a99392014-03-28 15:39:41 +0800245 .ack_interrupt = &at803x_ack_interrupt,
246 .config_intr = &at803x_config_intr,
Mugunthan V N05d7cce2013-06-03 20:10:07 +0000247 .driver = {
248 .owner = THIS_MODULE,
249 },
Mugunthan V N317420a2013-06-03 20:10:04 +0000250} };
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000251
252static int __init atheros_init(void)
253{
Mugunthan V N317420a2013-06-03 20:10:04 +0000254 return phy_drivers_register(at803x_driver,
255 ARRAY_SIZE(at803x_driver));
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000256}
257
258static void __exit atheros_exit(void)
259{
Shruti Kanetkar2ebb1582014-04-22 14:21:47 -0500260 phy_drivers_unregister(at803x_driver, ARRAY_SIZE(at803x_driver));
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000261}
262
263module_init(atheros_init);
264module_exit(atheros_exit);
265
266static struct mdio_device_id __maybe_unused atheros_tbl[] = {
Daniel Mackbd8ca172014-06-18 11:01:42 +0200267 { ATH8030_PHY_ID, 0xffffffef },
268 { ATH8031_PHY_ID, 0xffffffef },
269 { ATH8035_PHY_ID, 0xffffffef },
Matus Ujhelyi0ca71112012-10-14 19:07:16 +0000270 { }
271};
272
273MODULE_DEVICE_TABLE(mdio, atheros_tbl);