| /* | 
 |  * Driver for Teranetics PHY | 
 |  * | 
 |  * Author: Shaohui Xie <Shaohui.Xie@freescale.com> | 
 |  * | 
 |  * Copyright 2015 Freescale Semiconductor, Inc. | 
 |  * | 
 |  * This file is licensed under the terms of the GNU General Public License | 
 |  * version 2.  This program is licensed "as is" without any warranty of any | 
 |  * kind, whether express or implied. | 
 |  */ | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/mii.h> | 
 | #include <linux/ethtool.h> | 
 | #include <linux/mdio.h> | 
 | #include <linux/phy.h> | 
 |  | 
 | MODULE_DESCRIPTION("Teranetics PHY driver"); | 
 | MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>"); | 
 | MODULE_LICENSE("GPL v2"); | 
 |  | 
 | #define PHY_ID_TN2020	0x00a19410 | 
 | #define MDIO_PHYXS_LNSTAT_SYNC0	0x0001 | 
 | #define MDIO_PHYXS_LNSTAT_SYNC1	0x0002 | 
 | #define MDIO_PHYXS_LNSTAT_SYNC2	0x0004 | 
 | #define MDIO_PHYXS_LNSTAT_SYNC3	0x0008 | 
 | #define MDIO_PHYXS_LNSTAT_ALIGN 0x1000 | 
 |  | 
 | #define MDIO_PHYXS_LANE_READY	(MDIO_PHYXS_LNSTAT_SYNC0 | \ | 
 | 				MDIO_PHYXS_LNSTAT_SYNC1 | \ | 
 | 				MDIO_PHYXS_LNSTAT_SYNC2 | \ | 
 | 				MDIO_PHYXS_LNSTAT_SYNC3 | \ | 
 | 				MDIO_PHYXS_LNSTAT_ALIGN) | 
 |  | 
 | static int teranetics_aneg_done(struct phy_device *phydev) | 
 | { | 
 | 	/* auto negotiation state can only be checked when using copper | 
 | 	 * port, if using fiber port, just lie it's done. | 
 | 	 */ | 
 | 	if (!phy_read_mmd(phydev, MDIO_MMD_VEND1, 93)) | 
 | 		return genphy_c45_aneg_done(phydev); | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int teranetics_read_status(struct phy_device *phydev) | 
 | { | 
 | 	int reg; | 
 |  | 
 | 	phydev->link = 1; | 
 |  | 
 | 	phydev->speed = SPEED_10000; | 
 | 	phydev->duplex = DUPLEX_FULL; | 
 |  | 
 | 	if (!phy_read_mmd(phydev, MDIO_MMD_VEND1, 93)) { | 
 | 		reg = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MDIO_PHYXS_LNSTAT); | 
 | 		if (reg < 0 || | 
 | 		    !((reg & MDIO_PHYXS_LANE_READY) == MDIO_PHYXS_LANE_READY)) { | 
 | 			phydev->link = 0; | 
 | 			return 0; | 
 | 		} | 
 |  | 
 | 		reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); | 
 | 		if (reg < 0 || !(reg & MDIO_STAT1_LSTATUS)) | 
 | 			phydev->link = 0; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int teranetics_match_phy_device(struct phy_device *phydev) | 
 | { | 
 | 	return phydev->c45_ids.device_ids[3] == PHY_ID_TN2020; | 
 | } | 
 |  | 
 | static struct phy_driver teranetics_driver[] = { | 
 | { | 
 | 	.phy_id		= PHY_ID_TN2020, | 
 | 	.phy_id_mask	= 0xffffffff, | 
 | 	.name		= "Teranetics TN2020", | 
 | 	.soft_reset	= gen10g_no_soft_reset, | 
 | 	.aneg_done	= teranetics_aneg_done, | 
 | 	.config_init    = gen10g_config_init, | 
 | 	.config_aneg    = gen10g_config_aneg, | 
 | 	.read_status	= teranetics_read_status, | 
 | 	.match_phy_device = teranetics_match_phy_device, | 
 | }, | 
 | }; | 
 |  | 
 | module_phy_driver(teranetics_driver); | 
 |  | 
 | static struct mdio_device_id __maybe_unused teranetics_tbl[] = { | 
 | 	{ PHY_ID_TN2020, 0xffffffff }, | 
 | 	{ } | 
 | }; | 
 |  | 
 | MODULE_DEVICE_TABLE(mdio, teranetics_tbl); |