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/phy.h>
24#include <linux/netdevice.h>
25#include <linux/smscphy.h>
26
27static int smsc_phy_config_intr(struct phy_device *phydev)
28{
29	int rc = phy_write (phydev, MII_LAN83C185_IM,
30			((PHY_INTERRUPT_ENABLED == phydev->interrupts)
31			? MII_LAN83C185_ISF_INT_PHYLIB_EVENTS
32			: 0));
33
34	return rc < 0 ? rc : 0;
35}
36
37static int smsc_phy_ack_interrupt(struct phy_device *phydev)
38{
39	int rc = phy_read (phydev, MII_LAN83C185_ISF);
40
41	return rc < 0 ? rc : 0;
42}
43
44static int smsc_phy_config_init(struct phy_device *phydev)
45{
46	int __maybe_unused len;
47	struct device *dev __maybe_unused = &phydev->dev;
48	struct device_node *of_node __maybe_unused = dev->of_node;
49	int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
50	int enable_energy = 1;
51
52	if (rc < 0)
53		return rc;
54
55	if (of_find_property(of_node, "smsc,disable-energy-detect", &len))
56		enable_energy = 0;
57
58	if (enable_energy) {
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
69static 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		int timeout = 50000;
80
81		/* set "all capable" mode and reset the phy */
82		rc |= MII_LAN83C185_MODE_ALL;
83		phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
84		phy_write(phydev, MII_BMCR, BMCR_RESET);
85
86		/* wait end of reset (max 500 ms) */
87		do {
88			udelay(10);
89			if (timeout-- == 0)
90				return -1;
91			rc = phy_read(phydev, MII_BMCR);
92		} while (rc & BMCR_RESET);
93	}
94	return 0;
95}
96
97static int lan911x_config_init(struct phy_device *phydev)
98{
99	return smsc_phy_ack_interrupt(phydev);
100}
101
102/*
103 * The LAN87xx suffers from rare absence of the ENERGYON-bit when Ethernet cable
104 * plugs in while LAN87xx is in Energy Detect Power-Down mode. This leads to
105 * unstable detection of plugging in Ethernet cable.
106 * This workaround disables Energy Detect Power-Down mode and waiting for
107 * response on link pulses to detect presence of plugged Ethernet cable.
108 * The Energy Detect Power-Down mode is enabled again in the end of procedure to
109 * save approximately 220 mW of power if cable is unplugged.
110 */
111static int lan87xx_read_status(struct phy_device *phydev)
112{
113	int err = genphy_read_status(phydev);
114	int i;
115
116	if (!phydev->link) {
117		/* Disable EDPD to wake up PHY */
118		int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
119		if (rc < 0)
120			return rc;
121
122		rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
123			       rc & ~MII_LAN83C185_EDPWRDOWN);
124		if (rc < 0)
125			return rc;
126
127		/* Wait max 640 ms to detect energy */
128		for (i = 0; i < 64; i++) {
129			/* Sleep to allow link test pulses to be sent */
130			msleep(10);
131			rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
132			if (rc < 0)
133				return rc;
134			if (rc & MII_LAN83C185_ENERGYON)
135				break;
136		}
137
138		/* Re-enable EDPD */
139		rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
140		if (rc < 0)
141			return rc;
142
143		rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
144			       rc | MII_LAN83C185_EDPWRDOWN);
145		if (rc < 0)
146			return rc;
147	}
148
149	return err;
150}
151
152static struct phy_driver smsc_phy_driver[] = {
153{
154	.phy_id		= 0x0007c0a0, /* OUI=0x00800f, Model#=0x0a */
155	.phy_id_mask	= 0xfffffff0,
156	.name		= "SMSC LAN83C185",
157
158	.features	= (PHY_BASIC_FEATURES | SUPPORTED_Pause
159				| SUPPORTED_Asym_Pause),
160	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
161
162	/* basic functions */
163	.config_aneg	= genphy_config_aneg,
164	.read_status	= genphy_read_status,
165	.config_init	= smsc_phy_config_init,
166	.soft_reset	= smsc_phy_reset,
167
168	/* IRQ related */
169	.ack_interrupt	= smsc_phy_ack_interrupt,
170	.config_intr	= smsc_phy_config_intr,
171
172	.suspend	= genphy_suspend,
173	.resume		= genphy_resume,
174
175	.driver		= { .owner = THIS_MODULE, }
176}, {
177	.phy_id		= 0x0007c0b0, /* OUI=0x00800f, Model#=0x0b */
178	.phy_id_mask	= 0xfffffff0,
179	.name		= "SMSC LAN8187",
180
181	.features	= (PHY_BASIC_FEATURES | SUPPORTED_Pause
182				| SUPPORTED_Asym_Pause),
183	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
184
185	/* basic functions */
186	.config_aneg	= genphy_config_aneg,
187	.read_status	= genphy_read_status,
188	.config_init	= smsc_phy_config_init,
189	.soft_reset	= smsc_phy_reset,
190
191	/* IRQ related */
192	.ack_interrupt	= smsc_phy_ack_interrupt,
193	.config_intr	= smsc_phy_config_intr,
194
195	.suspend	= genphy_suspend,
196	.resume		= genphy_resume,
197
198	.driver		= { .owner = THIS_MODULE, }
199}, {
200	.phy_id		= 0x0007c0c0, /* OUI=0x00800f, Model#=0x0c */
201	.phy_id_mask	= 0xfffffff0,
202	.name		= "SMSC LAN8700",
203
204	.features	= (PHY_BASIC_FEATURES | SUPPORTED_Pause
205				| SUPPORTED_Asym_Pause),
206	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
207
208	/* basic functions */
209	.config_aneg	= genphy_config_aneg,
210	.read_status	= lan87xx_read_status,
211	.config_init	= smsc_phy_config_init,
212	.soft_reset	= smsc_phy_reset,
213
214	/* IRQ related */
215	.ack_interrupt	= smsc_phy_ack_interrupt,
216	.config_intr	= smsc_phy_config_intr,
217
218	.suspend	= genphy_suspend,
219	.resume		= genphy_resume,
220
221	.driver		= { .owner = THIS_MODULE, }
222}, {
223	.phy_id		= 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */
224	.phy_id_mask	= 0xfffffff0,
225	.name		= "SMSC LAN911x Internal PHY",
226
227	.features	= (PHY_BASIC_FEATURES | SUPPORTED_Pause
228				| SUPPORTED_Asym_Pause),
229	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
230
231	/* basic functions */
232	.config_aneg	= genphy_config_aneg,
233	.read_status	= genphy_read_status,
234	.config_init	= lan911x_config_init,
235
236	/* IRQ related */
237	.ack_interrupt	= smsc_phy_ack_interrupt,
238	.config_intr	= smsc_phy_config_intr,
239
240	.suspend	= genphy_suspend,
241	.resume		= genphy_resume,
242
243	.driver		= { .owner = THIS_MODULE, }
244}, {
245	.phy_id		= 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */
246	.phy_id_mask	= 0xfffffff0,
247	.name		= "SMSC LAN8710/LAN8720",
248
249	.features	= (PHY_BASIC_FEATURES | SUPPORTED_Pause
250				| SUPPORTED_Asym_Pause),
251	.flags		= PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
252
253	/* basic functions */
254	.config_aneg	= genphy_config_aneg,
255	.read_status	= lan87xx_read_status,
256	.config_init	= smsc_phy_config_init,
257	.soft_reset	= smsc_phy_reset,
258
259	/* IRQ related */
260	.ack_interrupt	= smsc_phy_ack_interrupt,
261	.config_intr	= smsc_phy_config_intr,
262
263	.suspend	= genphy_suspend,
264	.resume		= genphy_resume,
265
266	.driver		= { .owner = THIS_MODULE, }
267} };
268
269module_phy_driver(smsc_phy_driver);
270
271MODULE_DESCRIPTION("SMSC PHY driver");
272MODULE_AUTHOR("Herbert Valerio Riedel");
273MODULE_LICENSE("GPL");
274
275static struct mdio_device_id __maybe_unused smsc_tbl[] = {
276	{ 0x0007c0a0, 0xfffffff0 },
277	{ 0x0007c0b0, 0xfffffff0 },
278	{ 0x0007c0c0, 0xfffffff0 },
279	{ 0x0007c0d0, 0xfffffff0 },
280	{ 0x0007c0f0, 0xfffffff0 },
281	{ }
282};
283
284MODULE_DEVICE_TABLE(mdio, smsc_tbl);
285