xref: /openbmc/linux/drivers/net/phy/icplus.c (revision 76231e0297db30f1f0e947a02b42495e7d535d56)
10cefeebaSMichael Barkowski /*
20cefeebaSMichael Barkowski  * Driver for ICPlus PHYs
30cefeebaSMichael Barkowski  *
40cefeebaSMichael Barkowski  * Copyright (c) 2007 Freescale Semiconductor, Inc.
50cefeebaSMichael Barkowski  *
60cefeebaSMichael Barkowski  * This program is free software; you can redistribute  it and/or modify it
70cefeebaSMichael Barkowski  * under  the terms of  the GNU General  Public License as published by the
80cefeebaSMichael Barkowski  * Free Software Foundation;  either version 2 of the  License, or (at your
90cefeebaSMichael Barkowski  * option) any later version.
100cefeebaSMichael Barkowski  *
110cefeebaSMichael Barkowski  */
120cefeebaSMichael Barkowski #include <linux/kernel.h>
130cefeebaSMichael Barkowski #include <linux/string.h>
140cefeebaSMichael Barkowski #include <linux/errno.h>
150cefeebaSMichael Barkowski #include <linux/unistd.h>
160cefeebaSMichael Barkowski #include <linux/interrupt.h>
170cefeebaSMichael Barkowski #include <linux/init.h>
180cefeebaSMichael Barkowski #include <linux/delay.h>
190cefeebaSMichael Barkowski #include <linux/netdevice.h>
200cefeebaSMichael Barkowski #include <linux/etherdevice.h>
210cefeebaSMichael Barkowski #include <linux/skbuff.h>
220cefeebaSMichael Barkowski #include <linux/spinlock.h>
230cefeebaSMichael Barkowski #include <linux/mm.h>
240cefeebaSMichael Barkowski #include <linux/module.h>
250cefeebaSMichael Barkowski #include <linux/mii.h>
260cefeebaSMichael Barkowski #include <linux/ethtool.h>
270cefeebaSMichael Barkowski #include <linux/phy.h>
280cefeebaSMichael Barkowski 
290cefeebaSMichael Barkowski #include <asm/io.h>
300cefeebaSMichael Barkowski #include <asm/irq.h>
310cefeebaSMichael Barkowski #include <asm/uaccess.h>
320cefeebaSMichael Barkowski 
339c9b1f24SGiuseppe CAVALLARO MODULE_DESCRIPTION("ICPlus IP175C/IP101A/IC1001 PHY drivers");
340cefeebaSMichael Barkowski MODULE_AUTHOR("Michael Barkowski");
350cefeebaSMichael Barkowski MODULE_LICENSE("GPL");
360cefeebaSMichael Barkowski 
379c9b1f24SGiuseppe CAVALLARO /* IP101A/IP1001 */
389c9b1f24SGiuseppe CAVALLARO #define IP10XX_SPEC_CTRL_STATUS		16  /* Spec. Control Register */
399c9b1f24SGiuseppe CAVALLARO #define IP1001_SPEC_CTRL_STATUS_2	20  /* IP1001 Spec. Control Reg 2 */
409c9b1f24SGiuseppe CAVALLARO #define IP1001_PHASE_SEL_MASK		3 /* IP1001 RX/TXPHASE_SEL */
419c9b1f24SGiuseppe CAVALLARO #define IP1001_APS_ON			11  /* IP1001 APS Mode  bit */
429c9b1f24SGiuseppe CAVALLARO #define IP101A_APS_ON			2   /* IP101A APS Mode bit */
439c9b1f24SGiuseppe CAVALLARO 
440cefeebaSMichael Barkowski static int ip175c_config_init(struct phy_device *phydev)
450cefeebaSMichael Barkowski {
460cefeebaSMichael Barkowski 	int err, i;
470cefeebaSMichael Barkowski 	static int full_reset_performed = 0;
480cefeebaSMichael Barkowski 
490cefeebaSMichael Barkowski 	if (full_reset_performed == 0) {
500cefeebaSMichael Barkowski 
510cefeebaSMichael Barkowski 		/* master reset */
52*76231e02SDavid Daney 		err = mdiobus_write(phydev->bus, 30, 0, 0x175c);
530cefeebaSMichael Barkowski 		if (err < 0)
540cefeebaSMichael Barkowski 			return err;
550cefeebaSMichael Barkowski 
560cefeebaSMichael Barkowski 		/* ensure no bus delays overlap reset period */
57*76231e02SDavid Daney 		err = mdiobus_read(phydev->bus, 30, 0);
580cefeebaSMichael Barkowski 
590cefeebaSMichael Barkowski 		/* data sheet specifies reset period is 2 msec */
600cefeebaSMichael Barkowski 		mdelay(2);
610cefeebaSMichael Barkowski 
620cefeebaSMichael Barkowski 		/* enable IP175C mode */
63*76231e02SDavid Daney 		err = mdiobus_write(phydev->bus, 29, 31, 0x175c);
640cefeebaSMichael Barkowski 		if (err < 0)
650cefeebaSMichael Barkowski 			return err;
660cefeebaSMichael Barkowski 
670cefeebaSMichael Barkowski 		/* Set MII0 speed and duplex (in PHY mode) */
68*76231e02SDavid Daney 		err = mdiobus_write(phydev->bus, 29, 22, 0x420);
690cefeebaSMichael Barkowski 		if (err < 0)
700cefeebaSMichael Barkowski 			return err;
710cefeebaSMichael Barkowski 
720cefeebaSMichael Barkowski 		/* reset switch ports */
730cefeebaSMichael Barkowski 		for (i = 0; i < 5; i++) {
74*76231e02SDavid Daney 			err = mdiobus_write(phydev->bus, i,
750cefeebaSMichael Barkowski 					    MII_BMCR, BMCR_RESET);
760cefeebaSMichael Barkowski 			if (err < 0)
770cefeebaSMichael Barkowski 				return err;
780cefeebaSMichael Barkowski 		}
790cefeebaSMichael Barkowski 
800cefeebaSMichael Barkowski 		for (i = 0; i < 5; i++)
81*76231e02SDavid Daney 			err = mdiobus_read(phydev->bus, i, MII_BMCR);
820cefeebaSMichael Barkowski 
830cefeebaSMichael Barkowski 		mdelay(2);
840cefeebaSMichael Barkowski 
850cefeebaSMichael Barkowski 		full_reset_performed = 1;
860cefeebaSMichael Barkowski 	}
870cefeebaSMichael Barkowski 
880cefeebaSMichael Barkowski 	if (phydev->addr != 4) {
890cefeebaSMichael Barkowski 		phydev->state = PHY_RUNNING;
900cefeebaSMichael Barkowski 		phydev->speed = SPEED_100;
910cefeebaSMichael Barkowski 		phydev->duplex = DUPLEX_FULL;
920cefeebaSMichael Barkowski 		phydev->link = 1;
930cefeebaSMichael Barkowski 		netif_carrier_on(phydev->attached_dev);
940cefeebaSMichael Barkowski 	}
950cefeebaSMichael Barkowski 
960cefeebaSMichael Barkowski 	return 0;
970cefeebaSMichael Barkowski }
980cefeebaSMichael Barkowski 
999c9b1f24SGiuseppe CAVALLARO static int ip1xx_reset(struct phy_device *phydev)
100377ecca9SGiuseppe CAVALLARO {
1019c9b1f24SGiuseppe CAVALLARO 	int err, bmcr;
102377ecca9SGiuseppe CAVALLARO 
103377ecca9SGiuseppe CAVALLARO 	/* Software Reset PHY */
1049c9b1f24SGiuseppe CAVALLARO 	bmcr = phy_read(phydev, MII_BMCR);
1059c9b1f24SGiuseppe CAVALLARO 	bmcr |= BMCR_RESET;
1069c9b1f24SGiuseppe CAVALLARO 	err = phy_write(phydev, MII_BMCR, bmcr);
107377ecca9SGiuseppe CAVALLARO 	if (err < 0)
108377ecca9SGiuseppe CAVALLARO 		return err;
109377ecca9SGiuseppe CAVALLARO 
110377ecca9SGiuseppe CAVALLARO 	do {
1119c9b1f24SGiuseppe CAVALLARO 		bmcr = phy_read(phydev, MII_BMCR);
1129c9b1f24SGiuseppe CAVALLARO 	} while (bmcr & BMCR_RESET);
1139c9b1f24SGiuseppe CAVALLARO 
1149c9b1f24SGiuseppe CAVALLARO 	return err;
1159c9b1f24SGiuseppe CAVALLARO }
1169c9b1f24SGiuseppe CAVALLARO 
1179c9b1f24SGiuseppe CAVALLARO static int ip1001_config_init(struct phy_device *phydev)
1189c9b1f24SGiuseppe CAVALLARO {
1199c9b1f24SGiuseppe CAVALLARO 	int c;
1209c9b1f24SGiuseppe CAVALLARO 
1219c9b1f24SGiuseppe CAVALLARO 	c = ip1xx_reset(phydev);
1229c9b1f24SGiuseppe CAVALLARO 	if (c < 0)
1239c9b1f24SGiuseppe CAVALLARO 		return c;
1249c9b1f24SGiuseppe CAVALLARO 
1259c9b1f24SGiuseppe CAVALLARO 	/* Enable Auto Power Saving mode */
1269c9b1f24SGiuseppe CAVALLARO 	c = phy_read(phydev, IP1001_SPEC_CTRL_STATUS_2);
1279c9b1f24SGiuseppe CAVALLARO 	c |= IP1001_APS_ON;
1289c9b1f24SGiuseppe CAVALLARO 	if (c < 0)
1299c9b1f24SGiuseppe CAVALLARO 		return c;
130377ecca9SGiuseppe CAVALLARO 
131377ecca9SGiuseppe CAVALLARO 	/* Additional delay (2ns) used to adjust RX clock phase
132377ecca9SGiuseppe CAVALLARO 	 * at GMII/ RGMII interface */
1339c9b1f24SGiuseppe CAVALLARO 	c = phy_read(phydev, IP10XX_SPEC_CTRL_STATUS);
1349c9b1f24SGiuseppe CAVALLARO 	c |= IP1001_PHASE_SEL_MASK;
135377ecca9SGiuseppe CAVALLARO 
1369c9b1f24SGiuseppe CAVALLARO 	return phy_write(phydev, IP10XX_SPEC_CTRL_STATUS, c);
1379c9b1f24SGiuseppe CAVALLARO }
1389c9b1f24SGiuseppe CAVALLARO 
1399c9b1f24SGiuseppe CAVALLARO static int ip101a_config_init(struct phy_device *phydev)
1409c9b1f24SGiuseppe CAVALLARO {
1419c9b1f24SGiuseppe CAVALLARO 	int c;
1429c9b1f24SGiuseppe CAVALLARO 
1439c9b1f24SGiuseppe CAVALLARO 	c = ip1xx_reset(phydev);
1449c9b1f24SGiuseppe CAVALLARO 	if (c < 0)
1459c9b1f24SGiuseppe CAVALLARO 		return c;
1469c9b1f24SGiuseppe CAVALLARO 
1479c9b1f24SGiuseppe CAVALLARO 	/* Enable Auto Power Saving mode */
1489c9b1f24SGiuseppe CAVALLARO 	c = phy_read(phydev, IP10XX_SPEC_CTRL_STATUS);
1499c9b1f24SGiuseppe CAVALLARO 	c |= IP101A_APS_ON;
1509c9b1f24SGiuseppe CAVALLARO 	return c;
151377ecca9SGiuseppe CAVALLARO }
152377ecca9SGiuseppe CAVALLARO 
1530cefeebaSMichael Barkowski static int ip175c_read_status(struct phy_device *phydev)
1540cefeebaSMichael Barkowski {
1550cefeebaSMichael Barkowski 	if (phydev->addr == 4) /* WAN port */
1560cefeebaSMichael Barkowski 		genphy_read_status(phydev);
1570cefeebaSMichael Barkowski 	else
1580cefeebaSMichael Barkowski 		/* Don't need to read status for switch ports */
1590cefeebaSMichael Barkowski 		phydev->irq = PHY_IGNORE_INTERRUPT;
1600cefeebaSMichael Barkowski 
1610cefeebaSMichael Barkowski 	return 0;
1620cefeebaSMichael Barkowski }
1630cefeebaSMichael Barkowski 
1640cefeebaSMichael Barkowski static int ip175c_config_aneg(struct phy_device *phydev)
1650cefeebaSMichael Barkowski {
1660cefeebaSMichael Barkowski 	if (phydev->addr == 4) /* WAN port */
1670cefeebaSMichael Barkowski 		genphy_config_aneg(phydev);
1680cefeebaSMichael Barkowski 
1690cefeebaSMichael Barkowski 	return 0;
1700cefeebaSMichael Barkowski }
1710cefeebaSMichael Barkowski 
1720cefeebaSMichael Barkowski static struct phy_driver ip175c_driver = {
1730cefeebaSMichael Barkowski 	.phy_id		= 0x02430d80,
1740cefeebaSMichael Barkowski 	.name		= "ICPlus IP175C",
1750cefeebaSMichael Barkowski 	.phy_id_mask	= 0x0ffffff0,
1760cefeebaSMichael Barkowski 	.features	= PHY_BASIC_FEATURES,
1770cefeebaSMichael Barkowski 	.config_init	= &ip175c_config_init,
1780cefeebaSMichael Barkowski 	.config_aneg	= &ip175c_config_aneg,
1790cefeebaSMichael Barkowski 	.read_status	= &ip175c_read_status,
180dab10863SGiuseppe Cavallaro 	.suspend	= genphy_suspend,
181dab10863SGiuseppe Cavallaro 	.resume		= genphy_resume,
1820cefeebaSMichael Barkowski 	.driver		= { .owner = THIS_MODULE,},
1830cefeebaSMichael Barkowski };
1840cefeebaSMichael Barkowski 
185377ecca9SGiuseppe CAVALLARO static struct phy_driver ip1001_driver = {
186377ecca9SGiuseppe CAVALLARO 	.phy_id		= 0x02430d90,
187377ecca9SGiuseppe CAVALLARO 	.name		= "ICPlus IP1001",
188377ecca9SGiuseppe CAVALLARO 	.phy_id_mask	= 0x0ffffff0,
189377ecca9SGiuseppe CAVALLARO 	.features	= PHY_GBIT_FEATURES | SUPPORTED_Pause |
190377ecca9SGiuseppe CAVALLARO 			  SUPPORTED_Asym_Pause,
191377ecca9SGiuseppe CAVALLARO 	.config_init	= &ip1001_config_init,
192377ecca9SGiuseppe CAVALLARO 	.config_aneg	= &genphy_config_aneg,
193377ecca9SGiuseppe CAVALLARO 	.read_status	= &genphy_read_status,
194377ecca9SGiuseppe CAVALLARO 	.suspend	= genphy_suspend,
195377ecca9SGiuseppe CAVALLARO 	.resume		= genphy_resume,
196377ecca9SGiuseppe CAVALLARO 	.driver		= { .owner = THIS_MODULE,},
197377ecca9SGiuseppe CAVALLARO };
198377ecca9SGiuseppe CAVALLARO 
1999c9b1f24SGiuseppe CAVALLARO static struct phy_driver ip101a_driver = {
2009c9b1f24SGiuseppe CAVALLARO 	.phy_id		= 0x02430c54,
2019c9b1f24SGiuseppe CAVALLARO 	.name		= "ICPlus IP101A",
2029c9b1f24SGiuseppe CAVALLARO 	.phy_id_mask	= 0x0ffffff0,
2039c9b1f24SGiuseppe CAVALLARO 	.features	= PHY_BASIC_FEATURES | SUPPORTED_Pause |
2049c9b1f24SGiuseppe CAVALLARO 			  SUPPORTED_Asym_Pause,
2059c9b1f24SGiuseppe CAVALLARO 	.config_init	= &ip101a_config_init,
2069c9b1f24SGiuseppe CAVALLARO 	.config_aneg	= &genphy_config_aneg,
2079c9b1f24SGiuseppe CAVALLARO 	.read_status	= &genphy_read_status,
2089c9b1f24SGiuseppe CAVALLARO 	.suspend	= genphy_suspend,
2099c9b1f24SGiuseppe CAVALLARO 	.resume		= genphy_resume,
2109c9b1f24SGiuseppe CAVALLARO 	.driver		= { .owner = THIS_MODULE,},
2119c9b1f24SGiuseppe CAVALLARO };
2129c9b1f24SGiuseppe CAVALLARO 
213377ecca9SGiuseppe CAVALLARO static int __init icplus_init(void)
2140cefeebaSMichael Barkowski {
215377ecca9SGiuseppe CAVALLARO 	int ret = 0;
216377ecca9SGiuseppe CAVALLARO 
217377ecca9SGiuseppe CAVALLARO 	ret = phy_driver_register(&ip1001_driver);
218377ecca9SGiuseppe CAVALLARO 	if (ret < 0)
219377ecca9SGiuseppe CAVALLARO 		return -ENODEV;
220377ecca9SGiuseppe CAVALLARO 
2219c9b1f24SGiuseppe CAVALLARO 	ret = phy_driver_register(&ip101a_driver);
2229c9b1f24SGiuseppe CAVALLARO 	if (ret < 0)
2239c9b1f24SGiuseppe CAVALLARO 		return -ENODEV;
2249c9b1f24SGiuseppe CAVALLARO 
2250cefeebaSMichael Barkowski 	return phy_driver_register(&ip175c_driver);
2260cefeebaSMichael Barkowski }
2270cefeebaSMichael Barkowski 
228377ecca9SGiuseppe CAVALLARO static void __exit icplus_exit(void)
2290cefeebaSMichael Barkowski {
230377ecca9SGiuseppe CAVALLARO 	phy_driver_unregister(&ip1001_driver);
2319c9b1f24SGiuseppe CAVALLARO 	phy_driver_unregister(&ip101a_driver);
2320cefeebaSMichael Barkowski 	phy_driver_unregister(&ip175c_driver);
2330cefeebaSMichael Barkowski }
2340cefeebaSMichael Barkowski 
235377ecca9SGiuseppe CAVALLARO module_init(icplus_init);
236377ecca9SGiuseppe CAVALLARO module_exit(icplus_exit);
2374e4f10f6SDavid Woodhouse 
238cf93c945SUwe Kleine-König static struct mdio_device_id __maybe_unused icplus_tbl[] = {
2394e4f10f6SDavid Woodhouse 	{ 0x02430d80, 0x0ffffff0 },
240377ecca9SGiuseppe CAVALLARO 	{ 0x02430d90, 0x0ffffff0 },
2414e4f10f6SDavid Woodhouse 	{ }
2424e4f10f6SDavid Woodhouse };
2434e4f10f6SDavid Woodhouse 
2444e4f10f6SDavid Woodhouse MODULE_DEVICE_TABLE(mdio, icplus_tbl);
245