diff options
Diffstat (limited to 'drivers/net/phy')
-rw-r--r-- | drivers/net/phy/Kconfig | 22 | ||||
-rw-r--r-- | drivers/net/phy/Makefile | 2 | ||||
-rw-r--r-- | drivers/net/phy/at803x.c | 26 | ||||
-rw-r--r-- | drivers/net/phy/bcm7xxx.c | 60 | ||||
-rw-r--r-- | drivers/net/phy/dp83848.c | 88 | ||||
-rw-r--r-- | drivers/net/phy/fixed_phy.c | 11 | ||||
-rw-r--r-- | drivers/net/phy/marvell.c | 52 | ||||
-rw-r--r-- | drivers/net/phy/mdio-cavium.c | 153 | ||||
-rw-r--r-- | drivers/net/phy/mdio-cavium.h | 119 | ||||
-rw-r--r-- | drivers/net/phy/mdio-octeon.c | 280 | ||||
-rw-r--r-- | drivers/net/phy/mdio-thunder.c | 154 | ||||
-rw-r--r-- | drivers/net/phy/micrel.c | 9 | ||||
-rw-r--r-- | drivers/net/phy/spi_ks8995.c | 304 |
13 files changed, 866 insertions, 414 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index f0a77020037a..6dad9a9c356c 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -183,15 +183,29 @@ config MDIO_GPIO To compile this driver as a module, choose M here: the module will be called mdio-gpio. +config MDIO_CAVIUM + tristate + config MDIO_OCTEON - tristate "Support for MDIO buses on Octeon and ThunderX SOCs" + tristate "Support for MDIO buses on Octeon and some ThunderX SOCs" depends on 64BIT depends on HAS_IOMEM + select MDIO_CAVIUM help - This module provides a driver for the Octeon and ThunderX MDIO - busses. It is required by the Octeon and ThunderX ethernet device - drivers. + buses. It is required by the Octeon and ThunderX ethernet device + drivers on some systems. + +config MDIO_THUNDER + tristate "Support for MDIO buses on ThunderX SOCs" + depends on 64BIT + depends on PCI + select MDIO_CAVIUM + help + This driver supports the MDIO interfaces found on Cavium + ThunderX SoCs when the MDIO bus device appears as a PCI + device. + config MDIO_SUN4I tristate "Allwinner sun4i MDIO interface support" diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 680e88f9915a..fcdbb9299fab 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -31,6 +31,8 @@ obj-$(CONFIG_DP83867_PHY) += dp83867.o obj-$(CONFIG_STE10XP) += ste10Xp.o obj-$(CONFIG_MICREL_PHY) += micrel.o obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o +obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o +obj-$(CONFIG_MDIO_CAVIUM) += mdio-cavium.o obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o obj-$(CONFIG_AT803X_PHY) += at803x.o obj-$(CONFIG_AMD_PHY) += amd.o diff --git a/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c index 2174ec937b4d..1e901c7cfaac 100644 --- a/drivers/net/phy/at803x.c +++ b/drivers/net/phy/at803x.c @@ -52,6 +52,9 @@ #define AT803X_DEBUG_REG_5 0x05 #define AT803X_DEBUG_TX_CLK_DLY_EN BIT(8) +#define AT803X_REG_CHIP_CONFIG 0x1f +#define AT803X_BT_BX_REG_SEL 0x8000 + #define ATH8030_PHY_ID 0x004dd076 #define ATH8031_PHY_ID 0x004dd074 #define ATH8035_PHY_ID 0x004dd072 @@ -206,6 +209,7 @@ static int at803x_suspend(struct phy_device *phydev) { int value; int wol_enabled; + int ccr; mutex_lock(&phydev->lock); @@ -221,6 +225,16 @@ static int at803x_suspend(struct phy_device *phydev) phy_write(phydev, MII_BMCR, value); + if (phydev->interface != PHY_INTERFACE_MODE_SGMII) + goto done; + + /* also power-down SGMII interface */ + ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG); + phy_write(phydev, AT803X_REG_CHIP_CONFIG, ccr & ~AT803X_BT_BX_REG_SEL); + phy_write(phydev, MII_BMCR, phy_read(phydev, MII_BMCR) | BMCR_PDOWN); + phy_write(phydev, AT803X_REG_CHIP_CONFIG, ccr | AT803X_BT_BX_REG_SEL); + +done: mutex_unlock(&phydev->lock); return 0; @@ -229,6 +243,7 @@ static int at803x_suspend(struct phy_device *phydev) static int at803x_resume(struct phy_device *phydev) { int value; + int ccr; mutex_lock(&phydev->lock); @@ -236,6 +251,17 @@ static int at803x_resume(struct phy_device *phydev) value &= ~(BMCR_PDOWN | BMCR_ISOLATE); phy_write(phydev, MII_BMCR, value); + if (phydev->interface != PHY_INTERFACE_MODE_SGMII) + goto done; + + /* also power-up SGMII interface */ + ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG); + phy_write(phydev, AT803X_REG_CHIP_CONFIG, ccr & ~AT803X_BT_BX_REG_SEL); + value = phy_read(phydev, MII_BMCR) & ~(BMCR_PDOWN | BMCR_ISOLATE); + phy_write(phydev, MII_BMCR, value); + phy_write(phydev, AT803X_REG_CHIP_CONFIG, ccr | AT803X_BT_BX_REG_SEL); + +done: mutex_unlock(&phydev->lock); return 0; diff --git a/drivers/net/phy/bcm7xxx.c b/drivers/net/phy/bcm7xxx.c index db507e3bcab9..b881a7b1e4f6 100644 --- a/drivers/net/phy/bcm7xxx.c +++ b/drivers/net/phy/bcm7xxx.c @@ -24,7 +24,7 @@ #define MII_BCM7XXX_100TX_FALSE_CAR 0x13 #define MII_BCM7XXX_100TX_DISC 0x14 #define MII_BCM7XXX_AUX_MODE 0x1d -#define MII_BCM7XX_64CLK_MDIO BIT(12) +#define MII_BCM7XXX_64CLK_MDIO BIT(12) #define MII_BCM7XXX_TEST 0x1f #define MII_BCM7XXX_SHD_MODE_2 BIT(2) @@ -247,7 +247,7 @@ static int bcm7xxx_config_init(struct phy_device *phydev) int ret; /* Enable 64 clock MDIO */ - phy_write(phydev, MII_BCM7XXX_AUX_MODE, MII_BCM7XX_64CLK_MDIO); + phy_write(phydev, MII_BCM7XXX_AUX_MODE, MII_BCM7XXX_64CLK_MDIO); phy_read(phydev, MII_BCM7XXX_AUX_MODE); /* set shadow mode 2 */ @@ -317,6 +317,21 @@ static int bcm7xxx_suspend(struct phy_device *phydev) .resume = bcm7xxx_28nm_resume, \ } +#define BCM7XXX_40NM_EPHY(_oui, _name) \ +{ \ + .phy_id = (_oui), \ + .phy_id_mask = 0xfffffff0, \ + .name = _name, \ + .features = PHY_BASIC_FEATURES | \ + SUPPORTED_Pause | SUPPORTED_Asym_Pause, \ + .flags = PHY_IS_INTERNAL, \ + .config_init = bcm7xxx_config_init, \ + .config_aneg = genphy_config_aneg, \ + .read_status = genphy_read_status, \ + .suspend = bcm7xxx_suspend, \ + .resume = bcm7xxx_config_init, \ +} + static struct phy_driver bcm7xxx_driver[] = { BCM7XXX_28NM_GPHY(PHY_ID_BCM7250, "Broadcom BCM7250"), BCM7XXX_28NM_GPHY(PHY_ID_BCM7364, "Broadcom BCM7364"), @@ -324,43 +339,10 @@ static struct phy_driver bcm7xxx_driver[] = { BCM7XXX_28NM_GPHY(PHY_ID_BCM7439, "Broadcom BCM7439"), BCM7XXX_28NM_GPHY(PHY_ID_BCM7439_2, "Broadcom BCM7439 (2)"), BCM7XXX_28NM_GPHY(PHY_ID_BCM7445, "Broadcom BCM7445"), -{ - .phy_id = PHY_ID_BCM7425, - .phy_id_mask = 0xfffffff0, - .name = "Broadcom BCM7425", - .features = PHY_BASIC_FEATURES | - SUPPORTED_Pause | SUPPORTED_Asym_Pause, - .flags = PHY_IS_INTERNAL, - .config_init = bcm7xxx_config_init, - .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, - .suspend = bcm7xxx_suspend, - .resume = bcm7xxx_config_init, -}, { - .phy_id = PHY_ID_BCM7429, - .phy_id_mask = 0xfffffff0, - .name = "Broadcom BCM7429", - .features = PHY_BASIC_FEATURES | - SUPPORTED_Pause | SUPPORTED_Asym_Pause, - .flags = PHY_IS_INTERNAL, - .config_init = bcm7xxx_config_init, - .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, - .suspend = bcm7xxx_suspend, - .resume = bcm7xxx_config_init, -}, { - .phy_id = PHY_ID_BCM7435, - .phy_id_mask = 0xfffffff0, - .name = "Broadcom BCM7435", - .features = PHY_BASIC_FEATURES | - SUPPORTED_Pause | SUPPORTED_Asym_Pause, - .flags = PHY_IS_INTERNAL, - .config_init = bcm7xxx_config_init, - .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, - .suspend = bcm7xxx_suspend, - .resume = bcm7xxx_config_init, -} }; + BCM7XXX_40NM_EPHY(PHY_ID_BCM7425, "Broadcom BCM7425"), + BCM7XXX_40NM_EPHY(PHY_ID_BCM7429, "Broadcom BCM7429"), + BCM7XXX_40NM_EPHY(PHY_ID_BCM7435, "Broadcom BCM7435"), +}; static struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = { { PHY_ID_BCM7250, 0xfffffff0, }, diff --git a/drivers/net/phy/dp83848.c b/drivers/net/phy/dp83848.c index 5e14e629c597..03d54c4adc88 100644 --- a/drivers/net/phy/dp83848.c +++ b/drivers/net/phy/dp83848.c @@ -1,7 +1,7 @@ /* * Driver for the Texas Instruments DP83848 PHY * - * Copyright (C) 2015 Texas Instruments Inc. + * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com/ * * 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 @@ -16,11 +16,13 @@ #include <linux/module.h> #include <linux/phy.h> -#define DP83848_PHY_ID 0x20005c90 +#define TI_DP83848C_PHY_ID 0x20005ca0 +#define NS_DP83848C_PHY_ID 0x20005c90 +#define TLK10X_PHY_ID 0x2000a210 /* Registers */ -#define DP83848_MICR 0x11 -#define DP83848_MISR 0x12 +#define DP83848_MICR 0x11 /* MII Interrupt Control Register */ +#define DP83848_MISR 0x12 /* MII Interrupt Status Register */ /* MICR Register Fields */ #define DP83848_MICR_INT_OE BIT(0) /* Interrupt Output Enable */ @@ -36,6 +38,12 @@ #define DP83848_MISR_ED_INT_EN BIT(6) /* Energy detect */ #define DP83848_MISR_LQM_INT_EN BIT(7) /* Link Quality Monitor */ +#define DP83848_INT_EN_MASK \ + (DP83848_MISR_ANC_INT_EN | \ + DP83848_MISR_DUP_INT_EN | \ + DP83848_MISR_SPD_INT_EN | \ + DP83848_MISR_LINK_INT_EN) + static int dp83848_ack_interrupt(struct phy_device *phydev) { int err = phy_read(phydev, DP83848_MISR); @@ -45,50 +53,58 @@ static int dp83848_ack_interrupt(struct phy_device *phydev) static int dp83848_config_intr(struct phy_device *phydev) { - int err; + int control, ret; + + control = phy_read(phydev, DP83848_MICR); + if (control < 0) + return control; if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { - err = phy_write(phydev, DP83848_MICR, - DP83848_MICR_INT_OE | - DP83848_MICR_INTEN); - if (err < 0) - return err; - - return phy_write(phydev, DP83848_MISR, - DP83848_MISR_ANC_INT_EN | - DP83848_MISR_DUP_INT_EN | - DP83848_MISR_SPD_INT_EN | - DP83848_MISR_LINK_INT_EN); + control |= DP83848_MICR_INT_OE; + control |= DP83848_MICR_INTEN; + + ret = phy_write(phydev, DP83848_MISR, DP83848_INT_EN_MASK); + if (ret < 0) + return ret; + } else { + control &= ~DP83848_MICR_INTEN; } - return phy_write(phydev, DP83848_MICR, 0x0); + return phy_write(phydev, DP83848_MICR, control); } static struct mdio_device_id __maybe_unused dp83848_tbl[] = { - { DP83848_PHY_ID, 0xfffffff0 }, + { TI_DP83848C_PHY_ID, 0xfffffff0 }, + { NS_DP83848C_PHY_ID, 0xfffffff0 }, + { TLK10X_PHY_ID, 0xfffffff0 }, { } }; MODULE_DEVICE_TABLE(mdio, dp83848_tbl); +#define DP83848_PHY_DRIVER(_id, _name) \ + { \ + .phy_id = _id, \ + .phy_id_mask = 0xfffffff0, \ + .name = _name, \ + .features = PHY_BASIC_FEATURES, \ + .flags = PHY_HAS_INTERRUPT, \ + \ + .soft_reset = genphy_soft_reset, \ + .config_init = genphy_config_init, \ + .suspend = genphy_suspend, \ + .resume = genphy_resume, \ + .config_aneg = genphy_config_aneg, \ + .read_status = genphy_read_status, \ + \ + /* IRQ related */ \ + .ack_interrupt = dp83848_ack_interrupt, \ + .config_intr = dp83848_config_intr, \ + } + static struct phy_driver dp83848_driver[] = { - { - .phy_id = DP83848_PHY_ID, - .phy_id_mask = 0xfffffff0, - .name = "TI DP83848", - .features = PHY_BASIC_FEATURES, - .flags = PHY_HAS_INTERRUPT, - - .soft_reset = genphy_soft_reset, - .config_init = genphy_config_init, - .suspend = genphy_suspend, - .resume = genphy_resume, - .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, - - /* IRQ related */ - .ack_interrupt = dp83848_ack_interrupt, - .config_intr = dp83848_config_intr, - }, + DP83848_PHY_DRIVER(TI_DP83848C_PHY_ID, "TI DP83848C 10/100 Mbps PHY"), + DP83848_PHY_DRIVER(NS_DP83848C_PHY_ID, "NS DP83848C 10/100 Mbps PHY"), + DP83848_PHY_DRIVER(TLK10X_PHY_ID, "TI TLK10X 10/100 Mbps PHY"), }; module_phy_driver(dp83848_driver); diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c index ab9c473d75ea..fc07a8866020 100644 --- a/drivers/net/phy/fixed_phy.c +++ b/drivers/net/phy/fixed_phy.c @@ -285,7 +285,7 @@ err_regs: } EXPORT_SYMBOL_GPL(fixed_phy_add); -void fixed_phy_del(int phy_addr) +static void fixed_phy_del(int phy_addr) { struct fixed_mdio_bus *fmb = &platform_fmb; struct fixed_phy *fp, *tmp; @@ -300,7 +300,6 @@ void fixed_phy_del(int phy_addr) } } } -EXPORT_SYMBOL_GPL(fixed_phy_del); static int phy_fixed_addr; static DEFINE_SPINLOCK(phy_fixed_addr_lock); @@ -371,6 +370,14 @@ struct phy_device *fixed_phy_register(unsigned int irq, } EXPORT_SYMBOL_GPL(fixed_phy_register); +void fixed_phy_unregister(struct phy_device *phy) +{ + phy_device_remove(phy); + + fixed_phy_del(phy->mdio.addr); +} +EXPORT_SYMBOL_GPL(fixed_phy_unregister); + static int __init fixed_mdio_bus_init(void) { struct fixed_mdio_bus *fmb = &platform_fmb; diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index ab1d0fcaf1d9..280e8795b463 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -133,6 +133,11 @@ #define MII_88E3016_DISABLE_SCRAMBLER 0x0200 #define MII_88E3016_AUTO_MDIX_CROSSOVER 0x0030 +#define MII_88E1510_GEN_CTRL_REG_1 0x14 +#define MII_88E1510_GEN_CTRL_REG_1_MODE_MASK 0x7 +#define MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII 0x1 /* SGMII to copper */ +#define MII_88E1510_GEN_CTRL_REG_1_RESET 0x8000 /* Soft reset */ + MODULE_DESCRIPTION("Marvell PHY driver"); MODULE_AUTHOR("Andy Fleming"); MODULE_LICENSE("GPL"); @@ -631,6 +636,41 @@ static int m88e1111_config_init(struct phy_device *phydev) return phy_write(phydev, MII_BMCR, BMCR_RESET); } +static int m88e1510_config_init(struct phy_device *phydev) +{ + int err; + int temp; + + /* SGMII-to-Copper mode initialization */ + if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { + /* Select page 18 */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 18); + if (err < 0) + return err; + + /* In reg 20, write MODE[2:0] = 0x1 (SGMII to Copper) */ + temp = phy_read(phydev, MII_88E1510_GEN_CTRL_REG_1); + temp &= ~MII_88E1510_GEN_CTRL_REG_1_MODE_MASK; + temp |= MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII; + err = phy_write(phydev, MII_88E1510_GEN_CTRL_REG_1, temp); + if (err < 0) + return err; + + /* PHY reset is necessary after changing MODE[2:0] */ + temp |= MII_88E1510_GEN_CTRL_REG_1_RESET; + err = phy_write(phydev, MII_88E1510_GEN_CTRL_REG_1, temp); + if (err < 0) + return err; + + /* Reset page selection */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0); + if (err < 0) + return err; + } + + return marvell_config_init(phydev); +} + static int m88e1118_config_aneg(struct phy_device *phydev) { int err; @@ -1031,8 +1071,8 @@ static u64 marvell_get_stat(struct phy_device *phydev, int i) { struct marvell_hw_stat stat = marvell_hw_stats[i]; struct marvell_priv *priv = phydev->priv; - int err, oldpage; - u64 val; + int err, oldpage, val; + u64 ret; oldpage = phy_read(phydev, MII_MARVELL_PHY_PAGE); err = phy_write(phydev, MII_MARVELL_PHY_PAGE, @@ -1042,16 +1082,16 @@ static u64 marvell_get_stat(struct phy_device *phydev, int i) val = phy_read(phydev, stat.reg); if (val < 0) { - val = UINT64_MAX; + ret = UINT64_MAX; } else { val = val & ((1 << stat.bits) - 1); priv->stats[i] += val; - val = priv->stats[i]; + ret = priv->stats[i]; } phy_write(phydev, MII_MARVELL_PHY_PAGE, oldpage); - return val; + return ret; } static void marvell_get_stats(struct phy_device *phydev, @@ -1268,7 +1308,7 @@ static struct phy_driver marvell_drivers[] = { .features = PHY_GBIT_FEATURES, .flags = PHY_HAS_INTERRUPT, .probe = marvell_probe, - .config_init = &marvell_config_init, + .config_init = &m88e1510_config_init, .config_aneg = &m88e1510_config_aneg, .read_status = &marvell_read_status, .ack_interrupt = &marvell_ack_interrupt, diff --git a/drivers/net/phy/mdio-cavium.c b/drivers/net/phy/mdio-cavium.c new file mode 100644 index 000000000000..6df2fa755bb4 --- /dev/null +++ b/drivers/net/phy/mdio-cavium.c @@ -0,0 +1,153 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2009-2016 Cavium, Inc. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/io.h> + +#include "mdio-cavium.h" + +static void cavium_mdiobus_set_mode(struct cavium_mdiobus *p, + enum cavium_mdiobus_mode m) +{ + union cvmx_smix_clk smi_clk; + + if (m == p->mode) + return; + + smi_clk.u64 = oct_mdio_readq(p->register_base + SMI_CLK); + smi_clk.s.mode = (m == C45) ? 1 : 0; + smi_clk.s.preamble = 1; + oct_mdio_writeq(smi_clk.u64, p->register_base + SMI_CLK); + p->mode = m; +} + +static int cavium_mdiobus_c45_addr(struct cavium_mdiobus *p, + int phy_id, int regnum) +{ + union cvmx_smix_cmd smi_cmd; + union cvmx_smix_wr_dat smi_wr; + int timeout = 1000; + + cavium_mdiobus_set_mode(p, C45); + + smi_wr.u64 = 0; + smi_wr.s.dat = regnum & 0xffff; + oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT); + + regnum = (regnum >> 16) & 0x1f; + + smi_cmd.u64 = 0; + smi_cmd.s.phy_op = 0; /* MDIO_CLAUSE_45_ADDRESS */ + smi_cmd.s.phy_adr = phy_id; + smi_cmd.s.reg_adr = regnum; + oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); + + do { + /* Wait 1000 clocks so we don't saturate the RSL bus + * doing reads. + */ + __delay(1000); + smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT); + } while (smi_wr.s.pending && --timeout); + + if (timeout <= 0) + return -EIO; + return 0; +} + +int cavium_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum) +{ + struct cavium_mdiobus *p = bus->priv; + union cvmx_smix_cmd smi_cmd; + union cvmx_smix_rd_dat smi_rd; + unsigned int op = 1; /* MDIO_CLAUSE_22_READ */ + int timeout = 1000; + + if (regnum & MII_ADDR_C45) { + int r = cavium_mdiobus_c45_addr(p, phy_id, regnum); + + if (r < 0) + return r; + + regnum = (regnum >> 16) & 0x1f; + op = 3; /* MDIO_CLAUSE_45_READ */ + } else { + cavium_mdiobus_set_mode(p, C22); + } + + smi_cmd.u64 = 0; + smi_cmd.s.phy_op = op; + smi_cmd.s.phy_adr = phy_id; + smi_cmd.s.reg_adr = regnum; + oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); + + do { + /* Wait 1000 clocks so we don't saturate the RSL bus + * doing reads. + */ + __delay(1000); + smi_rd.u64 = oct_mdio_readq(p->register_base + SMI_RD_DAT); + } while (smi_rd.s.pending && --timeout); + + if (smi_rd.s.val) + return smi_rd.s.dat; + else + return -EIO; +} +EXPORT_SYMBOL(cavium_mdiobus_read); + +int cavium_mdiobus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val) +{ + struct cavium_mdiobus *p = bus->priv; + union cvmx_smix_cmd smi_cmd; + union cvmx_smix_wr_dat smi_wr; + unsigned int op = 0; /* MDIO_CLAUSE_22_WRITE */ + int timeout = 1000; + + if (regnum & MII_ADDR_C45) { + int r = cavium_mdiobus_c45_addr(p, phy_id, regnum); + + if (r < 0) + return r; + + regnum = (regnum >> 16) & 0x1f; + op = 1; /* MDIO_CLAUSE_45_WRITE */ + } else { + cavium_mdiobus_set_mode(p, C22); + } + + smi_wr.u64 = 0; + smi_wr.s.dat = val; + oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT); + + smi_cmd.u64 = 0; + smi_cmd.s.phy_op = op; + smi_cmd.s.phy_adr = phy_id; + smi_cmd.s.reg_adr = regnum; + oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); + + do { + /* Wait 1000 clocks so we don't saturate the RSL bus + * doing reads. + */ + __delay(1000); + smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT); + } while (smi_wr.s.pending && --timeout); + + if (timeout <= 0) + return -EIO; + + return 0; +} +EXPORT_SYMBOL(cavium_mdiobus_write); + +MODULE_DESCRIPTION("Common code for OCTEON and Thunder MDIO bus drivers"); +MODULE_AUTHOR("David Daney"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-cavium.h b/drivers/net/phy/mdio-cavium.h new file mode 100644 index 000000000000..4bccd45d24e2 --- /dev/null +++ b/drivers/net/phy/mdio-cavium.h @@ -0,0 +1,119 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2009-2016 Cavium, Inc. + */ + +enum cavium_mdiobus_mode { + UNINIT = 0, + C22, + C45 +}; + +#define SMI_CMD 0x0 +#define SMI_WR_DAT 0x8 +#define SMI_RD_DAT 0x10 +#define SMI_CLK 0x18 +#define SMI_EN 0x20 + +#ifdef __BIG_ENDIAN_BITFIELD +#define OCT_MDIO_BITFIELD_FIELD(field, more) \ + field; \ + more + +#else +#define OCT_MDIO_BITFIELD_FIELD(field, more) \ + more \ + field; + +#endif + +union cvmx_smix_clk { + u64 u64; + struct cvmx_smix_clk_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_25_63:39, + OCT_MDIO_BITFIELD_FIELD(u64 mode:1, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_21_23:3, + OCT_MDIO_BITFIELD_FIELD(u64 sample_hi:5, + OCT_MDIO_BITFIELD_FIELD(u64 sample_mode:1, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_14_14:1, + OCT_MDIO_BITFIELD_FIELD(u64 clk_idle:1, + OCT_MDIO_BITFIELD_FIELD(u64 preamble:1, + OCT_MDIO_BITFIELD_FIELD(u64 sample:4, + OCT_MDIO_BITFIELD_FIELD(u64 phase:8, + ;)))))))))) + } s; +}; + +union cvmx_smix_cmd { + u64 u64; + struct cvmx_smix_cmd_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, + OCT_MDIO_BITFIELD_FIELD(u64 phy_op:2, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_13_15:3, + OCT_MDIO_BITFIELD_FIELD(u64 phy_adr:5, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_5_7:3, + OCT_MDIO_BITFIELD_FIELD(u64 reg_adr:5, + ;)))))) + } s; +}; + +union cvmx_smix_en { + u64 u64; + struct cvmx_smix_en_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_1_63:63, + OCT_MDIO_BITFIELD_FIELD(u64 en:1, + ;)) + } s; +}; + +union cvmx_smix_rd_dat { + u64 u64; + struct cvmx_smix_rd_dat_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, + OCT_MDIO_BITFIELD_FIELD(u64 pending:1, + OCT_MDIO_BITFIELD_FIELD(u64 val:1, + OCT_MDIO_BITFIELD_FIELD(u64 dat:16, + ;)))) + } s; +}; + +union cvmx_smix_wr_dat { + u64 u64; + struct cvmx_smix_wr_dat_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, + OCT_MDIO_BITFIELD_FIELD(u64 pending:1, + OCT_MDIO_BITFIELD_FIELD(u64 val:1, + OCT_MDIO_BITFIELD_FIELD(u64 dat:16, + ;)))) + } s; +}; + +struct cavium_mdiobus { + struct mii_bus *mii_bus; + u64 register_base; + enum cavium_mdiobus_mode mode; +}; + +#ifdef CONFIG_CAVIUM_OCTEON_SOC + +#include <asm/octeon/octeon.h> + +static inline void oct_mdio_writeq(u64 val, u64 addr) +{ + cvmx_write_csr(addr, val); +} + +static inline u64 oct_mdio_readq(u64 addr) +{ + return cvmx_read_csr(addr); +} +#else +#define oct_mdio_writeq(val, addr) writeq(val, (void *)addr) +#define oct_mdio_readq(addr) readq((void *)addr) +#endif + +int cavium_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum); +int cavium_mdiobus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val); diff --git a/drivers/net/phy/mdio-octeon.c b/drivers/net/phy/mdio-octeon.c index 47d4f2f263d1..ab6914f8bd50 100644 --- a/drivers/net/phy/mdio-octeon.c +++ b/drivers/net/phy/mdio-octeon.c @@ -3,272 +3,26 @@ * License. See the file "COPYING" in the main directory of this archive * for more details. * - * Copyright (C) 2009-2012 Cavium, Inc. + * Copyright (C) 2009-2015 Cavium, Inc. */ #include <linux/platform_device.h> #include <linux/of_address.h> #include <linux/of_mdio.h> -#include <linux/delay.h> #include <linux/module.h> #include <linux/gfp.h> #include <linux/phy.h> #include <linux/io.h> -#ifdef CONFIG_CAVIUM_OCTEON_SOC -#include <asm/octeon/octeon.h> -#endif - -#define DRV_VERSION "1.1" -#define DRV_DESCRIPTION "Cavium Networks Octeon/ThunderX SMI/MDIO driver" - -#define SMI_CMD 0x0 -#define SMI_WR_DAT 0x8 -#define SMI_RD_DAT 0x10 -#define SMI_CLK 0x18 -#define SMI_EN 0x20 - -#ifdef __BIG_ENDIAN_BITFIELD -#define OCT_MDIO_BITFIELD_FIELD(field, more) \ - field; \ - more - -#else -#define OCT_MDIO_BITFIELD_FIELD(field, more) \ - more \ - field; - -#endif - -union cvmx_smix_clk { - u64 u64; - struct cvmx_smix_clk_s { - OCT_MDIO_BITFIELD_FIELD(u64 reserved_25_63:39, - OCT_MDIO_BITFIELD_FIELD(u64 mode:1, - OCT_MDIO_BITFIELD_FIELD(u64 reserved_21_23:3, - OCT_MDIO_BITFIELD_FIELD(u64 sample_hi:5, - OCT_MDIO_BITFIELD_FIELD(u64 sample_mode:1, - OCT_MDIO_BITFIELD_FIELD(u64 reserved_14_14:1, - OCT_MDIO_BITFIELD_FIELD(u64 clk_idle:1, - OCT_MDIO_BITFIELD_FIELD(u64 preamble:1, - OCT_MDIO_BITFIELD_FIELD(u64 sample:4, - OCT_MDIO_BITFIELD_FIELD(u64 phase:8, - ;)))))))))) - } s; -}; - -union cvmx_smix_cmd { - u64 u64; - struct cvmx_smix_cmd_s { - OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, - OCT_MDIO_BITFIELD_FIELD(u64 phy_op:2, - OCT_MDIO_BITFIELD_FIELD(u64 reserved_13_15:3, - OCT_MDIO_BITFIELD_FIELD(u64 phy_adr:5, - OCT_MDIO_BITFIELD_FIELD(u64 reserved_5_7:3, - OCT_MDIO_BITFIELD_FIELD(u64 reg_adr:5, - ;)))))) - } s; -}; - -union cvmx_smix_en { - u64 u64; - struct cvmx_smix_en_s { - OCT_MDIO_BITFIELD_FIELD(u64 reserved_1_63:63, - OCT_MDIO_BITFIELD_FIELD(u64 en:1, - ;)) - } s; -}; - -union cvmx_smix_rd_dat { - u64 u64; - struct cvmx_smix_rd_dat_s { - OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, - OCT_MDIO_BITFIELD_FIELD(u64 pending:1, - OCT_MDIO_BITFIELD_FIELD(u64 val:1, - OCT_MDIO_BITFIELD_FIELD(u64 dat:16, - ;)))) - } s; -}; - -union cvmx_smix_wr_dat { - u64 u64; - struct cvmx_smix_wr_dat_s { - OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, - OCT_MDIO_BITFIELD_FIELD(u64 pending:1, - OCT_MDIO_BITFIELD_FIELD(u64 val:1, - OCT_MDIO_BITFIELD_FIELD(u64 dat:16, - ;)))) - } s; -}; - -enum octeon_mdiobus_mode { - UNINIT = 0, - C22, - C45 -}; - -struct octeon_mdiobus { - struct mii_bus *mii_bus; - u64 register_base; - resource_size_t mdio_phys; - resource_size_t regsize; - enum octeon_mdiobus_mode mode; -}; - -#ifdef CONFIG_CAVIUM_OCTEON_SOC -static void oct_mdio_writeq(u64 val, u64 addr) -{ - cvmx_write_csr(addr, val); -} - -static u64 oct_mdio_readq(u64 addr) -{ - return cvmx_read_csr(addr); -} -#else -#define oct_mdio_writeq(val, addr) writeq_relaxed(val, (void *)addr) -#define oct_mdio_readq(addr) readq_relaxed((void *)addr) -#endif - -static void octeon_mdiobus_set_mode(struct octeon_mdiobus *p, - enum octeon_mdiobus_mode m) -{ - union cvmx_smix_clk smi_clk; - - if (m == p->mode) - return; - - smi_clk.u64 = oct_mdio_readq(p->register_base + SMI_CLK); - smi_clk.s.mode = (m == C45) ? 1 : 0; - smi_clk.s.preamble = 1; - oct_mdio_writeq(smi_clk.u64, p->register_base + SMI_CLK); - p->mode = m; -} - -static int octeon_mdiobus_c45_addr(struct octeon_mdiobus *p, - int phy_id, int regnum) -{ - union cvmx_smix_cmd smi_cmd; - union cvmx_smix_wr_dat smi_wr; - int timeout = 1000; - - octeon_mdiobus_set_mode(p, C45); - - smi_wr.u64 = 0; - smi_wr.s.dat = regnum & 0xffff; - oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT); - - regnum = (regnum >> 16) & 0x1f; - - smi_cmd.u64 = 0; - smi_cmd.s.phy_op = 0; /* MDIO_CLAUSE_45_ADDRESS */ - smi_cmd.s.phy_adr = phy_id; - smi_cmd.s.reg_adr = regnum; - oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); - - do { - /* Wait 1000 clocks so we don't saturate the RSL bus - * doing reads. - */ - __delay(1000); - smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT); - } while (smi_wr.s.pending && --timeout); - - if (timeout <= 0) - return -EIO; - return 0; -} - -static int octeon_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum) -{ - struct octeon_mdiobus *p = bus->priv; - union cvmx_smix_cmd smi_cmd; - union cvmx_smix_rd_dat smi_rd; - unsigned int op = 1; /* MDIO_CLAUSE_22_READ */ - int timeout = 1000; - - if (regnum & MII_ADDR_C45) { - int r = octeon_mdiobus_c45_addr(p, phy_id, regnum); - if (r < 0) - return r; - - regnum = (regnum >> 16) & 0x1f; - op = 3; /* MDIO_CLAUSE_45_READ */ - } else { - octeon_mdiobus_set_mode(p, C22); - } - - - smi_cmd.u64 = 0; - smi_cmd.s.phy_op = op; - smi_cmd.s.phy_adr = phy_id; - smi_cmd.s.reg_adr = regnum; - oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); - - do { - /* Wait 1000 clocks so we don't saturate the RSL bus - * doing reads. - */ - __delay(1000); - smi_rd.u64 = oct_mdio_readq(p->register_base + SMI_RD_DAT); - } while (smi_rd.s.pending && --timeout); - - if (smi_rd.s.val) - return smi_rd.s.dat; - else - return -EIO; -} - -static int octeon_mdiobus_write(struct mii_bus *bus, int phy_id, - int regnum, u16 val) -{ - struct octeon_mdiobus *p = bus->priv; - union cvmx_smix_cmd smi_cmd; - union cvmx_smix_wr_dat smi_wr; - unsigned int op = 0; /* MDIO_CLAUSE_22_WRITE */ - int timeout = 1000; - - - if (regnum & MII_ADDR_C45) { - int r = octeon_mdiobus_c45_addr(p, phy_id, regnum); - if (r < 0) - return r; - - regnum = (regnum >> 16) & 0x1f; - op = 1; /* MDIO_CLAUSE_45_WRITE */ - } else { - octeon_mdiobus_set_mode(p, C22); - } - - smi_wr.u64 = 0; - smi_wr.s.dat = val; - oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT); - - smi_cmd.u64 = 0; - smi_cmd.s.phy_op = op; - smi_cmd.s.phy_adr = phy_id; - smi_cmd.s.reg_adr = regnum; - oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); - - do { - /* Wait 1000 clocks so we don't saturate the RSL bus - * doing reads. - */ - __delay(1000); - smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT); - } while (smi_wr.s.pending && --timeout); - - if (timeout <= 0) - return -EIO; - - return 0; -} +#include "mdio-cavium.h" static int octeon_mdiobus_probe(struct platform_device *pdev) { - struct octeon_mdiobus *bus; + struct cavium_mdiobus *bus; struct mii_bus *mii_bus; struct resource *res_mem; + resource_size_t mdio_phys; + resource_size_t regsize; union cvmx_smix_en smi_en; int err = -ENOENT; @@ -284,17 +38,17 @@ static int octeon_mdiobus_probe(struct platform_device *pdev) bus = mii_bus->priv; bus->mii_bus = mii_bus; - bus->mdio_phys = res_mem->start; - bus->regsize = resource_size(res_mem); + mdio_phys = res_mem->start; + regsize = resource_size(res_mem); - if (!devm_request_mem_region(&pdev->dev, bus->mdio_phys, bus->regsize, + if (!devm_request_mem_region(&pdev->dev, mdio_phys, regsize, res_mem->name)) { dev_err(&pdev->dev, "request_mem_region failed\n"); return -ENXIO; } bus->register_base = - (u64)devm_ioremap(&pdev->dev, bus->mdio_phys, bus->regsize); + (u64)devm_ioremap(&pdev->dev, mdio_phys, regsize); if (!bus->register_base) { dev_err(&pdev->dev, "dev_ioremap failed\n"); return -ENOMEM; @@ -304,13 +58,12 @@ static int octeon_mdiobus_probe(struct platform_device *pdev) smi_en.s.en = 1; oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); - bus->mii_bus->priv = bus; - bus->mii_bus->name = "mdio-octeon"; + bus->mii_bus->name = KBUILD_MODNAME; snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%llx", bus->register_base); bus->mii_bus->parent = &pdev->dev; - bus->mii_bus->read = octeon_mdiobus_read; - bus->mii_bus->write = octeon_mdiobus_write; + bus->mii_bus->read = cavium_mdiobus_read; + bus->mii_bus->write = cavium_mdiobus_write; platform_set_drvdata(pdev, bus); @@ -318,7 +71,7 @@ static int octeon_mdiobus_probe(struct platform_device *pdev) if (err) goto fail_register; - dev_info(&pdev->dev, "Version " DRV_VERSION "\n"); + dev_info(&pdev->dev, "Probed\n"); return 0; fail_register: @@ -330,7 +83,7 @@ fail_register: static int octeon_mdiobus_remove(struct platform_device *pdev) { - struct octeon_mdiobus *bus; + struct cavium_mdiobus *bus; union cvmx_smix_en smi_en; bus = platform_get_drvdata(pdev); @@ -352,7 +105,7 @@ MODULE_DEVICE_TABLE(of, octeon_mdiobus_match); static struct platform_driver octeon_mdiobus_driver = { .driver = { - .name = "mdio-octeon", + .name = KBUILD_MODNAME, .of_match_table = octeon_mdiobus_match, }, .probe = octeon_mdiobus_probe, @@ -367,7 +120,6 @@ EXPORT_SYMBOL(octeon_mdiobus_force_mod_depencency); module_platform_driver(octeon_mdiobus_driver); -MODULE_DESCRIPTION(DRV_DESCRIPTION); -MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("Cavium OCTEON MDIO bus driver"); MODULE_AUTHOR("David Daney"); MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-thunder.c b/drivers/net/phy/mdio-thunder.c new file mode 100644 index 000000000000..564616968cad --- /dev/null +++ b/drivers/net/phy/mdio-thunder.c @@ -0,0 +1,154 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2009-2016 Cavium, Inc. + */ + +#include <linux/of_address.h> +#include <linux/of_mdio.h> +#include <linux/module.h> +#include <linux/gfp.h> +#include <linux/phy.h> +#include <linux/io.h> +#include <linux/acpi.h> +#include <linux/pci.h> + +#include "mdio-cavium.h" + +struct thunder_mdiobus_nexus { + void __iomem *bar0; + struct cavium_mdiobus *buses[4]; +}; + +static int thunder_mdiobus_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct device_node *node; + struct fwnode_handle *fwn; + struct thunder_mdiobus_nexus *nexus; + int err; + int i; + + nexus = devm_kzalloc(&pdev->dev, sizeof(*nexus), GFP_KERNEL); + if (!nexus) + return -ENOMEM; + + pci_set_drvdata(pdev, nexus); + + err = pcim_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "Failed to enable PCI device\n"); + pci_set_drvdata(pdev, NULL); + return err; + } + + err = pci_request_regions(pdev, KBUILD_MODNAME); + if (err) { + dev_err(&pdev->dev, "pci_request_regions failed\n"); + goto err_disable_device; + } + + nexus->bar0 = pcim_iomap(pdev, 0, pci_resource_len(pdev, 0)); + if (!nexus->bar0) { + err = -ENOMEM; + goto err_release_regions; + } + + i = 0; + device_for_each_child_node(&pdev->dev, fwn) { + struct resource r; + struct mii_bus *mii_bus; + struct cavium_mdiobus *bus; + union cvmx_smix_en smi_en; + + /* If it is not an OF node we cannot handle it yet, so + * exit the loop. + */ + node = to_of_node(fwn); + if (!node) + break; + + err = of_address_to_resource(node, 0, &r); + if (err) { + dev_err(&pdev->dev, + "Couldn't translate address for \"%s\"\n", + node->name); + break; + } + + mii_bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*bus)); + if (!mii_bus) + break; + bus = mii_bus->priv; + bus->mii_bus = mii_bus; + + nexus->buses[i] = bus; + i++; + + bus->register_base = (u64)nexus->bar0 + + r.start - pci_resource_start(pdev, 0); + + smi_en.u64 = 0; + smi_en.s.en = 1; + oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); + bus->mii_bus->name = KBUILD_MODNAME; + snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%llx", r.start); + bus->mii_bus->parent = &pdev->dev; + bus->mii_bus->read = cavium_mdiobus_read; + bus->mii_bus->write = cavium_mdiobus_write; + + err = of_mdiobus_register(bus->mii_bus, node); + if (err) + dev_err(&pdev->dev, "of_mdiobus_register failed\n"); + + dev_info(&pdev->dev, "Added bus at %llx\n", r.start); + if (i >= ARRAY_SIZE(nexus->buses)) + break; + } + return 0; + +err_release_regions: + pci_release_regions(pdev); + +err_disable_device: + pci_set_drvdata(pdev, NULL); + return err; +} + +static void thunder_mdiobus_pci_remove(struct pci_dev *pdev) +{ + int i; + struct thunder_mdiobus_nexus *nexus = pci_get_drvdata(pdev); + + for (i = 0; i < ARRAY_SIZE(nexus->buses); i++) { + struct cavium_mdiobus *bus = nexus->buses[i]; + + if (!bus) + continue; + + mdiobus_unregister(bus->mii_bus); + mdiobus_free(bus->mii_bus); + oct_mdio_writeq(0, bus->register_base + SMI_EN); + } + pci_set_drvdata(pdev, NULL); +} + +static const struct pci_device_id thunder_mdiobus_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xa02b) }, + { 0, } /* End of table. */ +}; +MODULE_DEVICE_TABLE(pci, thunder_mdiobus_id_table); + +static struct pci_driver thunder_mdiobus_driver = { + .name = KBUILD_MODNAME, + .id_table = thunder_mdiobus_id_table, + .probe = thunder_mdiobus_pci_probe, + .remove = thunder_mdiobus_pci_remove, +}; + +module_pci_driver(thunder_mdiobus_driver); + +MODULE_DESCRIPTION("Cavium ThunderX MDIO bus driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index dc85f7095e51..4516c8a4fd82 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -623,18 +623,19 @@ static u64 kszphy_get_stat(struct phy_device *phydev, int i) { struct kszphy_hw_stat stat = kszphy_hw_stats[i]; struct kszphy_priv *priv = phydev->priv; - u64 val; + int val; + u64 ret; val = phy_read(phydev, stat.reg); if (val < 0) { - val = UINT64_MAX; + ret = UINT64_MAX; } else { val = val & ((1 << stat.bits) - 1); priv->stats[i] += val; - val = priv->stats[i]; + ret = priv->stats[i]; } - return val; + return ret; } static void kszphy_get_stats(struct phy_device *phydev, diff --git a/drivers/net/phy/spi_ks8995.c b/drivers/net/phy/spi_ks8995.c index c72c42206850..b5d50d458728 100644 --- a/drivers/net/phy/spi_ks8995.c +++ b/drivers/net/phy/spi_ks8995.c @@ -18,6 +18,9 @@ #include <linux/module.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/of.h> +#include <linux/of_gpio.h> #include <linux/spi/spi.h> @@ -74,6 +77,7 @@ #define KS8995_REGS_SIZE 0x80 #define KSZ8864_REGS_SIZE 0x100 +#define KSZ8795_REGS_SIZE 0x100 #define ID1_CHIPID_M 0xf #define ID1_CHIPID_S 4 @@ -82,15 +86,63 @@ #define ID1_START_SW 1 /* start the switch */ #define FAMILY_KS8995 0x95 +#define FAMILY_KSZ8795 0x87 #define CHIPID_M 0 +#define KS8995_CHIP_ID 0x00 +#define KSZ8864_CHIP_ID 0x01 +#define KSZ8795_CHIP_ID 0x09 #define KS8995_CMD_WRITE 0x02U #define KS8995_CMD_READ 0x03U #define KS8995_RESET_DELAY 10 /* usec */ +enum ks8995_chip_variant { + ks8995, + ksz8864, + ksz8795, + max_variant +}; + +struct ks8995_chip_params { + char *name; + int family_id; + int chip_id; + int regs_size; + int addr_width; + int addr_shift; +}; + +static const struct ks8995_chip_params ks8995_chip[] = { + [ks8995] = { + .name = "KS8995MA", + .family_id = FAMILY_KS8995, + .chip_id = KS8995_CHIP_ID, + .regs_size = KS8995_REGS_SIZE, + .addr_width = 8, + .addr_shift = 0, + }, + [ksz8864] = { + .name = "KSZ8864RMN", + .family_id = FAMILY_KS8995, + .chip_id = KSZ8864_CHIP_ID, + .regs_size = KSZ8864_REGS_SIZE, + .addr_width = 8, + .addr_shift = 0, + }, + [ksz8795] = { + .name = "KSZ8795CLX", + .family_id = FAMILY_KSZ8795, + .chip_id = KSZ8795_CHIP_ID, + .regs_size = KSZ8795_REGS_SIZE, + .addr_width = 12, + .addr_shift = 1, + }, +}; + struct ks8995_pdata { - /* not yet implemented */ + int reset_gpio; + enum of_gpio_flags reset_gpio_flags; }; struct ks8995_switch { @@ -98,7 +150,17 @@ struct ks8995_switch { struct mutex lock; struct ks8995_pdata *pdata; struct bin_attribute regs_attr; + const struct ks8995_chip_params *chip; + int revision_id; +}; + +static const struct spi_device_id ks8995_id[] = { + {"ks8995", ks8995}, + {"ksz8864", ksz8864}, + {"ksz8795", ksz8795}, + { } }; +MODULE_DEVICE_TABLE(spi, ks8995_id); static inline u8 get_chip_id(u8 val) { @@ -110,20 +172,44 @@ static inline u8 get_chip_rev(u8 val) return (val >> ID1_REVISION_S) & ID1_REVISION_M; } +/* create_spi_cmd - create a chip specific SPI command header + * @ks: pointer to switch instance + * @cmd: SPI command for switch + * @address: register address for command + * + * Different chip families use different bit pattern to address the switches + * registers: + * + * KS8995: 8bit command + 8bit address + * KSZ8795: 3bit command + 12bit address + 1bit TR (?) + */ +static inline __be16 create_spi_cmd(struct ks8995_switch *ks, int cmd, + unsigned address) +{ + u16 result = cmd; + + /* make room for address (incl. address shift) */ + result <<= ks->chip->addr_width + ks->chip->addr_shift; + /* add address */ + result |= address << ks->chip->addr_shift; + /* SPI protocol needs big endian */ + return cpu_to_be16(result); +} /* ------------------------------------------------------------------------ */ static int ks8995_read(struct ks8995_switch *ks, char *buf, unsigned offset, size_t count) { - u8 cmd[2]; + __be16 cmd; struct spi_transfer t[2]; struct spi_message m; int err; + cmd = create_spi_cmd(ks, KS8995_CMD_READ, offset); spi_message_init(&m); memset(&t, 0, sizeof(t)); - t[0].tx_buf = cmd; + t[0].tx_buf = &cmd; t[0].len = sizeof(cmd); spi_message_add_tail(&t[0], &m); @@ -131,9 +217,6 @@ static int ks8995_read(struct ks8995_switch *ks, char *buf, t[1].len = count; spi_message_add_tail(&t[1], &m); - cmd[0] = KS8995_CMD_READ; - cmd[1] = offset; - mutex_lock(&ks->lock); err = spi_sync(ks->spi, &m); mutex_unlock(&ks->lock); @@ -141,20 +224,20 @@ static int ks8995_read(struct ks8995_switch *ks, char *buf, return err ? err : count; } - static int ks8995_write(struct ks8995_switch *ks, char *buf, unsigned offset, size_t count) { - u8 cmd[2]; + __be16 cmd; struct spi_transfer t[2]; struct spi_message m; int err; + cmd = create_spi_cmd(ks, KS8995_CMD_WRITE, offset); spi_message_init(&m); memset(&t, 0, sizeof(t)); - t[0].tx_buf = cmd; + t[0].tx_buf = &cmd; t[0].len = sizeof(cmd); spi_message_add_tail(&t[0], &m); @@ -162,9 +245,6 @@ static int ks8995_write(struct ks8995_switch *ks, char *buf, t[1].len = count; spi_message_add_tail(&t[1], &m); - cmd[0] = KS8995_CMD_WRITE; - cmd[1] = offset; - mutex_lock(&ks->lock); err = spi_sync(ks->spi, &m); mutex_unlock(&ks->lock); @@ -233,6 +313,107 @@ static ssize_t ks8995_registers_write(struct file *filp, struct kobject *kobj, return ks8995_write(ks8995, buf, off, count); } +/* ks8995_get_revision - get chip revision + * @ks: pointer to switch instance + * + * Verify chip family and id and get chip revision. + */ +static int ks8995_get_revision(struct ks8995_switch *ks) +{ + int err; + u8 id0, id1, ksz8864_id; + + /* read family id */ + err = ks8995_read_reg(ks, KS8995_REG_ID0, &id0); + if (err) { + err = -EIO; + goto err_out; + } + + /* verify family id */ + if (id0 != ks->chip->family_id) { + dev_err(&ks->spi->dev, "chip family id mismatch: expected 0x%02x but 0x%02x read\n", + ks->chip->family_id, id0); + err = -ENODEV; + goto err_out; + } + + switch (ks->chip->family_id) { + case FAMILY_KS8995: + /* try reading chip id at CHIP ID1 */ + err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1); + if (err) { + err = -EIO; + goto err_out; + } + + /* verify chip id */ + if ((get_chip_id(id1) == CHIPID_M) && + (get_chip_id(id1) == ks->chip->chip_id)) { + /* KS8995MA */ + ks->revision_id = get_chip_rev(id1); + } else if (get_chip_id(id1) != CHIPID_M) { + /* KSZ8864RMN */ + err = ks8995_read_reg(ks, KS8995_REG_ID1, &ksz8864_id); + if (err) { + err = -EIO; + goto err_out; + } + + if ((ksz8864_id & 0x80) && + (ks->chip->chip_id == KSZ8864_CHIP_ID)) { + ks->revision_id = get_chip_rev(id1); + } + + } else { + dev_err(&ks->spi->dev, "unsupported chip id for KS8995 family: 0x%02x\n", + id1); + err = -ENODEV; + } + break; + case FAMILY_KSZ8795: + /* try reading chip id at CHIP ID1 */ + err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1); + if (err) { + err = -EIO; + goto err_out; + } + + if (get_chip_id(id1) == ks->chip->chip_id) { + ks->revision_id = get_chip_rev(id1); + } else { + dev_err(&ks->spi->dev, "unsupported chip id for KSZ8795 family: 0x%02x\n", + id1); + err = -ENODEV; + } + break; + default: + dev_err(&ks->spi->dev, "unsupported family id: 0x%02x\n", id0); + err = -ENODEV; + break; + } +err_out: + return err; +} + +/* ks8995_parse_dt - setup platform data from devicetree + * @ks: pointer to switch instance + * + * Parses supported DT properties and sets up platform data + * accordingly. + */ +static void ks8995_parse_dt(struct ks8995_switch *ks) +{ + struct device_node *np = ks->spi->dev.of_node; + struct ks8995_pdata *pdata = ks->pdata; + + if (!np) + return; + + pdata->reset_gpio = of_get_named_gpio_flags(np, "reset-gpios", 0, + &pdata->reset_gpio_flags); +} + static const struct bin_attribute ks8995_registers_attr = { .attr = { .name = "registers", @@ -244,24 +425,58 @@ static const struct bin_attribute ks8995_registers_attr = { }; /* ------------------------------------------------------------------------ */ - static int ks8995_probe(struct spi_device *spi) { - struct ks8995_switch *ks; - struct ks8995_pdata *pdata; - u8 ids[2]; - int err; + struct ks8995_switch *ks; + int err; + int variant = spi_get_device_id(spi)->driver_data; - /* Chip description */ - pdata = spi->dev.platform_data; + if (variant >= max_variant) { + dev_err(&spi->dev, "bad chip variant %d\n", variant); + return -ENODEV; + } ks = devm_kzalloc(&spi->dev, sizeof(*ks), GFP_KERNEL); if (!ks) return -ENOMEM; mutex_init(&ks->lock); - ks->pdata = pdata; ks->spi = spi_dev_get(spi); + ks->chip = &ks8995_chip[variant]; + + if (ks->spi->dev.of_node) { + ks->pdata = devm_kzalloc(&spi->dev, sizeof(*ks->pdata), + GFP_KERNEL); + if (!ks->pdata) + return -ENOMEM; + + ks->pdata->reset_gpio = -1; + + ks8995_parse_dt(ks); + } + + if (!ks->pdata) + ks->pdata = spi->dev.platform_data; + + /* de-assert switch reset */ + if (ks->pdata && gpio_is_valid(ks->pdata->reset_gpio)) { + unsigned long flags; + + flags = (ks->pdata->reset_gpio_flags == OF_GPIO_ACTIVE_LOW ? + GPIOF_ACTIVE_LOW : 0); + + err = devm_gpio_request_one(&spi->dev, + ks->pdata->reset_gpio, + flags, "switch-reset"); + if (err) { + dev_err(&spi->dev, + "failed to get reset-gpios: %d\n", err); + return -EIO; + } + + gpiod_set_value(gpio_to_desc(ks->pdata->reset_gpio), 0); + } + spi_set_drvdata(spi, ks); spi->mode = SPI_MODE_0; @@ -272,39 +487,12 @@ static int ks8995_probe(struct spi_device *spi) return err; } - err = ks8995_read(ks, ids, KS8995_REG_ID0, sizeof(ids)); - if (err < 0) { - dev_err(&spi->dev, "unable to read id registers, err=%d\n", - err); + err = ks8995_get_revision(ks); + if (err) return err; - } - - switch (ids[0]) { - case FAMILY_KS8995: - break; - default: - dev_err(&spi->dev, "unknown family id:%02x\n", ids[0]); - return -ENODEV; - } + ks->regs_attr.size = ks->chip->regs_size; memcpy(&ks->regs_attr, &ks8995_registers_attr, sizeof(ks->regs_attr)); - if (get_chip_id(ids[1]) != CHIPID_M) { - u8 val; - - /* Check if this is a KSZ8864RMN */ - err = ks8995_read(ks, &val, KSZ8864_REG_ID1, sizeof(val)); - if (err < 0) { - dev_err(&spi->dev, - "unable to read chip id register, err=%d\n", - err); - return err; - } - if ((val & 0x80) == 0) { - dev_err(&spi->dev, "unknown chip:%02x,0\n", ids[1]); - return err; - } - ks->regs_attr.size = KSZ8864_REGS_SIZE; - } err = ks8995_reset(ks); if (err) @@ -317,14 +505,8 @@ static int ks8995_probe(struct spi_device *spi) return err; } - if (get_chip_id(ids[1]) == CHIPID_M) { - dev_info(&spi->dev, - "KS8995 device found, Chip ID:%x, Revision:%x\n", - get_chip_id(ids[1]), get_chip_rev(ids[1])); - } else { - dev_info(&spi->dev, "KSZ8864 device found, Revision:%x\n", - get_chip_rev(ids[1])); - } + dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n", + ks->chip->name, ks->chip->chip_id, ks->revision_id); return 0; } @@ -335,17 +517,21 @@ static int ks8995_remove(struct spi_device *spi) sysfs_remove_bin_file(&spi->dev.kobj, &ks->regs_attr); + /* assert reset */ + if (ks->pdata && gpio_is_valid(ks->pdata->reset_gpio)) + gpiod_set_value(gpio_to_desc(ks->pdata->reset_gpio), 1); + return 0; } /* ------------------------------------------------------------------------ */ - static struct spi_driver ks8995_driver = { .driver = { .name = "spi-ks8995", }, .probe = ks8995_probe, .remove = ks8995_remove, + .id_table = ks8995_id, }; module_spi_driver(ks8995_driver); |