diff options
author | juhosg <juhosg@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2009-12-08 10:29:35 +0000 |
---|---|---|
committer | juhosg <juhosg@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2009-12-08 10:29:35 +0000 |
commit | bfa2f874b92a8eb44e50cdad05cd37ff5ec24bb8 (patch) | |
tree | fd489f9057a8c57bafd585504a1ae4ac9732594e /target/linux/ar71xx/files/drivers | |
parent | 67c4686a288336ca005fd00667cf5370e474e657 (diff) |
ar71xx: add mdio bus driver for the rtl8366 switch
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@18694 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/ar71xx/files/drivers')
-rw-r--r-- | target/linux/ar71xx/files/drivers/net/phy/rtl8366_smi.c | 661 |
1 files changed, 661 insertions, 0 deletions
diff --git a/target/linux/ar71xx/files/drivers/net/phy/rtl8366_smi.c b/target/linux/ar71xx/files/drivers/net/phy/rtl8366_smi.c new file mode 100644 index 0000000000..e43f8a2d03 --- /dev/null +++ b/target/linux/ar71xx/files/drivers/net/phy/rtl8366_smi.c @@ -0,0 +1,661 @@ +/* + * Platform driver for the Realtek RTL8366 ethernet switch + * + * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/spinlock.h> +#include <linux/skbuff.h> +#include <linux/phy.h> +#include <linux/rtl8366_smi.h> + +//#define DEBUG 1 + +#define RTL8366_SMI_DRIVER_NAME "rtl8366-smi" +#define RTL8366_SMI_DRIVER_DESC "Realtek RTL8366 switch driver" +#define RTL8366_SMI_DRIVER_VER "0.1.0" + +#define RTL8366S_PHY_NO_MAX 4 +#define RTL8366S_PHY_PAGE_MAX 7 +#define RTL8366S_PHY_ADDR_MAX 31 + +#define RTL8366S_CHIP_VERSION_CTRL_REG 0x0104 +#define RTL8366S_CHIP_VERSION_MASK 0xf +#define RTL8366S_CHIP_ID_REG 0x0105 +#define RTL8366S_CHIP_ID_8366 0x8366 + +/* PHY registers control */ +#define RTL8366S_PHY_ACCESS_CTRL_REG 0x8028 +#define RTL8366S_PHY_ACCESS_DATA_REG 0x8029 + +#define RTL8366S_PHY_CTRL_READ 1 +#define RTL8366S_PHY_CTRL_WRITE 0 + +#define RTL8366S_PHY_REG_MASK 0x1f +#define RTL8366S_PHY_PAGE_OFFSET 5 +#define RTL8366S_PHY_PAGE_MASK (0x7 << 5) +#define RTL8366S_PHY_NO_OFFSET 9 +#define RTL8366S_PHY_NO_MASK (0x1f << 9) + +#define RTL8366_SMI_ACK_RETRY_COUNT 5 +#define RTL8366_SMI_CLK_DELAY 10 /* nsec */ + +struct rtl8366_smi { + struct platform_device *pdev; + struct rtl8366_smi_platform_data *pdata; + spinlock_t lock; + struct mii_bus *mii_bus; + int mii_irq[PHY_MAX_ADDR]; +}; + +static inline void rtl8366_smi_clk_delay(struct rtl8366_smi *smi) +{ + ndelay(RTL8366_SMI_CLK_DELAY); +} + +static void rtl8366_smi_start(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->pdata->gpio_sda; + unsigned int sck = smi->pdata->gpio_sck; + + /* + * Set GPIO pins to output mode, with initial state: + * SCK = 0, SDA = 1 + */ + gpio_direction_output(sck, 0); + gpio_direction_output(sda, 1); + rtl8366_smi_clk_delay(smi); + + /* CLK 1: 0 -> 1, 1 -> 0 */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + + /* CLK 2: */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); +} + +static void rtl8366_smi_stop(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->pdata->gpio_sda; + unsigned int sck = smi->pdata->gpio_sck; + + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* add a click */ + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* set GPIO pins to input mode */ + gpio_direction_input(sda); + gpio_direction_input(sck); +} + +static void rtl8366_smi_write_bits(struct rtl8366_smi *smi, u32 data, u32 len) +{ + unsigned int sda = smi->pdata->gpio_sda; + unsigned int sck = smi->pdata->gpio_sck; + + for (; len > 0; len--) { + rtl8366_smi_clk_delay(smi); + + /* prepare data */ + if ( data & ( 1 << (len - 1)) ) + gpio_set_value(sda, 1); + else + gpio_set_value(sda, 0); + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + } +} + +static void rtl8366_smi_read_bits(struct rtl8366_smi *smi, u32 len, u32 *data) +{ + unsigned int sda = smi->pdata->gpio_sda; + unsigned int sck = smi->pdata->gpio_sck; + + gpio_direction_input(sda); + + for (*data = 0; len > 0; len--) { + u32 u; + + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + u = gpio_get_value(sda); + gpio_set_value(sck, 0); + + *data |= (u << (len - 1)); + } + + gpio_direction_output(sda, 0); +} + +static int rtl8366_smi_wait_for_ack(struct rtl8366_smi *smi) +{ + int retry_cnt; + + retry_cnt = 0; + do { + u32 ack; + + rtl8366_smi_read_bits(smi, 1, &ack); + if (ack == 0) + break; + + if (++retry_cnt > RTL8366_SMI_ACK_RETRY_COUNT) + return -EIO; + } while (1); + + return 0; +} + +static int rtl8366_smi_write_byte(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return rtl8366_smi_wait_for_ack(smi); +} + +static int rtl8366_smi_read_byte0(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x00, 1); + + return 0; +} + +static int rtl8366_smi_read_byte1(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x01, 1); + + return 0; +} + +static int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + unsigned long flags; + u8 lo = 0; + u8 hi = 0; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send READ command */ + ret = rtl8366_smi_write_byte(smi, 0x0a << 4 | 0x04 << 1 | 0x01); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* read DATA[7:0] */ + rtl8366_smi_read_byte0(smi, &lo); + /* read DATA[15:8] */ + rtl8366_smi_read_byte1(smi, &hi); + + *data = ((u32) lo) | (((u32) hi) << 8); + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} + +static int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send WRITE command */ + ret = rtl8366_smi_write_byte(smi, 0x0a << 4 | 0x04 << 1 | 0x00); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* write DATA[7:0] */ + ret = rtl8366_smi_write_byte(smi, data & 0xff); + if (ret) + goto out; + + /* write DATA[15:8] */ + ret = rtl8366_smi_write_byte(smi, data >> 8); + if (ret) + goto out; + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} + +static int rtl8366_smi_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366_smi_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +#ifdef DEBUG +static void rtl8366_smi_dump_regs(struct rtl8366_smi *smi) +{ + u32 t; + int err; + int i; + + for (i = 0; i < 0x200; i++) { + err = rtl8366_smi_read_reg(smi, i, &t); + if (err) { + dev_err(&smi->pdev->dev, + "unable to read register %04x\n", i); + return; + } + dev_info(&smi->pdev->dev, "reg %04x: %04x\n", i, t); + } + + for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { + int j; + + for (j = 0; j <= RTL8366S_PHY_ADDR_MAX; j++) { + err = rtl8366_smi_read_phy_reg(smi, i, 0, j, &t); + if (err) { + dev_err(&smi->pdev->dev, + "unable to read PHY%u:%02x register\n", + i, j); + return; + } + dev_info(&smi->pdev->dev, + "PHY%u:%02x: %04x\n", i, j, t); + } + } +} +#else +static inline void rtl8366_smi_dump_regs(struct rtl8366_smi *smi) {} +#endif + +static int rtl8366_smi_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366_smi_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366_smi_mii_write(struct mii_bus *bus, int addr, int reg, + u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366_smi_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366_smi_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366_smi_mii_init(struct rtl8366_smi *smi) +{ + int ret; + int i; + + smi->mii_bus = mdiobus_alloc(); + if (smi->mii_bus == NULL) { + ret = -ENOMEM; + goto err; + } + + spin_lock_init(&smi->lock); + smi->mii_bus->priv = (void *) smi; + smi->mii_bus->name = "rtl8366-smi"; + smi->mii_bus->read = rtl8366_smi_mii_read; + smi->mii_bus->write = rtl8366_smi_mii_write; + snprintf(smi->mii_bus->id, MII_BUS_ID_SIZE, "%s", + dev_name(&smi->pdev->dev)); + smi->mii_bus->parent = &smi->pdev->dev; + smi->mii_bus->phy_mask = ~(0x1f); + smi->mii_bus->irq = smi->mii_irq; + for (i = 0; i < PHY_MAX_ADDR; i++) + smi->mii_irq[i] = PHY_POLL; + + rtl8366_smi_dump_regs(smi); + + ret = mdiobus_register(smi->mii_bus); + if (ret) + goto err_free; + + rtl8366_smi_dump_regs(smi); + + return 0; + + err_free: + mdiobus_free(smi->mii_bus); + err: + return ret; +} + +static void rtl8366_smi_mii_cleanup(struct rtl8366_smi *smi) +{ + mdiobus_unregister(smi->mii_bus); + mdiobus_free(smi->mii_bus); +} + +static int rtl8366_smi_setup(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(&smi->pdev->dev, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366S_CHIP_ID_8366: + break; + default: + dev_err(&smi->pdev->dev, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(&smi->pdev->dev, "unable to read chip version\n"); + return ret; + } + + dev_info(&smi->pdev->dev, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366S_CHIP_VERSION_MASK); + + return 0; +} + +static int __init rtl8366_smi_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_smi_platform_data *pdata; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366_SMI_DRIVER_DESC + " version " RTL8366_SMI_DRIVER_VER"\n"); + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data specified\n"); + err = -EINVAL; + goto err_out; + } + + smi = kzalloc(sizeof(struct rtl8366_smi), GFP_KERNEL); + if (!smi) { + dev_err(&pdev->dev, "no memory for private data\n"); + err = -ENOMEM; + goto err_out; + } + + err = gpio_request(pdata->gpio_sda, dev_name(&pdev->dev)); + if (err) { + dev_err(&pdev->dev, "gpio_request failed for %u, err=%d\n", + pdata->gpio_sda, err); + goto err_free_smi; + } + + err = gpio_request(pdata->gpio_sck, dev_name(&pdev->dev)); + if (err) { + dev_err(&pdev->dev, "gpio_request failed for %u, err=%d\n", + pdata->gpio_sck, err); + goto err_free_sda; + } + + smi->pdev = pdev; + smi->pdata = pdata; + spin_lock_init(&smi->lock); + + platform_set_drvdata(pdev, smi); + + dev_info(&pdev->dev, "using GPIO pins %u (SDA) and %u (SCK)\n", + pdata->gpio_sda, pdata->gpio_sck); + + err = rtl8366_smi_setup(smi); + if (err) + goto err_clear_drvdata; + + err = rtl8366_smi_mii_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + gpio_free(pdata->gpio_sck); + err_free_sda: + gpio_free(pdata->gpio_sda); + err_free_smi: + kfree(smi); + err_out: + return err; +} + +static int __devexit rtl8366_smi_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + struct rtl8366_smi_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + rtl8366_smi_mii_cleanup(smi); + platform_set_drvdata(pdev, NULL); + gpio_free(pdata->gpio_sck); + gpio_free(pdata->gpio_sda); + kfree(smi); + } + + return 0; +} + +int rtl8366_phy_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static struct platform_driver rtl8366_smi_driver = { + .driver = { + .name = RTL8366_SMI_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = rtl8366_smi_probe, + .remove = __devexit_p(rtl8366_smi_remove), +}; + +static struct phy_driver rtl8366_smi_phy_driver = { + .phy_id = 0x001cc960, + .name = "Realtek RTL8366", + .phy_id_mask = 0x1ffffff0, + .features = PHY_GBIT_FEATURES, + .config_aneg = rtl8366_phy_config_aneg, + .read_status = genphy_read_status, + .driver = { + .owner = THIS_MODULE, + }, +}; + +static int __init rtl8366_smi_init(void) +{ + int ret; + + ret = phy_driver_register(&rtl8366_smi_phy_driver); + if (ret) + return ret; + + ret = platform_driver_register(&rtl8366_smi_driver); + if (ret) + goto err_phy_unregister; + + return 0; + + err_phy_unregister: + phy_driver_unregister(&rtl8366_smi_phy_driver); + return ret; +} +module_init(rtl8366_smi_init); + +static void __exit rtl8366_smi_exit(void) +{ + platform_driver_unregister(&rtl8366_smi_driver); + phy_driver_unregister(&rtl8366_smi_phy_driver); +} +module_exit(rtl8366_smi_exit); + +MODULE_DESCRIPTION(RTL8366_SMI_DRIVER_DESC); +MODULE_VERSION(RTL8366_SMI_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366_SMI_DRIVER_NAME); |