net: phy: add Broadcom BCM7xxx internal PHY driver
[deliverable/linux.git] / drivers / net / phy / bcm7xxx.c
CommitLineData
b560a58c
FF
1/*
2 * Broadcom BCM7xxx internal transceivers support.
3 *
4 * Copyright (C) 2014, Broadcom Corporation
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 */
11
12#include <linux/module.h>
13#include <linux/phy.h>
14#include <linux/delay.h>
15#include <linux/bitops.h>
16#include <linux/brcmphy.h>
17
18/* Broadcom BCM7xxx internal PHY registers */
19#define MII_BCM7XXX_CHANNEL_WIDTH 0x2000
20
21/* 40nm only register definitions */
22#define MII_BCM7XXX_100TX_AUX_CTL 0x10
23#define MII_BCM7XXX_100TX_FALSE_CAR 0x13
24#define MII_BCM7XXX_100TX_DISC 0x14
25#define MII_BCM7XXX_AUX_MODE 0x1d
26#define MII_BCM7XX_64CLK_MDIO BIT(12)
27#define MII_BCM7XXX_CORE_BASE1E 0x1e
28#define MII_BCM7XXX_TEST 0x1f
29#define MII_BCM7XXX_SHD_MODE_2 BIT(2)
30
31static int bcm7445_config_init(struct phy_device *phydev)
32{
33 int ret;
34 const struct bcm7445_regs {
35 int reg;
36 u16 value;
37 } bcm7445_regs_cfg[] = {
38 /* increases ADC latency by 24ns */
39 { MII_BCM54XX_EXP_SEL, 0x0038 },
40 { MII_BCM54XX_EXP_DATA, 0xAB95 },
41 /* increases internal 1V LDO voltage by 5% */
42 { MII_BCM54XX_EXP_SEL, 0x2038 },
43 { MII_BCM54XX_EXP_DATA, 0xBB22 },
44 /* reduce RX low pass filter corner frequency */
45 { MII_BCM54XX_EXP_SEL, 0x6038 },
46 { MII_BCM54XX_EXP_DATA, 0xFFC5 },
47 /* reduce RX high pass filter corner frequency */
48 { MII_BCM54XX_EXP_SEL, 0x003a },
49 { MII_BCM54XX_EXP_DATA, 0x2002 },
50 };
51 unsigned int i;
52
53 for (i = 0; i < ARRAY_SIZE(bcm7445_regs_cfg); i++) {
54 ret = phy_write(phydev,
55 bcm7445_regs_cfg[i].reg,
56 bcm7445_regs_cfg[i].value);
57 if (ret)
58 return ret;
59 }
60
61 return 0;
62}
63
64static void phy_write_exp(struct phy_device *phydev,
65 u16 reg, u16 value)
66{
67 phy_write(phydev, MII_BCM54XX_EXP_SEL, MII_BCM54XX_EXP_SEL_ER | reg);
68 phy_write(phydev, MII_BCM54XX_EXP_DATA, value);
69}
70
71static void phy_write_misc(struct phy_device *phydev,
72 u16 reg, u16 chl, u16 value)
73{
74 int tmp;
75
76 phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
77
78 tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
79 tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
80 phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
81
82 tmp = (chl * MII_BCM7XXX_CHANNEL_WIDTH) | reg;
83 phy_write(phydev, MII_BCM54XX_EXP_SEL, tmp);
84
85 phy_write(phydev, MII_BCM54XX_EXP_DATA, value);
86}
87
88static int bcm7xxx_28nm_afe_config_init(struct phy_device *phydev)
89{
90 /* write AFE_RXCONFIG_0 */
91 phy_write_misc(phydev, 0x38, 0x0000, 0xeb19);
92
93 /* write AFE_RXCONFIG_1 */
94 phy_write_misc(phydev, 0x38, 0x0001, 0x9a3f);
95
96 /* write AFE_RX_LP_COUNTER */
97 phy_write_misc(phydev, 0x38, 0x0003, 0x7fc7);
98
99 /* write AFE_HPF_TRIM_OTHERS */
100 phy_write_misc(phydev, 0x3A, 0x0000, 0x000b);
101
102 /* write AFTE_TX_CONFIG */
103 phy_write_misc(phydev, 0x39, 0x0000, 0x0800);
104
105 /* Increase VCO range to prevent unlocking problem of PLL at low
106 * temp
107 */
108 phy_write_misc(phydev, 0x0032, 0x0001, 0x0048);
109
110 /* Change Ki to 011 */
111 phy_write_misc(phydev, 0x0032, 0x0002, 0x021b);
112
113 /* Disable loading of TVCO buffer to bandgap, set bandgap trim
114 * to 111
115 */
116 phy_write_misc(phydev, 0x0033, 0x0000, 0x0e20);
117
118 /* Adjust bias current trim by -3 */
119 phy_write_misc(phydev, 0x000a, 0x0000, 0x690b);
120
121 /* Switch to CORE_BASE1E */
122 phy_write(phydev, MII_BCM7XXX_CORE_BASE1E, 0xd);
123
124 /* Reset R_CAL/RC_CAL Engine */
125 phy_write_exp(phydev, 0x00b0, 0x0010);
126
127 /* Disable Reset R_CAL/RC_CAL Engine */
128 phy_write_exp(phydev, 0x00b0, 0x0000);
129
130 return 0;
131}
132
133static int bcm7xxx_28nm_config_init(struct phy_device *phydev)
134{
135 int ret;
136
137 ret = bcm7445_config_init(phydev);
138 if (ret)
139 return ret;
140
141 return bcm7xxx_28nm_afe_config_init(phydev);
142}
143
144static int phy_set_clr_bits(struct phy_device *dev, int location,
145 int set_mask, int clr_mask)
146{
147 int v, ret;
148
149 v = phy_read(dev, location);
150 if (v < 0)
151 return v;
152
153 v &= ~clr_mask;
154 v |= set_mask;
155
156 ret = phy_write(dev, location, v);
157 if (ret < 0)
158 return ret;
159
160 return v;
161}
162
163static int bcm7xxx_config_init(struct phy_device *phydev)
164{
165 int ret;
166
167 /* Enable 64 clock MDIO */
168 phy_write(phydev, MII_BCM7XXX_AUX_MODE, MII_BCM7XX_64CLK_MDIO);
169 phy_read(phydev, MII_BCM7XXX_AUX_MODE);
170
171 /* Workaround only required for 100Mbits/sec */
172 if (!(phydev->dev_flags & PHY_BRCM_100MBPS_WAR))
173 return 0;
174
175 /* set shadow mode 2 */
176 ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST,
177 MII_BCM7XXX_SHD_MODE_2, MII_BCM7XXX_SHD_MODE_2);
178 if (ret < 0)
179 return ret;
180
181 /* set iddq_clkbias */
182 phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0F00);
183 udelay(10);
184
185 /* reset iddq_clkbias */
186 phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0C00);
187
188 phy_write(phydev, MII_BCM7XXX_100TX_FALSE_CAR, 0x7555);
189
190 /* reset shadow mode 2 */
191 ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, MII_BCM7XXX_SHD_MODE_2, 0);
192 if (ret < 0)
193 return ret;
194
195 return 0;
196}
197
198/* Workaround for putting the PHY in IDDQ mode, required
199 * for all BCM7XXX PHYs
200 */
201static int bcm7xxx_suspend(struct phy_device *phydev)
202{
203 int ret;
204 const struct bcm7xxx_regs {
205 int reg;
206 u16 value;
207 } bcm7xxx_suspend_cfg[] = {
208 { MII_BCM7XXX_TEST, 0x008b },
209 { MII_BCM7XXX_100TX_AUX_CTL, 0x01c0 },
210 { MII_BCM7XXX_100TX_DISC, 0x7000 },
211 { MII_BCM7XXX_TEST, 0x000f },
212 { MII_BCM7XXX_100TX_AUX_CTL, 0x20d0 },
213 { MII_BCM7XXX_TEST, 0x000b },
214 };
215 unsigned int i;
216
217 for (i = 0; i < ARRAY_SIZE(bcm7xxx_suspend_cfg); i++) {
218 ret = phy_write(phydev,
219 bcm7xxx_suspend_cfg[i].reg,
220 bcm7xxx_suspend_cfg[i].value);
221 if (ret)
222 return ret;
223 }
224
225 return 0;
226}
227
228static int bcm7xxx_dummy_config_init(struct phy_device *phydev)
229{
230 return 0;
231}
232
233static struct phy_driver bcm7xxx_driver[] = {
234{
235 .phy_id = PHY_ID_BCM7366,
236 .phy_id_mask = 0xfffffff0,
237 .name = "Broadcom BCM7366",
238 .features = PHY_GBIT_FEATURES |
239 SUPPORTED_Pause | SUPPORTED_Asym_Pause,
240 .flags = PHY_IS_INTERNAL,
241 .config_init = bcm7xxx_28nm_afe_config_init,
242 .config_aneg = genphy_config_aneg,
243 .read_status = genphy_read_status,
244 .suspend = bcm7xxx_suspend,
245 .resume = bcm7xxx_28nm_afe_config_init,
246 .driver = { .owner = THIS_MODULE },
247}, {
248 .phy_id = PHY_ID_BCM7439,
249 .phy_id_mask = 0xfffffff0,
250 .name = "Broadcom BCM7439",
251 .features = PHY_GBIT_FEATURES |
252 SUPPORTED_Pause | SUPPORTED_Asym_Pause,
253 .flags = PHY_IS_INTERNAL,
254 .config_init = bcm7xxx_28nm_afe_config_init,
255 .config_aneg = genphy_config_aneg,
256 .read_status = genphy_read_status,
257 .suspend = bcm7xxx_suspend,
258 .resume = bcm7xxx_28nm_afe_config_init,
259 .driver = { .owner = THIS_MODULE },
260}, {
261 .phy_id = PHY_ID_BCM7445,
262 .phy_id_mask = 0xfffffff0,
263 .name = "Broadcom BCM7445",
264 .features = PHY_GBIT_FEATURES |
265 SUPPORTED_Pause | SUPPORTED_Asym_Pause,
266 .flags = PHY_IS_INTERNAL,
267 .config_init = bcm7xxx_28nm_config_init,
268 .config_aneg = genphy_config_aneg,
269 .read_status = genphy_read_status,
270 .suspend = bcm7xxx_suspend,
271 .resume = bcm7xxx_28nm_config_init,
272 .driver = { .owner = THIS_MODULE },
273}, {
274 .name = "Broadcom BCM7XXX 28nm",
275 .phy_id = PHY_ID_BCM7XXX_28,
276 .phy_id_mask = PHY_BCM_OUI_MASK,
277 .features = PHY_GBIT_FEATURES |
278 SUPPORTED_Pause | SUPPORTED_Asym_Pause,
279 .flags = PHY_IS_INTERNAL,
280 .config_init = bcm7xxx_28nm_config_init,
281 .config_aneg = genphy_config_aneg,
282 .read_status = genphy_read_status,
283 .suspend = bcm7xxx_suspend,
284 .resume = bcm7xxx_28nm_config_init,
285 .driver = { .owner = THIS_MODULE },
286}, {
287 .phy_id = PHY_BCM_OUI_4,
288 .phy_id_mask = 0xffff0000,
289 .name = "Broadcom BCM7XXX 40nm",
290 .features = PHY_GBIT_FEATURES |
291 SUPPORTED_Pause | SUPPORTED_Asym_Pause,
292 .flags = PHY_IS_INTERNAL,
293 .config_init = bcm7xxx_config_init,
294 .config_aneg = genphy_config_aneg,
295 .read_status = genphy_read_status,
296 .suspend = bcm7xxx_suspend,
297 .resume = bcm7xxx_config_init,
298 .driver = { .owner = THIS_MODULE },
299}, {
300 .phy_id = PHY_BCM_OUI_5,
301 .phy_id_mask = 0xffffff00,
302 .name = "Broadcom BCM7XXX 65nm",
303 .features = PHY_BASIC_FEATURES |
304 SUPPORTED_Pause | SUPPORTED_Asym_Pause,
305 .flags = PHY_IS_INTERNAL,
306 .config_init = bcm7xxx_dummy_config_init,
307 .config_aneg = genphy_config_aneg,
308 .read_status = genphy_read_status,
309 .suspend = bcm7xxx_suspend,
310 .resume = bcm7xxx_config_init,
311 .driver = { .owner = THIS_MODULE },
312} };
313
314static struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = {
315 { PHY_ID_BCM7366, 0xfffffff0, },
316 { PHY_ID_BCM7439, 0xfffffff0, },
317 { PHY_ID_BCM7445, 0xfffffff0, },
318 { PHY_ID_BCM7XXX_28, 0xfffffc00 },
319 { PHY_BCM_OUI_4, 0xffff0000 },
320 { PHY_BCM_OUI_5, 0xffffff00 },
321 { }
322};
323
324static int __init bcm7xxx_phy_init(void)
325{
326 return phy_drivers_register(bcm7xxx_driver,
327 ARRAY_SIZE(bcm7xxx_driver));
328}
329
330static void __exit bcm7xxx_phy_exit(void)
331{
332 phy_drivers_unregister(bcm7xxx_driver,
333 ARRAY_SIZE(bcm7xxx_driver));
334}
335
336module_init(bcm7xxx_phy_init);
337module_exit(bcm7xxx_phy_exit);
338
339MODULE_DEVICE_TABLE(mdio, bcm7xxx_tbl);
340
341MODULE_DESCRIPTION("Broadcom BCM7xxx internal PHY driver");
342MODULE_LICENSE("GPL");
343MODULE_AUTHOR("Broadcom Corporation");
This page took 0.036458 seconds and 5 git commands to generate.