xref: /openbmc/linux/drivers/net/phy/lxt.c (revision 1953feb0)
1a2443fd1SAndrew Lunn // SPDX-License-Identifier: GPL-2.0+
200db8189SAndy Fleming /*
300db8189SAndy Fleming  * drivers/net/phy/lxt.c
400db8189SAndy Fleming  *
500db8189SAndy Fleming  * Driver for Intel LXT PHYs
600db8189SAndy Fleming  *
700db8189SAndy Fleming  * Author: Andy Fleming
800db8189SAndy Fleming  *
900db8189SAndy Fleming  * Copyright (c) 2004 Freescale Semiconductor, Inc.
1000db8189SAndy Fleming  */
1100db8189SAndy Fleming #include <linux/kernel.h>
1200db8189SAndy Fleming #include <linux/string.h>
1300db8189SAndy Fleming #include <linux/errno.h>
1400db8189SAndy Fleming #include <linux/unistd.h>
1500db8189SAndy Fleming #include <linux/interrupt.h>
1600db8189SAndy Fleming #include <linux/init.h>
1700db8189SAndy Fleming #include <linux/delay.h>
1800db8189SAndy Fleming #include <linux/netdevice.h>
1900db8189SAndy Fleming #include <linux/etherdevice.h>
2000db8189SAndy Fleming #include <linux/skbuff.h>
2100db8189SAndy Fleming #include <linux/spinlock.h>
2200db8189SAndy Fleming #include <linux/mm.h>
2300db8189SAndy Fleming #include <linux/module.h>
2400db8189SAndy Fleming #include <linux/mii.h>
2500db8189SAndy Fleming #include <linux/ethtool.h>
2600db8189SAndy Fleming #include <linux/phy.h>
2700db8189SAndy Fleming 
2800db8189SAndy Fleming #include <asm/io.h>
2900db8189SAndy Fleming #include <asm/irq.h>
307c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
3100db8189SAndy Fleming 
3200db8189SAndy Fleming /* The Level one LXT970 is used by many boards				     */
3300db8189SAndy Fleming 
3400db8189SAndy Fleming #define MII_LXT970_IER       17  /* Interrupt Enable Register */
3500db8189SAndy Fleming 
3600db8189SAndy Fleming #define MII_LXT970_IER_IEN	0x0002
3700db8189SAndy Fleming 
3800db8189SAndy Fleming #define MII_LXT970_ISR       18  /* Interrupt Status Register */
3900db8189SAndy Fleming 
4001c4a00bSIoana Ciornei #define MII_LXT970_IRS_MINT  BIT(15)
4101c4a00bSIoana Ciornei 
4200db8189SAndy Fleming #define MII_LXT970_CONFIG    19  /* Configuration Register    */
4300db8189SAndy Fleming 
4400db8189SAndy Fleming /* ------------------------------------------------------------------------- */
4500db8189SAndy Fleming /* The Level one LXT971 is used on some of my custom boards                  */
4600db8189SAndy Fleming 
4700db8189SAndy Fleming /* register definitions for the 971 */
4800db8189SAndy Fleming #define MII_LXT971_IER		18  /* Interrupt Enable Register */
4900db8189SAndy Fleming #define MII_LXT971_IER_IEN	0x00f2
5000db8189SAndy Fleming 
5100db8189SAndy Fleming #define MII_LXT971_ISR		19  /* Interrupt Status Register */
5201c4a00bSIoana Ciornei #define MII_LXT971_ISR_MASK	0x00f0
5300db8189SAndy Fleming 
54e13647c1SRichard Cochran /* register definitions for the 973 */
55e13647c1SRichard Cochran #define MII_LXT973_PCR 16 /* Port Configuration Register */
56e13647c1SRichard Cochran #define PCR_FIBER_SELECT 1
5700db8189SAndy Fleming 
5800db8189SAndy Fleming MODULE_DESCRIPTION("Intel LXT PHY driver");
5900db8189SAndy Fleming MODULE_AUTHOR("Andy Fleming");
6000db8189SAndy Fleming MODULE_LICENSE("GPL");
6100db8189SAndy Fleming 
lxt970_ack_interrupt(struct phy_device * phydev)6200db8189SAndy Fleming static int lxt970_ack_interrupt(struct phy_device *phydev)
6300db8189SAndy Fleming {
6400db8189SAndy Fleming 	int err;
6500db8189SAndy Fleming 
6600db8189SAndy Fleming 	err = phy_read(phydev, MII_BMSR);
6700db8189SAndy Fleming 
6800db8189SAndy Fleming 	if (err < 0)
6900db8189SAndy Fleming 		return err;
7000db8189SAndy Fleming 
7100db8189SAndy Fleming 	err = phy_read(phydev, MII_LXT970_ISR);
7200db8189SAndy Fleming 
7300db8189SAndy Fleming 	if (err < 0)
7400db8189SAndy Fleming 		return err;
7500db8189SAndy Fleming 
7600db8189SAndy Fleming 	return 0;
7700db8189SAndy Fleming }
7800db8189SAndy Fleming 
lxt970_config_intr(struct phy_device * phydev)7900db8189SAndy Fleming static int lxt970_config_intr(struct phy_device *phydev)
8000db8189SAndy Fleming {
819a12dd6fSIoana Ciornei 	int err;
829a12dd6fSIoana Ciornei 
839a12dd6fSIoana Ciornei 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
849a12dd6fSIoana Ciornei 		err = lxt970_ack_interrupt(phydev);
859a12dd6fSIoana Ciornei 		if (err)
869a12dd6fSIoana Ciornei 			return err;
879a12dd6fSIoana Ciornei 
889a12dd6fSIoana Ciornei 		err = phy_write(phydev, MII_LXT970_IER, MII_LXT970_IER_IEN);
899a12dd6fSIoana Ciornei 	} else {
909a12dd6fSIoana Ciornei 		err = phy_write(phydev, MII_LXT970_IER, 0);
919a12dd6fSIoana Ciornei 		if (err)
929a12dd6fSIoana Ciornei 			return err;
939a12dd6fSIoana Ciornei 
949a12dd6fSIoana Ciornei 		err = lxt970_ack_interrupt(phydev);
959a12dd6fSIoana Ciornei 	}
969a12dd6fSIoana Ciornei 
979a12dd6fSIoana Ciornei 	return err;
9800db8189SAndy Fleming }
9900db8189SAndy Fleming 
lxt970_handle_interrupt(struct phy_device * phydev)10001c4a00bSIoana Ciornei static irqreturn_t lxt970_handle_interrupt(struct phy_device *phydev)
10101c4a00bSIoana Ciornei {
10201c4a00bSIoana Ciornei 	int irq_status;
10301c4a00bSIoana Ciornei 
10401c4a00bSIoana Ciornei 	/* The interrupt status register is cleared by reading BMSR
10501c4a00bSIoana Ciornei 	 * followed by MII_LXT970_ISR
10601c4a00bSIoana Ciornei 	 */
10701c4a00bSIoana Ciornei 	irq_status = phy_read(phydev, MII_BMSR);
10801c4a00bSIoana Ciornei 	if (irq_status < 0) {
10901c4a00bSIoana Ciornei 		phy_error(phydev);
11001c4a00bSIoana Ciornei 		return IRQ_NONE;
11101c4a00bSIoana Ciornei 	}
11201c4a00bSIoana Ciornei 
11301c4a00bSIoana Ciornei 	irq_status = phy_read(phydev, MII_LXT970_ISR);
11401c4a00bSIoana Ciornei 	if (irq_status < 0) {
11501c4a00bSIoana Ciornei 		phy_error(phydev);
11601c4a00bSIoana Ciornei 		return IRQ_NONE;
11701c4a00bSIoana Ciornei 	}
11801c4a00bSIoana Ciornei 
11901c4a00bSIoana Ciornei 	if (!(irq_status & MII_LXT970_IRS_MINT))
12001c4a00bSIoana Ciornei 		return IRQ_NONE;
12101c4a00bSIoana Ciornei 
12201c4a00bSIoana Ciornei 	phy_trigger_machine(phydev);
12301c4a00bSIoana Ciornei 
12401c4a00bSIoana Ciornei 	return IRQ_HANDLED;
12501c4a00bSIoana Ciornei }
12601c4a00bSIoana Ciornei 
lxt970_config_init(struct phy_device * phydev)12700db8189SAndy Fleming static int lxt970_config_init(struct phy_device *phydev)
12800db8189SAndy Fleming {
129c8396d84SSergei Shtylyov 	return phy_write(phydev, MII_LXT970_CONFIG, 0);
13000db8189SAndy Fleming }
13100db8189SAndy Fleming 
13200db8189SAndy Fleming 
lxt971_ack_interrupt(struct phy_device * phydev)13300db8189SAndy Fleming static int lxt971_ack_interrupt(struct phy_device *phydev)
13400db8189SAndy Fleming {
13500db8189SAndy Fleming 	int err = phy_read(phydev, MII_LXT971_ISR);
13600db8189SAndy Fleming 
13700db8189SAndy Fleming 	if (err < 0)
13800db8189SAndy Fleming 		return err;
13900db8189SAndy Fleming 
14000db8189SAndy Fleming 	return 0;
14100db8189SAndy Fleming }
14200db8189SAndy Fleming 
lxt971_config_intr(struct phy_device * phydev)14300db8189SAndy Fleming static int lxt971_config_intr(struct phy_device *phydev)
14400db8189SAndy Fleming {
1459a12dd6fSIoana Ciornei 	int err;
1469a12dd6fSIoana Ciornei 
1479a12dd6fSIoana Ciornei 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
1489a12dd6fSIoana Ciornei 		err = lxt971_ack_interrupt(phydev);
1499a12dd6fSIoana Ciornei 		if (err)
1509a12dd6fSIoana Ciornei 			return err;
1519a12dd6fSIoana Ciornei 
1529a12dd6fSIoana Ciornei 		err = phy_write(phydev, MII_LXT971_IER, MII_LXT971_IER_IEN);
1539a12dd6fSIoana Ciornei 	} else {
1549a12dd6fSIoana Ciornei 		err = phy_write(phydev, MII_LXT971_IER, 0);
1559a12dd6fSIoana Ciornei 		if (err)
1569a12dd6fSIoana Ciornei 			return err;
1579a12dd6fSIoana Ciornei 
1589a12dd6fSIoana Ciornei 		err = lxt971_ack_interrupt(phydev);
1599a12dd6fSIoana Ciornei 	}
1609a12dd6fSIoana Ciornei 
1619a12dd6fSIoana Ciornei 	return err;
16200db8189SAndy Fleming }
16300db8189SAndy Fleming 
lxt971_handle_interrupt(struct phy_device * phydev)16401c4a00bSIoana Ciornei static irqreturn_t lxt971_handle_interrupt(struct phy_device *phydev)
16501c4a00bSIoana Ciornei {
16601c4a00bSIoana Ciornei 	int irq_status;
16701c4a00bSIoana Ciornei 
16801c4a00bSIoana Ciornei 	irq_status = phy_read(phydev, MII_LXT971_ISR);
16901c4a00bSIoana Ciornei 	if (irq_status < 0) {
17001c4a00bSIoana Ciornei 		phy_error(phydev);
17101c4a00bSIoana Ciornei 		return IRQ_NONE;
17201c4a00bSIoana Ciornei 	}
17301c4a00bSIoana Ciornei 
17401c4a00bSIoana Ciornei 	if (!(irq_status & MII_LXT971_ISR_MASK))
17501c4a00bSIoana Ciornei 		return IRQ_NONE;
17601c4a00bSIoana Ciornei 
17701c4a00bSIoana Ciornei 	phy_trigger_machine(phydev);
17801c4a00bSIoana Ciornei 
17901c4a00bSIoana Ciornei 	return IRQ_HANDLED;
18001c4a00bSIoana Ciornei }
18101c4a00bSIoana Ciornei 
182871d1d6bSLEROY Christophe /*
183871d1d6bSLEROY Christophe  * A2 version of LXT973 chip has an ERRATA: it randomly return the contents
184871d1d6bSLEROY Christophe  * of the previous even register when you read a odd register regularly
185871d1d6bSLEROY Christophe  */
186871d1d6bSLEROY Christophe 
lxt973a2_update_link(struct phy_device * phydev)187871d1d6bSLEROY Christophe static int lxt973a2_update_link(struct phy_device *phydev)
188871d1d6bSLEROY Christophe {
189871d1d6bSLEROY Christophe 	int status;
190871d1d6bSLEROY Christophe 	int control;
191871d1d6bSLEROY Christophe 	int retry = 8; /* we try 8 times */
192871d1d6bSLEROY Christophe 
193871d1d6bSLEROY Christophe 	/* Do a fake read */
194871d1d6bSLEROY Christophe 	status = phy_read(phydev, MII_BMSR);
195871d1d6bSLEROY Christophe 
196871d1d6bSLEROY Christophe 	if (status < 0)
197871d1d6bSLEROY Christophe 		return status;
198871d1d6bSLEROY Christophe 
199871d1d6bSLEROY Christophe 	control = phy_read(phydev, MII_BMCR);
200871d1d6bSLEROY Christophe 	if (control < 0)
201871d1d6bSLEROY Christophe 		return control;
202871d1d6bSLEROY Christophe 
203871d1d6bSLEROY Christophe 	do {
204871d1d6bSLEROY Christophe 		/* Read link and autonegotiation status */
205871d1d6bSLEROY Christophe 		status = phy_read(phydev, MII_BMSR);
206871d1d6bSLEROY Christophe 	} while (status >= 0 && retry-- && status == control);
207871d1d6bSLEROY Christophe 
208871d1d6bSLEROY Christophe 	if (status < 0)
209871d1d6bSLEROY Christophe 		return status;
210871d1d6bSLEROY Christophe 
211871d1d6bSLEROY Christophe 	if ((status & BMSR_LSTATUS) == 0)
212871d1d6bSLEROY Christophe 		phydev->link = 0;
213871d1d6bSLEROY Christophe 	else
214871d1d6bSLEROY Christophe 		phydev->link = 1;
215871d1d6bSLEROY Christophe 
216871d1d6bSLEROY Christophe 	return 0;
217871d1d6bSLEROY Christophe }
218871d1d6bSLEROY Christophe 
lxt973a2_read_status(struct phy_device * phydev)21919945333Sstephen hemminger static int lxt973a2_read_status(struct phy_device *phydev)
220871d1d6bSLEROY Christophe {
221871d1d6bSLEROY Christophe 	int adv;
222871d1d6bSLEROY Christophe 	int err;
223871d1d6bSLEROY Christophe 	int lpa;
224871d1d6bSLEROY Christophe 
225871d1d6bSLEROY Christophe 	/* Update the link, but return if there was an error */
226871d1d6bSLEROY Christophe 	err = lxt973a2_update_link(phydev);
227871d1d6bSLEROY Christophe 	if (err)
228871d1d6bSLEROY Christophe 		return err;
229871d1d6bSLEROY Christophe 
230871d1d6bSLEROY Christophe 	if (AUTONEG_ENABLE == phydev->autoneg) {
231871d1d6bSLEROY Christophe 		int retry = 1;
232871d1d6bSLEROY Christophe 
233871d1d6bSLEROY Christophe 		adv = phy_read(phydev, MII_ADVERTISE);
234871d1d6bSLEROY Christophe 
235871d1d6bSLEROY Christophe 		if (adv < 0)
236871d1d6bSLEROY Christophe 			return adv;
237871d1d6bSLEROY Christophe 
238871d1d6bSLEROY Christophe 		do {
239871d1d6bSLEROY Christophe 			lpa = phy_read(phydev, MII_LPA);
240871d1d6bSLEROY Christophe 
241871d1d6bSLEROY Christophe 			if (lpa < 0)
242871d1d6bSLEROY Christophe 				return lpa;
243871d1d6bSLEROY Christophe 
244871d1d6bSLEROY Christophe 			/* If both registers are equal, it is suspect but not
245871d1d6bSLEROY Christophe 			 * impossible, hence a new try
246871d1d6bSLEROY Christophe 			 */
247871d1d6bSLEROY Christophe 		} while (lpa == adv && retry--);
248871d1d6bSLEROY Christophe 
249c0ec3c27SAndrew Lunn 		mii_lpa_to_linkmode_lpa_t(phydev->lp_advertising, lpa);
250ca83697cSThomas Bogendoerfer 
251871d1d6bSLEROY Christophe 		lpa &= adv;
252871d1d6bSLEROY Christophe 
253871d1d6bSLEROY Christophe 		phydev->speed = SPEED_10;
254871d1d6bSLEROY Christophe 		phydev->duplex = DUPLEX_HALF;
255871d1d6bSLEROY Christophe 		phydev->pause = phydev->asym_pause = 0;
256871d1d6bSLEROY Christophe 
257ca83697cSThomas Bogendoerfer 		if (lpa & (LPA_100FULL | LPA_100HALF)) {
258871d1d6bSLEROY Christophe 			phydev->speed = SPEED_100;
259871d1d6bSLEROY Christophe 
260871d1d6bSLEROY Christophe 			if (lpa & LPA_100FULL)
261871d1d6bSLEROY Christophe 				phydev->duplex = DUPLEX_FULL;
262871d1d6bSLEROY Christophe 		} else {
263871d1d6bSLEROY Christophe 			if (lpa & LPA_10FULL)
264871d1d6bSLEROY Christophe 				phydev->duplex = DUPLEX_FULL;
265871d1d6bSLEROY Christophe 		}
266871d1d6bSLEROY Christophe 
267af006240SRussell King 		phy_resolve_aneg_pause(phydev);
268871d1d6bSLEROY Christophe 	} else {
2690efc286aSRussell King 		err = genphy_read_status_fixed(phydev);
2700efc286aSRussell King 		if (err < 0)
2710efc286aSRussell King 			return err;
272871d1d6bSLEROY Christophe 
273871d1d6bSLEROY Christophe 		phydev->pause = phydev->asym_pause = 0;
274c0ec3c27SAndrew Lunn 		linkmode_zero(phydev->lp_advertising);
275871d1d6bSLEROY Christophe 	}
276871d1d6bSLEROY Christophe 
277871d1d6bSLEROY Christophe 	return 0;
278871d1d6bSLEROY Christophe }
279871d1d6bSLEROY Christophe 
lxt973_probe(struct phy_device * phydev)280e13647c1SRichard Cochran static int lxt973_probe(struct phy_device *phydev)
281e13647c1SRichard Cochran {
282e13647c1SRichard Cochran 	int val = phy_read(phydev, MII_LXT973_PCR);
283e13647c1SRichard Cochran 
284e13647c1SRichard Cochran 	if (val & PCR_FIBER_SELECT) {
285e13647c1SRichard Cochran 		/*
286e13647c1SRichard Cochran 		 * If fiber is selected, then the only correct setting
287e13647c1SRichard Cochran 		 * is 100Mbps, full duplex, and auto negotiation off.
288e13647c1SRichard Cochran 		 */
289e13647c1SRichard Cochran 		val = phy_read(phydev, MII_BMCR);
290e13647c1SRichard Cochran 		val |= (BMCR_SPEED100 | BMCR_FULLDPLX);
291e13647c1SRichard Cochran 		val &= ~BMCR_ANENABLE;
292e13647c1SRichard Cochran 		phy_write(phydev, MII_BMCR, val);
293e13647c1SRichard Cochran 		/* Remember that the port is in fiber mode. */
294e13647c1SRichard Cochran 		phydev->priv = lxt973_probe;
295*4217a64eSMichael Walle 		phydev->port = PORT_FIBRE;
296e13647c1SRichard Cochran 	} else {
297e13647c1SRichard Cochran 		phydev->priv = NULL;
298e13647c1SRichard Cochran 	}
299e13647c1SRichard Cochran 	return 0;
300e13647c1SRichard Cochran }
301e13647c1SRichard Cochran 
lxt973_config_aneg(struct phy_device * phydev)302e13647c1SRichard Cochran static int lxt973_config_aneg(struct phy_device *phydev)
303e13647c1SRichard Cochran {
304e13647c1SRichard Cochran 	/* Do nothing if port is in fiber mode. */
305e13647c1SRichard Cochran 	return phydev->priv ? 0 : genphy_config_aneg(phydev);
306e13647c1SRichard Cochran }
307e13647c1SRichard Cochran 
308d5bf9071SChristian Hohnstaedt static struct phy_driver lxt97x_driver[] = {
309d5bf9071SChristian Hohnstaedt {
310600991b0SUwe Zeisberger 	.phy_id		= 0x78100000,
31100db8189SAndy Fleming 	.name		= "LXT970",
312600991b0SUwe Zeisberger 	.phy_id_mask	= 0xfffffff0,
313dcdecdcfSHeiner Kallweit 	/* PHY_BASIC_FEATURES */
31400db8189SAndy Fleming 	.config_init	= lxt970_config_init,
31500db8189SAndy Fleming 	.config_intr	= lxt970_config_intr,
31601c4a00bSIoana Ciornei 	.handle_interrupt = lxt970_handle_interrupt,
317d5bf9071SChristian Hohnstaedt }, {
318600991b0SUwe Zeisberger 	.phy_id		= 0x001378e0,
31900db8189SAndy Fleming 	.name		= "LXT971",
320600991b0SUwe Zeisberger 	.phy_id_mask	= 0xfffffff0,
321dcdecdcfSHeiner Kallweit 	/* PHY_BASIC_FEATURES */
32200db8189SAndy Fleming 	.config_intr	= lxt971_config_intr,
32301c4a00bSIoana Ciornei 	.handle_interrupt = lxt971_handle_interrupt,
3245556fdb0SChristophe Leroy 	.suspend	= genphy_suspend,
3255556fdb0SChristophe Leroy 	.resume		= genphy_resume,
326d5bf9071SChristian Hohnstaedt }, {
327e13647c1SRichard Cochran 	.phy_id		= 0x00137a10,
328871d1d6bSLEROY Christophe 	.name		= "LXT973-A2",
329871d1d6bSLEROY Christophe 	.phy_id_mask	= 0xffffffff,
330dcdecdcfSHeiner Kallweit 	/* PHY_BASIC_FEATURES */
331871d1d6bSLEROY Christophe 	.flags		= 0,
332871d1d6bSLEROY Christophe 	.probe		= lxt973_probe,
333871d1d6bSLEROY Christophe 	.config_aneg	= lxt973_config_aneg,
334871d1d6bSLEROY Christophe 	.read_status	= lxt973a2_read_status,
3355556fdb0SChristophe Leroy 	.suspend	= genphy_suspend,
3365556fdb0SChristophe Leroy 	.resume		= genphy_resume,
337871d1d6bSLEROY Christophe }, {
338871d1d6bSLEROY Christophe 	.phy_id		= 0x00137a10,
339e13647c1SRichard Cochran 	.name		= "LXT973",
340e13647c1SRichard Cochran 	.phy_id_mask	= 0xfffffff0,
341dcdecdcfSHeiner Kallweit 	/* PHY_BASIC_FEATURES */
342e13647c1SRichard Cochran 	.flags		= 0,
343e13647c1SRichard Cochran 	.probe		= lxt973_probe,
344e13647c1SRichard Cochran 	.config_aneg	= lxt973_config_aneg,
3455556fdb0SChristophe Leroy 	.suspend	= genphy_suspend,
3465556fdb0SChristophe Leroy 	.resume		= genphy_resume,
347d5bf9071SChristian Hohnstaedt } };
348e13647c1SRichard Cochran 
34950fd7150SJohan Hovold module_phy_driver(lxt97x_driver);
3504e4f10f6SDavid Woodhouse 
351cf93c945SUwe Kleine-König static struct mdio_device_id __maybe_unused lxt_tbl[] = {
3524e4f10f6SDavid Woodhouse 	{ 0x78100000, 0xfffffff0 },
3534e4f10f6SDavid Woodhouse 	{ 0x001378e0, 0xfffffff0 },
354e2f5b045SDavid Woodhouse 	{ 0x00137a10, 0xfffffff0 },
3554e4f10f6SDavid Woodhouse 	{ }
3564e4f10f6SDavid Woodhouse };
3574e4f10f6SDavid Woodhouse 
3584e4f10f6SDavid Woodhouse MODULE_DEVICE_TABLE(mdio, lxt_tbl);
359