| /* | 
 |  * drivers/net/phy/realtek.c | 
 |  * | 
 |  * Driver for Realtek PHYs | 
 |  * | 
 |  * Author: Johnson Leung <r58129@freescale.com> | 
 |  * | 
 |  * Copyright (c) 2004 Freescale Semiconductor, Inc. | 
 |  * | 
 |  * This program is free software; you can redistribute  it and/or modify it | 
 |  * under  the terms of  the GNU General  Public License as published by the | 
 |  * Free Software Foundation;  either version 2 of the  License, or (at your | 
 |  * option) any later version. | 
 |  * | 
 |  */ | 
 | #include <linux/bitops.h> | 
 | #include <linux/phy.h> | 
 | #include <linux/module.h> | 
 |  | 
 | #define RTL821x_PHYSR				0x11 | 
 | #define RTL821x_PHYSR_DUPLEX			BIT(13) | 
 | #define RTL821x_PHYSR_SPEED			GENMASK(15, 14) | 
 |  | 
 | #define RTL821x_INER				0x12 | 
 | #define RTL8211B_INER_INIT			0x6400 | 
 | #define RTL8211E_INER_LINK_STATUS		BIT(10) | 
 | #define RTL8211F_INER_LINK_STATUS		BIT(4) | 
 |  | 
 | #define RTL821x_INSR				0x13 | 
 |  | 
 | #define RTL821x_PAGE_SELECT			0x1f | 
 |  | 
 | #define RTL8211F_INSR				0x1d | 
 |  | 
 | #define RTL8211F_TX_DELAY			BIT(8) | 
 |  | 
 | #define RTL8201F_ISR				0x1e | 
 | #define RTL8201F_IER				0x13 | 
 |  | 
 | #define RTL8366RB_POWER_SAVE			0x15 | 
 | #define RTL8366RB_POWER_SAVE_ON			BIT(12) | 
 |  | 
 | MODULE_DESCRIPTION("Realtek PHY driver"); | 
 | MODULE_AUTHOR("Johnson Leung"); | 
 | MODULE_LICENSE("GPL"); | 
 |  | 
 | static int rtl821x_read_page(struct phy_device *phydev) | 
 | { | 
 | 	return __phy_read(phydev, RTL821x_PAGE_SELECT); | 
 | } | 
 |  | 
 | static int rtl821x_write_page(struct phy_device *phydev, int page) | 
 | { | 
 | 	return __phy_write(phydev, RTL821x_PAGE_SELECT, page); | 
 | } | 
 |  | 
 | static int rtl8201_ack_interrupt(struct phy_device *phydev) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	err = phy_read(phydev, RTL8201F_ISR); | 
 |  | 
 | 	return (err < 0) ? err : 0; | 
 | } | 
 |  | 
 | static int rtl821x_ack_interrupt(struct phy_device *phydev) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	err = phy_read(phydev, RTL821x_INSR); | 
 |  | 
 | 	return (err < 0) ? err : 0; | 
 | } | 
 |  | 
 | static int rtl8211f_ack_interrupt(struct phy_device *phydev) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	err = phy_read_paged(phydev, 0xa43, RTL8211F_INSR); | 
 |  | 
 | 	return (err < 0) ? err : 0; | 
 | } | 
 |  | 
 | static int rtl8201_config_intr(struct phy_device *phydev) | 
 | { | 
 | 	u16 val; | 
 |  | 
 | 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) | 
 | 		val = BIT(13) | BIT(12) | BIT(11); | 
 | 	else | 
 | 		val = 0; | 
 |  | 
 | 	return phy_write_paged(phydev, 0x7, RTL8201F_IER, val); | 
 | } | 
 |  | 
 | static int rtl8211b_config_intr(struct phy_device *phydev) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) | 
 | 		err = phy_write(phydev, RTL821x_INER, | 
 | 				RTL8211B_INER_INIT); | 
 | 	else | 
 | 		err = phy_write(phydev, RTL821x_INER, 0); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | static int rtl8211e_config_intr(struct phy_device *phydev) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) | 
 | 		err = phy_write(phydev, RTL821x_INER, | 
 | 				RTL8211E_INER_LINK_STATUS); | 
 | 	else | 
 | 		err = phy_write(phydev, RTL821x_INER, 0); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | static int rtl8211f_config_intr(struct phy_device *phydev) | 
 | { | 
 | 	u16 val; | 
 |  | 
 | 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) | 
 | 		val = RTL8211F_INER_LINK_STATUS; | 
 | 	else | 
 | 		val = 0; | 
 |  | 
 | 	return phy_write_paged(phydev, 0xa42, RTL821x_INER, val); | 
 | } | 
 |  | 
 | static int rtl8211_config_aneg(struct phy_device *phydev) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = genphy_config_aneg(phydev); | 
 | 	if (ret < 0) | 
 | 		return ret; | 
 |  | 
 | 	/* Quirk was copied from vendor driver. Unfortunately it includes no | 
 | 	 * description of the magic numbers. | 
 | 	 */ | 
 | 	if (phydev->speed == SPEED_100 && phydev->autoneg == AUTONEG_DISABLE) { | 
 | 		phy_write(phydev, 0x17, 0x2138); | 
 | 		phy_write(phydev, 0x0e, 0x0260); | 
 | 	} else { | 
 | 		phy_write(phydev, 0x17, 0x2108); | 
 | 		phy_write(phydev, 0x0e, 0x0000); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int rtl8211c_config_init(struct phy_device *phydev) | 
 | { | 
 | 	/* RTL8211C has an issue when operating in Gigabit slave mode */ | 
 | 	phy_set_bits(phydev, MII_CTRL1000, | 
 | 		     CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER); | 
 |  | 
 | 	return genphy_config_init(phydev); | 
 | } | 
 |  | 
 | static int rtl8211f_config_init(struct phy_device *phydev) | 
 | { | 
 | 	int ret; | 
 | 	u16 val = 0; | 
 |  | 
 | 	ret = genphy_config_init(phydev); | 
 | 	if (ret < 0) | 
 | 		return ret; | 
 |  | 
 | 	/* enable TX-delay for rgmii-id and rgmii-txid, otherwise disable it */ | 
 | 	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || | 
 | 	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) | 
 | 		val = RTL8211F_TX_DELAY; | 
 | 	 | 
 | 	phy_write_paged(phydev, 0x0A43, 0x1B, 0xdc84);/*modify by chencheng*/ | 
 | 	phy_write_paged(phydev, 0x0A43, 0x1C,0x7180);/*modify by chencheng*/	 | 
 |  | 
 | 	return phy_modify_paged(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY, val); | 
 | } | 
 |  | 
 | static int rtl8211b_suspend(struct phy_device *phydev) | 
 | { | 
 | 	phy_write(phydev, MII_MMD_DATA, BIT(9)); | 
 |  | 
 | 	return genphy_suspend(phydev); | 
 | } | 
 |  | 
 | static int rtl8211b_resume(struct phy_device *phydev) | 
 | { | 
 | 	phy_write(phydev, MII_MMD_DATA, 0); | 
 |  | 
 | 	return genphy_resume(phydev); | 
 | } | 
 |  | 
 | static int rtl8366rb_config_init(struct phy_device *phydev) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = genphy_config_init(phydev); | 
 | 	if (ret < 0) | 
 | 		return ret; | 
 |  | 
 | 	ret = phy_set_bits(phydev, RTL8366RB_POWER_SAVE, | 
 | 			   RTL8366RB_POWER_SAVE_ON); | 
 | 	if (ret) { | 
 | 		dev_err(&phydev->mdio.dev, | 
 | 			"error enabling power management\n"); | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static struct phy_driver realtek_drvs[] = { | 
 | 	{ | 
 | 		.phy_id         = 0x00008201, | 
 | 		.name           = "RTL8201CP Ethernet", | 
 | 		.phy_id_mask    = 0x0000ffff, | 
 | 		.features       = PHY_BASIC_FEATURES, | 
 | 		.flags          = PHY_HAS_INTERRUPT, | 
 | 	}, { | 
 | 		.phy_id		= 0x001cc816, | 
 | 		.name		= "RTL8201F Fast Ethernet", | 
 | 		.phy_id_mask	= 0x001fffff, | 
 | 		.features	= PHY_BASIC_FEATURES, | 
 | 		.flags		= PHY_HAS_INTERRUPT, | 
 | 		.ack_interrupt	= &rtl8201_ack_interrupt, | 
 | 		.config_intr	= &rtl8201_config_intr, | 
 | 		.suspend	= genphy_suspend, | 
 | 		.resume		= genphy_resume, | 
 | 		.read_page	= rtl821x_read_page, | 
 | 		.write_page	= rtl821x_write_page, | 
 | 	}, { | 
 | 		.phy_id		= 0x001cc910, | 
 | 		.name		= "RTL8211 Gigabit Ethernet", | 
 | 		.phy_id_mask	= 0x001fffff, | 
 | 		.features	= PHY_GBIT_FEATURES, | 
 | 		.config_aneg	= rtl8211_config_aneg, | 
 | 		.read_mmd	= &genphy_read_mmd_unsupported, | 
 | 		.write_mmd	= &genphy_write_mmd_unsupported, | 
 | 	}, { | 
 | 		.phy_id		= 0x001cc912, | 
 | 		.name		= "RTL8211B Gigabit Ethernet", | 
 | 		.phy_id_mask	= 0x001fffff, | 
 | 		.features	= PHY_GBIT_FEATURES, | 
 | 		.flags		= PHY_HAS_INTERRUPT, | 
 | 		.ack_interrupt	= &rtl821x_ack_interrupt, | 
 | 		.config_intr	= &rtl8211b_config_intr, | 
 | 		.read_mmd	= &genphy_read_mmd_unsupported, | 
 | 		.write_mmd	= &genphy_write_mmd_unsupported, | 
 | 		.suspend	= rtl8211b_suspend, | 
 | 		.resume		= rtl8211b_resume, | 
 | 	}, { | 
 | 		.phy_id		= 0x001cc913, | 
 | 		.name		= "RTL8211C Gigabit Ethernet", | 
 | 		.phy_id_mask	= 0x001fffff, | 
 | 		.features	= PHY_GBIT_FEATURES, | 
 | 		.config_init	= rtl8211c_config_init, | 
 | 		.read_mmd	= &genphy_read_mmd_unsupported, | 
 | 		.write_mmd	= &genphy_write_mmd_unsupported, | 
 | 	}, { | 
 | 		.phy_id		= 0x001cc914, | 
 | 		.name		= "RTL8211DN Gigabit Ethernet", | 
 | 		.phy_id_mask	= 0x001fffff, | 
 | 		.features	= PHY_GBIT_FEATURES, | 
 | 		.flags		= PHY_HAS_INTERRUPT, | 
 | 		.ack_interrupt	= rtl821x_ack_interrupt, | 
 | 		.config_intr	= rtl8211e_config_intr, | 
 | 		.suspend	= genphy_suspend, | 
 | 		.resume		= genphy_resume, | 
 | 	}, { | 
 | 		.phy_id		= 0x001cc915, | 
 | 		.name		= "RTL8211E Gigabit Ethernet", | 
 | 		.phy_id_mask	= 0x001fffff, | 
 | 		.features	= PHY_GBIT_FEATURES, | 
 | 		.flags		= PHY_HAS_INTERRUPT, | 
 | 		.ack_interrupt	= &rtl821x_ack_interrupt, | 
 | 		.config_intr	= &rtl8211e_config_intr, | 
 | 		.suspend	= genphy_suspend, | 
 | 		.resume		= genphy_resume, | 
 | 	}, { | 
 | 		.phy_id		= 0x001cc916, | 
 | 		.name		= "RTL8211F Gigabit Ethernet", | 
 | 		.phy_id_mask	= 0x001fffff, | 
 | 		.features	= PHY_GBIT_FEATURES, | 
 | 		.flags		= PHY_HAS_INTERRUPT, | 
 | 		.config_init	= &rtl8211f_config_init, | 
 | 		.ack_interrupt	= &rtl8211f_ack_interrupt, | 
 | 		.config_intr	= &rtl8211f_config_intr, | 
 | 		.suspend	= genphy_suspend, | 
 | 		.resume		= genphy_resume, | 
 | 		.read_page	= rtl821x_read_page, | 
 | 		.write_page	= rtl821x_write_page, | 
 | 	}, { | 
 | 		.phy_id		= 0x001cc961, | 
 | 		.name		= "RTL8366RB Gigabit Ethernet", | 
 | 		.phy_id_mask	= 0x001fffff, | 
 | 		.features	= PHY_GBIT_FEATURES, | 
 | 		.flags		= PHY_HAS_INTERRUPT, | 
 | 		.config_init	= &rtl8366rb_config_init, | 
 | 		.suspend	= genphy_suspend, | 
 | 		.resume		= genphy_resume, | 
 | 	}, | 
 | }; | 
 |  | 
 | module_phy_driver(realtek_drvs); | 
 |  | 
 | static struct mdio_device_id __maybe_unused realtek_tbl[] = { | 
 | 	{ 0x001cc816, 0x001fffff }, | 
 | 	{ 0x001cc910, 0x001fffff }, | 
 | 	{ 0x001cc912, 0x001fffff }, | 
 | 	{ 0x001cc913, 0x001fffff }, | 
 | 	{ 0x001cc914, 0x001fffff }, | 
 | 	{ 0x001cc915, 0x001fffff }, | 
 | 	{ 0x001cc916, 0x001fffff }, | 
 | 	{ 0x001cc961, 0x001fffff }, | 
 | 	{ } | 
 | }; | 
 |  | 
 | MODULE_DEVICE_TABLE(mdio, realtek_tbl); |