/* * Platform driver for the Realtek RTL8366 ethernet switch * * Copyright (C) 2009 Gabor Juhos * * 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 #include #include #include #include #include #include #include #include #include //#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 "); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" RTL8366_SMI_DRIVER_NAME);