xref: /openbmc/linux/drivers/net/phy/smsc.c (revision 1c2dd16a)
1 /*
2  * drivers/net/phy/smsc.c
3  *
4  * Driver for SMSC PHYs
5  *
6  * Author: Herbert Valerio Riedel
7  *
8  * Copyright (c) 2006 Herbert Valerio Riedel <hvr@gnu.org>
9  *
10  * This program is free software; you can redistribute  it and/or modify it
11  * under  the terms of  the GNU General  Public License as published by the
12  * Free Software Foundation;  either version 2 of the  License, or (at your
13  * option) any later version.
14  *
15  * Support added for SMSC LAN8187 and LAN8700 by steve.glendinning@shawell.net
16  *
17  */
18 
19 #include <linux/kernel.h>
20 #include <linux/module.h>
21 #include <linux/mii.h>
22 #include <linux/ethtool.h>
23 #include <linux/of.h>
24 #include <linux/phy.h>
25 #include <linux/netdevice.h>
26 #include <linux/smscphy.h>
27 
28 struct smsc_phy_priv {
29 	bool energy_enable;
30 };
31 
32 static int smsc_phy_config_intr(struct phy_device *phydev)
33 {
34 	int rc = phy_write (phydev, MII_LAN83C185_IM,
35 			((PHY_INTERRUPT_ENABLED == phydev->interrupts)
36 			? MII_LAN83C185_ISF_INT_PHYLIB_EVENTS
37 			: 0));
38 
39 	return rc < 0 ? rc : 0;
40 }
41 
42 static int smsc_phy_ack_interrupt(struct phy_device *phydev)
43 {
44 	int rc = phy_read (phydev, MII_LAN83C185_ISF);
45 
46 	return rc < 0 ? rc : 0;
47 }
48 
49 static int smsc_phy_config_init(struct phy_device *phydev)
50 {
51 	struct smsc_phy_priv *priv = phydev->priv;
52 
53 	int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
54 
55 	if (rc < 0)
56 		return rc;
57 
58 	if (priv->energy_enable) {
59 		/* Enable energy detect mode for this SMSC Transceivers */
60 		rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
61 			       rc | MII_LAN83C185_EDPWRDOWN);
62 		if (rc < 0)
63 			return rc;
64 	}
65 
66 	return smsc_phy_ack_interrupt(phydev);
67 }
68 
69 static int smsc_phy_reset(struct phy_device *phydev)
70 {
71 	int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES);
72 	if (rc < 0)
73 		return rc;
74 
75 	/* If the SMSC PHY is in power down mode, then set it
76 	 * in all capable mode before using it.
77 	 */
78 	if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) {
79 		/* set "all capable" mode */
80 		rc |= MII_LAN83C185_MODE_ALL;
81 		phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
82 	}
83 
84 	/* reset the phy */
85 	return genphy_soft_reset(phydev);
86 }
87 
88 static int lan911x_config_init(struct phy_device *phydev)
89 {
90 	return smsc_phy_ack_interrupt(phydev);
91 }
92 
93 /*
94  * The LAN87xx suffers from rare absence of the ENERGYON-bit when Ethernet cable
95  * plugs in while LAN87xx is in Energy Detect Power-Down mode. This leads to
96  * unstable detection of plugging in Ethernet cable.
97  * This workaround disables Energy Detect Power-Down mode and waiting for
98  * response on link pulses to detect presence of plugged Ethernet cable.
99  * The Energy Detect Power-Down mode is enabled again in the end of procedure to
100  * save approximately 220 mW of power if cable is unplugged.
101  */
102 static int lan87xx_read_status(struct phy_device *phydev)
103 {
104 	struct smsc_phy_priv *priv = phydev->priv;
105 
106 	int err = genphy_read_status(phydev);
107 
108 	if (!phydev->link && priv->energy_enable) {
109 		int i;
110 
111 		/* Disable EDPD to wake up PHY */
112 		int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
113 		if (rc < 0)
114 			return rc;
115 
116 		rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
117 			       rc & ~MII_LAN83C185_EDPWRDOWN);
118 		if (rc < 0)
119 			return rc;
120 
121 		/* Wait max 640 ms to detect energy */
122 		for (i = 0; i < 64; i++) {
123 			/* Sleep to allow link test pulses to be sent */
124 			msleep(10);
125 			rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
126 			if (rc < 0)
127 				return rc;
128 			if (rc & MII_LAN83C185_ENERGYON)
129 				break;
130 		}
131 
132 		/* Re-enable EDPD */
133 		rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
134 		if (rc < 0)
135 			return rc;
136 
137 		rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
138 			       rc | MII_LAN83C185_EDPWRDOWN);
139 		if (rc < 0)
140 			return rc;
141 	}
142 
143 	return err;
144 }
145 
146 static int smsc_phy_probe(struct phy_device *phydev)
147 {
148 	struct device *dev = &phydev->mdio.dev;
149 	struct device_node *of_node = dev->of_node;
150 	struct smsc_phy_priv *priv;
151 
152 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
153 	if (!priv)
154 		return -ENOMEM;
155 
156 	priv->energy_enable = true;
157 
158 	if (of_property_read_bool(of_node, "smsc,disable-energy-detect"))
159 		priv->energy_enable = false;
160 
161 	phydev->priv = priv;
162 
163 	return 0;
164 }
165 
166 static struct phy_driver smsc_phy_driver[] = {
167 {
168 	.phy_id		= 0x0007c0a0, /* OUI=0x00800f, Model#=0x0a */
169 	.phy_id_mask	= 0xfffffff0,
170 	.name		= "SMSC LAN83C185",
171 
172 	.features	= PHY_BASIC_FEATURES,
173 	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
174 
175 	.probe		= smsc_phy_probe,
176 
177 	/* basic functions */
178 	.config_aneg	= genphy_config_aneg,
179 	.read_status	= genphy_read_status,
180 	.config_init	= smsc_phy_config_init,
181 	.soft_reset	= smsc_phy_reset,
182 
183 	/* IRQ related */
184 	.ack_interrupt	= smsc_phy_ack_interrupt,
185 	.config_intr	= smsc_phy_config_intr,
186 
187 	.suspend	= genphy_suspend,
188 	.resume		= genphy_resume,
189 }, {
190 	.phy_id		= 0x0007c0b0, /* OUI=0x00800f, Model#=0x0b */
191 	.phy_id_mask	= 0xfffffff0,
192 	.name		= "SMSC LAN8187",
193 
194 	.features	= PHY_BASIC_FEATURES,
195 	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
196 
197 	.probe		= smsc_phy_probe,
198 
199 	/* basic functions */
200 	.config_aneg	= genphy_config_aneg,
201 	.read_status	= genphy_read_status,
202 	.config_init	= smsc_phy_config_init,
203 	.soft_reset	= smsc_phy_reset,
204 
205 	/* IRQ related */
206 	.ack_interrupt	= smsc_phy_ack_interrupt,
207 	.config_intr	= smsc_phy_config_intr,
208 
209 	.suspend	= genphy_suspend,
210 	.resume		= genphy_resume,
211 }, {
212 	.phy_id		= 0x0007c0c0, /* OUI=0x00800f, Model#=0x0c */
213 	.phy_id_mask	= 0xfffffff0,
214 	.name		= "SMSC LAN8700",
215 
216 	.features	= PHY_BASIC_FEATURES,
217 	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
218 
219 	.probe		= smsc_phy_probe,
220 
221 	/* basic functions */
222 	.config_aneg	= genphy_config_aneg,
223 	.read_status	= lan87xx_read_status,
224 	.config_init	= smsc_phy_config_init,
225 	.soft_reset	= smsc_phy_reset,
226 
227 	/* IRQ related */
228 	.ack_interrupt	= smsc_phy_ack_interrupt,
229 	.config_intr	= smsc_phy_config_intr,
230 
231 	.suspend	= genphy_suspend,
232 	.resume		= genphy_resume,
233 }, {
234 	.phy_id		= 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */
235 	.phy_id_mask	= 0xfffffff0,
236 	.name		= "SMSC LAN911x Internal PHY",
237 
238 	.features	= PHY_BASIC_FEATURES,
239 	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
240 
241 	.probe		= smsc_phy_probe,
242 
243 	/* basic functions */
244 	.config_aneg	= genphy_config_aneg,
245 	.read_status	= genphy_read_status,
246 	.config_init	= lan911x_config_init,
247 
248 	/* IRQ related */
249 	.ack_interrupt	= smsc_phy_ack_interrupt,
250 	.config_intr	= smsc_phy_config_intr,
251 
252 	.suspend	= genphy_suspend,
253 	.resume		= genphy_resume,
254 }, {
255 	.phy_id		= 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */
256 	.phy_id_mask	= 0xfffffff0,
257 	.name		= "SMSC LAN8710/LAN8720",
258 
259 	.features	= PHY_BASIC_FEATURES,
260 	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
261 
262 	.probe		= smsc_phy_probe,
263 
264 	/* basic functions */
265 	.config_aneg	= genphy_config_aneg,
266 	.read_status	= lan87xx_read_status,
267 	.config_init	= smsc_phy_config_init,
268 	.soft_reset	= smsc_phy_reset,
269 
270 	/* IRQ related */
271 	.ack_interrupt	= smsc_phy_ack_interrupt,
272 	.config_intr	= smsc_phy_config_intr,
273 
274 	.suspend	= genphy_suspend,
275 	.resume		= genphy_resume,
276 }, {
277 	.phy_id		= 0x0007c110,
278 	.phy_id_mask	= 0xfffffff0,
279 	.name		= "SMSC LAN8740",
280 
281 	.features	= PHY_BASIC_FEATURES,
282 	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
283 
284 	.probe		= smsc_phy_probe,
285 
286 	/* basic functions */
287 	.config_aneg	= genphy_config_aneg,
288 	.read_status	= lan87xx_read_status,
289 	.config_init	= smsc_phy_config_init,
290 	.soft_reset	= smsc_phy_reset,
291 
292 	/* IRQ related */
293 	.ack_interrupt	= smsc_phy_ack_interrupt,
294 	.config_intr	= smsc_phy_config_intr,
295 
296 	.suspend	= genphy_suspend,
297 	.resume		= genphy_resume,
298 } };
299 
300 module_phy_driver(smsc_phy_driver);
301 
302 MODULE_DESCRIPTION("SMSC PHY driver");
303 MODULE_AUTHOR("Herbert Valerio Riedel");
304 MODULE_LICENSE("GPL");
305 
306 static struct mdio_device_id __maybe_unused smsc_tbl[] = {
307 	{ 0x0007c0a0, 0xfffffff0 },
308 	{ 0x0007c0b0, 0xfffffff0 },
309 	{ 0x0007c0c0, 0xfffffff0 },
310 	{ 0x0007c0d0, 0xfffffff0 },
311 	{ 0x0007c0f0, 0xfffffff0 },
312 	{ 0x0007c110, 0xfffffff0 },
313 	{ }
314 };
315 
316 MODULE_DEVICE_TABLE(mdio, smsc_tbl);
317