summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/net/phy/Kconfig5
-rw-r--r--drivers/net/phy/Makefile3
-rw-r--r--drivers/net/phy/phy-core.c175
-rw-r--r--drivers/net/phy/realtek.c172
4 files changed, 354 insertions, 1 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 0665aefd99..c08b8257a7 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -48,6 +48,11 @@ config NATIONAL_PHY
---help---
Currently supports the DP83865 PHY.
+config REALTEK_PHY
+ bool "Driver for Realtek PHYs"
+ ---help---
+ Supports the Realtek 821x PHY.
+
config SMSC_PHY
bool "Drivers for SMSC PHYs"
---help---
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 5f11a857d4..7053b5762c 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -1,4 +1,4 @@
-obj-y += phy.o mdio_bus.o
+obj-y += phy.o mdio_bus.o phy-core.o
obj-$(CONFIG_AR8327N_PHY) += ar8327.o
obj-$(CONFIG_AT803X_PHY) += at803x.o
obj-$(CONFIG_DAVICOM_PHY) += davicom.o
@@ -6,6 +6,7 @@ obj-$(CONFIG_LXT_PHY) += lxt.o
obj-$(CONFIG_MARVELL_PHY) += marvell.o
obj-$(CONFIG_MICREL_PHY) += micrel.o
obj-$(CONFIG_NATIONAL_PHY) += national.o
+obj-$(CONFIG_REALTEK_PHY) += realtek.o
obj-$(CONFIG_SMSC_PHY) += smsc.o
obj-$(CONFIG_NET_DSA_MV88E6XXX) += mv88e6xxx/
diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c
new file mode 100644
index 0000000000..21daaa9a2b
--- /dev/null
+++ b/drivers/net/phy/phy-core.c
@@ -0,0 +1,175 @@
+#include <common.h>
+#include <linux/phy.h>
+
+/**
+ * phy_modify - Convenience function for modifying a given PHY register
+ * @phydev: the phy_device struct
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: new value of bits set in mask to write to @regnum
+ *
+ */
+int phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
+{
+ int ret;
+
+ ret = phy_read(phydev, regnum);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, regnum, (ret & ~mask) | set);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int phy_read_page(struct phy_device *phydev)
+{
+ struct phy_driver *phydrv = to_phy_driver(phydev->dev.driver);
+
+ return phydrv->read_page(phydev);
+}
+
+static int phy_write_page(struct phy_device *phydev, int page)
+{
+ struct phy_driver *phydrv = to_phy_driver(phydev->dev.driver);
+
+ return phydrv->write_page(phydev, page);
+}
+
+/**
+ * phy_save_page() - take the bus lock and save the current page
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * Take the MDIO bus lock, and return the current page number. On error,
+ * returns a negative errno. phy_restore_page() must always be called
+ * after this, irrespective of success or failure of this call.
+ */
+int phy_save_page(struct phy_device *phydev)
+{
+ return phy_read_page(phydev);
+}
+
+/**
+ * phy_select_page() - take the bus lock, save the current page, and set a page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: desired page
+ *
+ * Take the MDIO bus lock to protect against concurrent access, save the
+ * current PHY page, and set the current page. On error, returns a
+ * negative errno, otherwise returns the previous page number.
+ * phy_restore_page() must always be called after this, irrespective
+ * of success or failure of this call.
+ */
+int phy_select_page(struct phy_device *phydev, int page)
+{
+ int ret, oldpage;
+
+ oldpage = ret = phy_save_page(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (oldpage != page) {
+ ret = phy_write_page(phydev, page);
+ if (ret < 0)
+ return ret;
+ }
+
+ return oldpage;
+}
+
+/**
+ * phy_restore_page() - restore the page register and release the bus lock
+ * @phydev: a pointer to a &struct phy_device
+ * @oldpage: the old page, return value from phy_save_page() or phy_select_page()
+ * @ret: operation's return code
+ *
+ * Release the MDIO bus lock, restoring @oldpage if it is a valid page.
+ * This function propagates the earliest error code from the group of
+ * operations.
+ *
+ * Returns:
+ * @oldpage if it was a negative value, otherwise
+ * @ret if it was a negative errno value, otherwise
+ * phy_write_page()'s negative value if it were in error, otherwise
+ * @ret.
+ */
+int phy_restore_page(struct phy_device *phydev, int oldpage, int ret)
+{
+ int r;
+
+ if (oldpage >= 0) {
+ r = phy_write_page(phydev, oldpage);
+
+ /* Propagate the operation return code if the page write
+ * was successful.
+ */
+ if (ret >= 0 && r < 0)
+ ret = r;
+ } else {
+ /* Propagate the phy page selection error code */
+ ret = oldpage;
+ }
+
+ return ret;
+}
+
+/**
+ * phy_read_paged() - Convenience function for reading a paged register
+ * @phydev: a pointer to a &struct phy_device
+ * @page: the page for the phy
+ * @regnum: register number
+ *
+ * Same rules as for phy_read().
+ */
+int phy_read_paged(struct phy_device *phydev, int page, u32 regnum)
+{
+ int ret = 0, oldpage;
+
+ oldpage = phy_select_page(phydev, page);
+ if (oldpage >= 0)
+ ret = phy_read(phydev, regnum);
+
+ return phy_restore_page(phydev, oldpage, ret);
+}
+
+/**
+ * phy_write_paged() - Convenience function for writing a paged register
+ * @phydev: a pointer to a &struct phy_device
+ * @page: the page for the phy
+ * @regnum: register number
+ * @val: value to write
+ *
+ * Same rules as for phy_write().
+ */
+int phy_write_paged(struct phy_device *phydev, int page, u32 regnum, u16 val)
+{
+ int ret = 0, oldpage;
+
+ oldpage = phy_select_page(phydev, page);
+ if (oldpage >= 0)
+ ret = phy_write(phydev, regnum, val);
+
+ return phy_restore_page(phydev, oldpage, ret);
+}
+
+/**
+ * phy_modify_paged() - Convenience function for modifying a paged register
+ * @phydev: a pointer to a &struct phy_device
+ * @page: the page for the phy
+ * @regnum: register number
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * Same rules as for phy_read() and phy_write().
+ */
+int phy_modify_paged(struct phy_device *phydev, int page, u32 regnum,
+ u16 mask, u16 set)
+{
+ int ret = 0, oldpage;
+
+ oldpage = phy_select_page(phydev, page);
+ if (oldpage >= 0)
+ ret = phy_modify(phydev, regnum, mask, set);
+
+ return phy_restore_page(phydev, oldpage, ret);
+}
diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c
new file mode 100644
index 0000000000..4ae050128c
--- /dev/null
+++ b/drivers/net/phy/realtek.c
@@ -0,0 +1,172 @@
+/*
+ * 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 <common.h>
+#include <init.h>
+#include <linux/bitops.h>
+#include <linux/phy.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)
+
+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 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;
+
+ return phy_modify_paged(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY, val);
+}
+
+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->dev, "error enabling power management\n");
+ }
+
+ return ret;
+}
+
+static struct phy_driver realtek_drvs[] = {
+ {
+ PHY_ID_MATCH_EXACT(0x00008201),
+ .drv.name = "RTL8201CP Ethernet",
+ .features = PHY_BASIC_FEATURES,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc816),
+ .drv.name = "RTL8201F Fast Ethernet",
+ .features = PHY_BASIC_FEATURES,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc910),
+ .drv.name = "RTL8211 Gigabit Ethernet",
+ .features = PHY_GBIT_FEATURES,
+ .config_aneg = rtl8211_config_aneg,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc912),
+ .drv.name = "RTL8211B Gigabit Ethernet",
+ .features = PHY_GBIT_FEATURES,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc913),
+ .drv.name = "RTL8211C Gigabit Ethernet",
+ .features = PHY_GBIT_FEATURES,
+ .config_init = rtl8211c_config_init,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc914),
+ .drv.name = "RTL8211DN Gigabit Ethernet",
+ .features = PHY_GBIT_FEATURES,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc915),
+ .drv.name = "RTL8211E Gigabit Ethernet",
+ .features = PHY_GBIT_FEATURES,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc916),
+ .drv.name = "RTL8211F Gigabit Ethernet",
+ .features = PHY_GBIT_FEATURES,
+ .config_init = &rtl8211f_config_init,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001cc961),
+ .drv.name = "RTL8366RB Gigabit Ethernet",
+ .features = PHY_GBIT_FEATURES,
+ .config_init = &rtl8366rb_config_init,
+ },
+};
+
+static int __init realtek_phy_init(void)
+{
+ return phy_drivers_register(realtek_drvs,
+ ARRAY_SIZE(realtek_drvs));
+}
+fs_initcall(realtek_phy_init);