Commit | Line | Data |
---|---|---|
0ca7111a MU |
1 | /* |
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 | |
30 | ||
31 | MODULE_DESCRIPTION("Atheros 803x PHY driver"); | |
32 | MODULE_AUTHOR("Matus Ujhelyi"); | |
33 | MODULE_LICENSE("GPL"); | |
34 | ||
ea13c9ee M |
35 | static int at803x_set_wol(struct phy_device *phydev, |
36 | struct ethtool_wolinfo *wol) | |
0ca7111a MU |
37 | { |
38 | struct net_device *ndev = phydev->attached_dev; | |
39 | const u8 *mac; | |
ea13c9ee M |
40 | int ret; |
41 | u32 value; | |
0ca7111a MU |
42 | unsigned int i, offsets[] = { |
43 | AT803X_LOC_MAC_ADDR_32_47_OFFSET, | |
44 | AT803X_LOC_MAC_ADDR_16_31_OFFSET, | |
45 | AT803X_LOC_MAC_ADDR_0_15_OFFSET, | |
46 | }; | |
47 | ||
48 | if (!ndev) | |
ea13c9ee | 49 | return -ENODEV; |
0ca7111a | 50 | |
ea13c9ee M |
51 | if (wol->wolopts & WAKE_MAGIC) { |
52 | mac = (const u8 *) ndev->dev_addr; | |
0ca7111a | 53 | |
ea13c9ee M |
54 | if (!is_valid_ether_addr(mac)) |
55 | return -EFAULT; | |
0ca7111a | 56 | |
ea13c9ee M |
57 | for (i = 0; i < 3; i++) { |
58 | phy_write(phydev, AT803X_MMD_ACCESS_CONTROL, | |
0ca7111a | 59 | AT803X_DEVICE_ADDR); |
ea13c9ee | 60 | phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA, |
0ca7111a | 61 | offsets[i]); |
ea13c9ee | 62 | phy_write(phydev, AT803X_MMD_ACCESS_CONTROL, |
0ca7111a | 63 | AT803X_FUNC_DATA); |
ea13c9ee | 64 | phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA, |
0ca7111a | 65 | mac[(i * 2) + 1] | (mac[(i * 2)] << 8)); |
ea13c9ee M |
66 | } |
67 | ||
68 | value = phy_read(phydev, AT803X_INTR_ENABLE); | |
69 | value |= AT803X_WOL_ENABLE; | |
70 | ret = phy_write(phydev, AT803X_INTR_ENABLE, value); | |
71 | if (ret) | |
72 | return ret; | |
73 | value = phy_read(phydev, AT803X_INTR_STATUS); | |
74 | } else { | |
75 | value = phy_read(phydev, AT803X_INTR_ENABLE); | |
76 | value &= (~AT803X_WOL_ENABLE); | |
77 | ret = phy_write(phydev, AT803X_INTR_ENABLE, value); | |
78 | if (ret) | |
79 | return ret; | |
80 | value = phy_read(phydev, AT803X_INTR_STATUS); | |
0ca7111a | 81 | } |
ea13c9ee M |
82 | |
83 | return ret; | |
84 | } | |
85 | ||
86 | static void at803x_get_wol(struct phy_device *phydev, | |
87 | struct ethtool_wolinfo *wol) | |
88 | { | |
89 | u32 value; | |
90 | ||
91 | wol->supported = WAKE_MAGIC; | |
92 | wol->wolopts = 0; | |
93 | ||
94 | value = phy_read(phydev, AT803X_INTR_ENABLE); | |
95 | if (value & AT803X_WOL_ENABLE) | |
96 | wol->wolopts |= WAKE_MAGIC; | |
0ca7111a MU |
97 | } |
98 | ||
99 | static int at803x_config_init(struct phy_device *phydev) | |
100 | { | |
101 | int val; | |
102 | u32 features; | |
0ca7111a MU |
103 | |
104 | features = SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_AUI | | |
105 | SUPPORTED_FIBRE | SUPPORTED_BNC; | |
106 | ||
107 | val = phy_read(phydev, MII_BMSR); | |
108 | if (val < 0) | |
109 | return val; | |
110 | ||
111 | if (val & BMSR_ANEGCAPABLE) | |
112 | features |= SUPPORTED_Autoneg; | |
113 | if (val & BMSR_100FULL) | |
114 | features |= SUPPORTED_100baseT_Full; | |
115 | if (val & BMSR_100HALF) | |
116 | features |= SUPPORTED_100baseT_Half; | |
117 | if (val & BMSR_10FULL) | |
118 | features |= SUPPORTED_10baseT_Full; | |
119 | if (val & BMSR_10HALF) | |
120 | features |= SUPPORTED_10baseT_Half; | |
121 | ||
122 | if (val & BMSR_ESTATEN) { | |
123 | val = phy_read(phydev, MII_ESTATUS); | |
124 | if (val < 0) | |
125 | return val; | |
126 | ||
127 | if (val & ESTATUS_1000_TFULL) | |
128 | features |= SUPPORTED_1000baseT_Full; | |
129 | if (val & ESTATUS_1000_THALF) | |
130 | features |= SUPPORTED_1000baseT_Half; | |
131 | } | |
132 | ||
133 | phydev->supported = features; | |
134 | phydev->advertising = features; | |
135 | ||
0ca7111a MU |
136 | return 0; |
137 | } | |
138 | ||
317420ab M |
139 | static struct phy_driver at803x_driver[] = { |
140 | { | |
141 | /* ATHEROS 8035 */ | |
0ca7111a MU |
142 | .phy_id = 0x004dd072, |
143 | .name = "Atheros 8035 ethernet", | |
144 | .phy_id_mask = 0xffffffef, | |
145 | .config_init = at803x_config_init, | |
ea13c9ee M |
146 | .set_wol = at803x_set_wol, |
147 | .get_wol = at803x_get_wol, | |
0ca7111a MU |
148 | .features = PHY_GBIT_FEATURES, |
149 | .flags = PHY_HAS_INTERRUPT, | |
150 | .config_aneg = &genphy_config_aneg, | |
151 | .read_status = &genphy_read_status, | |
152 | .driver = { | |
153 | .owner = THIS_MODULE, | |
154 | }, | |
317420ab M |
155 | }, { |
156 | /* ATHEROS 8030 */ | |
0ca7111a MU |
157 | .phy_id = 0x004dd076, |
158 | .name = "Atheros 8030 ethernet", | |
159 | .phy_id_mask = 0xffffffef, | |
160 | .config_init = at803x_config_init, | |
ea13c9ee M |
161 | .set_wol = at803x_set_wol, |
162 | .get_wol = at803x_get_wol, | |
0ca7111a MU |
163 | .features = PHY_GBIT_FEATURES, |
164 | .flags = PHY_HAS_INTERRUPT, | |
165 | .config_aneg = &genphy_config_aneg, | |
166 | .read_status = &genphy_read_status, | |
167 | .driver = { | |
168 | .owner = THIS_MODULE, | |
169 | }, | |
317420ab | 170 | } }; |
0ca7111a MU |
171 | |
172 | static int __init atheros_init(void) | |
173 | { | |
317420ab M |
174 | return phy_drivers_register(at803x_driver, |
175 | ARRAY_SIZE(at803x_driver)); | |
0ca7111a MU |
176 | } |
177 | ||
178 | static void __exit atheros_exit(void) | |
179 | { | |
317420ab M |
180 | return phy_drivers_unregister(at803x_driver, |
181 | ARRAY_SIZE(at803x_driver)); | |
0ca7111a MU |
182 | } |
183 | ||
184 | module_init(atheros_init); | |
185 | module_exit(atheros_exit); | |
186 | ||
187 | static struct mdio_device_id __maybe_unused atheros_tbl[] = { | |
188 | { 0x004dd076, 0xffffffef }, | |
189 | { 0x004dd072, 0xffffffef }, | |
190 | { } | |
191 | }; | |
192 | ||
193 | MODULE_DEVICE_TABLE(mdio, atheros_tbl); |