From 6dffbe53fbdcc7bd5f6379fa18264f060b0cf61d Mon Sep 17 00:00:00 2001 From: Tony Prisk Date: Sat, 18 May 2013 09:39:07 +0000 Subject: net: velocity: Add platform device support to VIA velocity driver Add support for the VIA Velocity network driver to be bound to a OF created platform device. Signed-off-by: Tony Prisk Signed-off-by: David S. Miller --- .../devicetree/bindings/net/via-velocity.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/via-velocity.txt (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/via-velocity.txt b/Documentation/devicetree/bindings/net/via-velocity.txt new file mode 100644 index 00000000000..b3db469b1ad --- /dev/null +++ b/Documentation/devicetree/bindings/net/via-velocity.txt @@ -0,0 +1,20 @@ +* VIA Velocity 10/100/1000 Network Controller + +Required properties: +- compatible : Should be "via,velocity-vt6110" +- reg : Address and length of the io space +- interrupts : Should contain the controller interrupt line + +Optional properties: +- no-eeprom : PCI network cards use an external EEPROM to store data. Embedded + devices quite often set this data in uboot and do not provide an eeprom. + Specify this option if you have no external eeprom. + +Examples: + +eth0@d8004000 { + compatible = "via,velocity-vt6110"; + reg = <0xd8004000 0x400>; + interrupts = <10>; + no-eeprom; +}; -- cgit v1.2.3-70-g09d2 From 0b8bf1baabe56f721d541953f083560d0660d78f Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Mon, 20 May 2013 09:16:58 +0000 Subject: net: dm9000: Allow instantiation using device tree This patch adds Device Tree support to dm9000 driver. Signed-off-by: Tomasz Figa Reviewed-by: Sylwester Nawrocki Reviewed-by: Sascha Hauer Signed-off-by: David S. Miller --- .../devicetree/bindings/net/davicom-dm9000.txt | 26 ++++++++++++++ .../devicetree/bindings/vendor-prefixes.txt | 1 + drivers/net/ethernet/davicom/dm9000.c | 42 ++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/davicom-dm9000.txt (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/davicom-dm9000.txt b/Documentation/devicetree/bindings/net/davicom-dm9000.txt new file mode 100644 index 00000000000..2d39c990e64 --- /dev/null +++ b/Documentation/devicetree/bindings/net/davicom-dm9000.txt @@ -0,0 +1,26 @@ +Davicom DM9000 Fast Ethernet controller + +Required properties: +- compatible = "davicom,dm9000"; +- reg : physical addresses and sizes of registers, must contain 2 entries: + first entry : address register, + second entry : data register. +- interrupt-parent : interrupt controller to which the device is connected +- interrupts : interrupt specifier specific to interrupt controller + +Optional properties: +- local-mac-address : A bytestring of 6 bytes specifying Ethernet MAC address + to use (from firmware or bootloader) +- davicom,no-eeprom : Configuration EEPROM is not available +- davicom,ext-phy : Use external PHY + +Example: + + ethernet@18000000 { + compatible = "davicom,dm9000"; + reg = <0x18000000 0x2 0x18000004 0x2>; + interrupt-parent = <&gpn>; + interrupts = <7 4>; + local-mac-address = [00 00 de ad be ef]; + davicom,no-eeprom; + }; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 6931c4348d2..2fe74e6ec20 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -18,6 +18,7 @@ chrp Common Hardware Reference Platform cirrus Cirrus Logic, Inc. cortina Cortina Systems, Inc. dallas Maxim Integrated Products (formerly Dallas Semiconductor) +davicom DAVICOM Semiconductor, Inc. denx Denx Software Engineering emmicro EM Microelectronic epson Seiko Epson Corp. diff --git a/drivers/net/ethernet/davicom/dm9000.c b/drivers/net/ethernet/davicom/dm9000.c index a2408c84bbd..dd243a1b03e 100644 --- a/drivers/net/ethernet/davicom/dm9000.c +++ b/drivers/net/ethernet/davicom/dm9000.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include #include @@ -1351,6 +1353,31 @@ static const struct net_device_ops dm9000_netdev_ops = { #endif }; +static struct dm9000_plat_data *dm9000_parse_dt(struct device *dev) +{ + struct dm9000_plat_data *pdata; + struct device_node *np = dev->of_node; + const void *mac_addr; + + if (!IS_ENABLED(CONFIG_OF) || !np) + return NULL; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + if (of_find_property(np, "davicom,ext-phy", NULL)) + pdata->flags |= DM9000_PLATF_EXT_PHY; + if (of_find_property(np, "davicom,no-eeprom", NULL)) + pdata->flags |= DM9000_PLATF_NO_EEPROM; + + mac_addr = of_get_mac_address(np); + if (mac_addr) + memcpy(pdata->dev_addr, mac_addr, sizeof(pdata->dev_addr)); + + return pdata; +} + /* * Search DM9000 board, allocate space and register it */ @@ -1366,6 +1393,12 @@ dm9000_probe(struct platform_device *pdev) int i; u32 id_val; + if (!pdata) { + pdata = dm9000_parse_dt(&pdev->dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + /* Init network device */ ndev = alloc_etherdev(sizeof(struct board_info)); if (!ndev) @@ -1676,11 +1709,20 @@ dm9000_drv_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id dm9000_of_matches[] = { + { .compatible = "davicom,dm9000", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dm9000_of_matches); +#endif + static struct platform_driver dm9000_driver = { .driver = { .name = "dm9000", .owner = THIS_MODULE, .pm = &dm9000_drv_pm_ops, + .of_match_table = of_match_ptr(dm9000_of_matches), }, .probe = dm9000_probe, .remove = dm9000_drv_remove, -- cgit v1.2.3-70-g09d2 From 87227b8b2d4d556a6924ad9af87450fdc3fcd7e3 Mon Sep 17 00:00:00 2001 From: Jean-Christophe PLAGNIOL-VILLARD Date: Thu, 23 May 2013 23:01:22 +0000 Subject: net: micrel : ks8851-ml: add dt support Signed-off-by: Jean-Christophe PLAGNIOL-VILLARD Cc: netdev@vger.kernel.org Signed-off-by: David S. Miller --- .../devicetree/bindings/net/micrel-ks8851.txt | 9 ++++++ drivers/net/ethernet/micrel/ks8851_mll.c | 33 +++++++++++++++++----- 2 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 Documentation/devicetree/bindings/net/micrel-ks8851.txt (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/micrel-ks8851.txt b/Documentation/devicetree/bindings/net/micrel-ks8851.txt new file mode 100644 index 00000000000..11ace3c3d80 --- /dev/null +++ b/Documentation/devicetree/bindings/net/micrel-ks8851.txt @@ -0,0 +1,9 @@ +Micrel KS8851 Ethernet mac + +Required properties: +- compatible = "micrel,ks8851-ml" of parallel interface +- reg : 2 physical address and size of registers for data and command +- interrupts : interrupt connection + +Optional properties: +- local-mac-address : Ethernet mac address to use diff --git a/drivers/net/ethernet/micrel/ks8851_mll.c b/drivers/net/ethernet/micrel/ks8851_mll.c index e9b1a830d58..ac20098b542 100644 --- a/drivers/net/ethernet/micrel/ks8851_mll.c +++ b/drivers/net/ethernet/micrel/ks8851_mll.c @@ -35,6 +35,9 @@ #include #include #include +#include +#include +#include #define DRV_NAME "ks8851_mll" @@ -1524,6 +1527,13 @@ static int ks_hw_init(struct ks_net *ks) return true; } +#if defined(CONFIG_OF) +static const struct of_device_id ks8851_ml_dt_ids[] = { + { .compatible = "micrel,ks8851-mll" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ks8851_ml_dt_ids); +#endif static int ks8851_probe(struct platform_device *pdev) { @@ -1532,7 +1542,7 @@ static int ks8851_probe(struct platform_device *pdev) struct net_device *netdev; struct ks_net *ks; u16 id, data; - struct ks8851_mll_platform_data *pdata; + const char *mac; io_d = platform_get_resource(pdev, IORESOURCE_MEM, 0); io_c = platform_get_resource(pdev, IORESOURCE_MEM, 1); @@ -1619,13 +1629,21 @@ static int ks8851_probe(struct platform_device *pdev) ks_wrreg16(ks, KS_OBCR, data | OBCR_ODS_16MA); /* overwriting the default MAC address */ - pdata = pdev->dev.platform_data; - if (!pdata) { - netdev_err(netdev, "No platform data\n"); - err = -ENODEV; - goto err_pdata; + if (pdev->dev.of_node) { + mac = of_get_mac_address(pdev->dev.of_node); + if (mac) + memcpy(ks->mac_addr, mac, ETH_ALEN); + } else { + struct ks8851_mll_platform_data *pdata; + + pdata = pdev->dev.platform_data; + if (!pdata) { + netdev_err(netdev, "No platform data\n"); + err = -ENODEV; + goto err_pdata; + } + memcpy(ks->mac_addr, pdata->mac_addr, ETH_ALEN); } - memcpy(ks->mac_addr, pdata->mac_addr, 6); if (!is_valid_ether_addr(ks->mac_addr)) { /* Use random MAC address if none passed */ eth_random_addr(ks->mac_addr); @@ -1679,6 +1697,7 @@ static struct platform_driver ks8851_platform_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ks8851_ml_dt_ids), }, .probe = ks8851_probe, .remove = ks8851_remove, -- cgit v1.2.3-70-g09d2 From 76723bca2802eb80990a5fefa179662e2e561d66 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 29 May 2013 09:32:48 +0000 Subject: net: mv643xx_eth: add DT parsing support This adds device tree parsing support for the shared driver of mv643xx_eth. As the bindings are slightly different from current PPC bindings new binding documentation is also added. Following PPC-style device setup, the shared driver now also adds port platform_devices and sets up port platform_data. Signed-off-by: Sebastian Hesselbarth Signed-off-by: David S. Miller --- .../devicetree/bindings/net/marvell-orion-net.txt | 85 ++++++++++++ drivers/net/ethernet/marvell/mv643xx_eth.c | 153 ++++++++++++++++++++- 2 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 Documentation/devicetree/bindings/net/marvell-orion-net.txt (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/marvell-orion-net.txt b/Documentation/devicetree/bindings/net/marvell-orion-net.txt new file mode 100644 index 00000000000..a73b79f227e --- /dev/null +++ b/Documentation/devicetree/bindings/net/marvell-orion-net.txt @@ -0,0 +1,85 @@ +Marvell Orion/Discovery ethernet controller +============================================= + +The Marvell Discovery ethernet controller can be found on Marvell Orion SoCs +(Kirkwood, Dove, Orion5x, and Discovery Innovation) and as part of Marvell +Discovery system controller chips (mv64[345]60). + +The Discovery ethernet controller is described with two levels of nodes. The +first level describes the ethernet controller itself and the second level +describes up to 3 ethernet port nodes within that controller. The reason for +the multiple levels is that the port registers are interleaved within a single +set of controller registers. Each port node describes port-specific properties. + +Note: The above separation is only true for Discovery system controllers. +For Orion SoCs we stick to the separation, although there each controller has +only one port associated. Multiple ports are implemented as multiple single-port +controllers. As Kirkwood has some issues with proper initialization after reset, +an extra compatible string is added for it. + +* Ethernet controller node + +Required controller properties: + - #address-cells: shall be 1. + - #size-cells: shall be 0. + - compatible: shall be one of "marvell,orion-eth", "marvell,kirkwood-eth". + - reg: address and length of the controller registers. + +Optional controller properties: + - clocks: phandle reference to the controller clock. + - marvell,tx-checksum-limit: max tx packet size for hardware checksum. + +* Ethernet port node + +Required port properties: + - device_type: shall be "network". + - compatible: shall be one of "marvell,orion-eth-port", + "marvell,kirkwood-eth-port". + - reg: port number relative to ethernet controller, shall be 0, 1, or 2. + - interrupts: port interrupt. + - local-mac-address: 6 bytes MAC address. + +Optional port properties: + - marvell,tx-queue-size: size of the transmit ring buffer. + - marvell,tx-sram-addr: address of transmit descriptor buffer located in SRAM. + - marvell,tx-sram-size: size of transmit descriptor buffer located in SRAM. + - marvell,rx-queue-size: size of the receive ring buffer. + - marvell,rx-sram-addr: address of receive descriptor buffer located in SRAM. + - marvell,rx-sram-size: size of receive descriptor buffer located in SRAM. + +and + + - phy-handle: phandle reference to ethernet PHY. + +or + + - speed: port speed if no PHY connected. + - duplex: port mode if no PHY connected. + +* Node example: + +mdio-bus { + ... + ethphy: ethernet-phy@8 { + device_type = "ethernet-phy"; + ... + }; +}; + +eth: ethernet-controller@72000 { + compatible = "marvell,orion-eth"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x72000 0x2000>; + clocks = <&gate_clk 2>; + marvell,tx-checksum-limit = <1600>; + + ethernet@0 { + device_type = "network"; + compatible = "marvell,orion-eth-port"; + reg = <0>; + interrupts = <29>; + phy-handle = <ðphy>; + local-mac-address = [00 00 00 00 00 00]; + }; +}; diff --git a/drivers/net/ethernet/marvell/mv643xx_eth.c b/drivers/net/ethernet/marvell/mv643xx_eth.c index 1b31d752ff9..23ea7b6e23f 100644 --- a/drivers/net/ethernet/marvell/mv643xx_eth.c +++ b/drivers/net/ethernet/marvell/mv643xx_eth.c @@ -60,6 +60,9 @@ #include #include #include +#include +#include +#include #include static char mv643xx_eth_driver_name[] = "mv643xx_eth"; @@ -2453,13 +2456,148 @@ static void infer_hw_params(struct mv643xx_eth_shared_private *msp) } } +#if defined(CONFIG_OF) +static const struct of_device_id mv643xx_eth_shared_ids[] = { + { .compatible = "marvell,orion-eth", }, + { .compatible = "marvell,kirkwood-eth", }, + { } +}; +MODULE_DEVICE_TABLE(of, mv643xx_eth_shared_ids); +#endif + +#if defined(CONFIG_OF) && !defined(CONFIG_MV64X60) +#define mv643xx_eth_property(_np, _name, _v) \ + do { \ + u32 tmp; \ + if (!of_property_read_u32(_np, "marvell," _name, &tmp)) \ + _v = tmp; \ + } while (0) + +static struct platform_device *port_platdev[3]; + +static int mv643xx_eth_shared_of_add_port(struct platform_device *pdev, + struct device_node *pnp) +{ + struct platform_device *ppdev; + struct mv643xx_eth_platform_data ppd; + struct resource res; + const char *mac_addr; + int ret; + + memset(&ppd, 0, sizeof(ppd)); + ppd.shared = pdev; + + memset(&res, 0, sizeof(res)); + if (!of_irq_to_resource(pnp, 0, &res)) { + dev_err(&pdev->dev, "missing interrupt on %s\n", pnp->name); + return -EINVAL; + } + + if (of_property_read_u32(pnp, "reg", &ppd.port_number)) { + dev_err(&pdev->dev, "missing reg property on %s\n", pnp->name); + return -EINVAL; + } + + if (ppd.port_number >= 3) { + dev_err(&pdev->dev, "invalid reg property on %s\n", pnp->name); + return -EINVAL; + } + + mac_addr = of_get_mac_address(pnp); + if (mac_addr) + memcpy(ppd.mac_addr, mac_addr, 6); + + mv643xx_eth_property(pnp, "tx-queue-size", ppd.tx_queue_size); + mv643xx_eth_property(pnp, "tx-sram-addr", ppd.tx_sram_addr); + mv643xx_eth_property(pnp, "tx-sram-size", ppd.tx_sram_size); + mv643xx_eth_property(pnp, "rx-queue-size", ppd.rx_queue_size); + mv643xx_eth_property(pnp, "rx-sram-addr", ppd.rx_sram_addr); + mv643xx_eth_property(pnp, "rx-sram-size", ppd.rx_sram_size); + + ppd.phy_node = of_parse_phandle(pnp, "phy-handle", 0); + if (!ppd.phy_node) { + ppd.phy_addr = MV643XX_ETH_PHY_NONE; + of_property_read_u32(pnp, "speed", &ppd.speed); + of_property_read_u32(pnp, "duplex", &ppd.duplex); + } + + ppdev = platform_device_alloc(MV643XX_ETH_NAME, ppd.port_number); + if (!ppdev) + return -ENOMEM; + ppdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + ret = platform_device_add_resources(ppdev, &res, 1); + if (ret) + goto port_err; + + ret = platform_device_add_data(ppdev, &ppd, sizeof(ppd)); + if (ret) + goto port_err; + + ret = platform_device_add(ppdev); + if (ret) + goto port_err; + + port_platdev[ppd.port_number] = ppdev; + + return 0; + +port_err: + platform_device_put(ppdev); + return ret; +} + +static int mv643xx_eth_shared_of_probe(struct platform_device *pdev) +{ + struct mv643xx_eth_shared_platform_data *pd; + struct device_node *pnp, *np = pdev->dev.of_node; + int ret; + + /* bail out if not registered from DT */ + if (!np) + return 0; + + pd = devm_kzalloc(&pdev->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + pdev->dev.platform_data = pd; + + mv643xx_eth_property(np, "tx-checksum-limit", pd->tx_csum_limit); + + for_each_available_child_of_node(np, pnp) { + ret = mv643xx_eth_shared_of_add_port(pdev, pnp); + if (ret) + return ret; + } + return 0; +} + +static void mv643xx_eth_shared_of_remove(void) +{ + int n; + + for (n = 0; n < 3; n++) { + platform_device_del(port_platdev[n]); + port_platdev[n] = NULL; + } +} +#else +static int mv643xx_eth_shared_of_probe(struct platform_device *pdev) +{ + return 0 +} + +#define mv643xx_eth_shared_of_remove() +#endif + static int mv643xx_eth_shared_probe(struct platform_device *pdev) { static int mv643xx_eth_version_printed; - struct mv643xx_eth_shared_platform_data *pd = pdev->dev.platform_data; + struct mv643xx_eth_shared_platform_data *pd; struct mv643xx_eth_shared_private *msp; const struct mbus_dram_target_info *dram; struct resource *res; + int ret; if (!mv643xx_eth_version_printed++) pr_notice("MV-643xx 10/100/1000 ethernet driver version %s\n", @@ -2472,6 +2610,7 @@ static int mv643xx_eth_shared_probe(struct platform_device *pdev) msp = devm_kzalloc(&pdev->dev, sizeof(*msp), GFP_KERNEL); if (msp == NULL) return -ENOMEM; + platform_set_drvdata(pdev, msp); msp->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (msp->base == NULL) @@ -2488,12 +2627,15 @@ static int mv643xx_eth_shared_probe(struct platform_device *pdev) if (dram) mv643xx_eth_conf_mbus_windows(msp, dram); + ret = mv643xx_eth_shared_of_probe(pdev); + if (ret) + return ret; + pd = pdev->dev.platform_data; + msp->tx_csum_limit = (pd != NULL && pd->tx_csum_limit) ? pd->tx_csum_limit : 9 * 1024; infer_hw_params(msp); - platform_set_drvdata(pdev, msp); - return 0; } @@ -2501,9 +2643,9 @@ static int mv643xx_eth_shared_remove(struct platform_device *pdev) { struct mv643xx_eth_shared_private *msp = platform_get_drvdata(pdev); + mv643xx_eth_shared_of_remove(); if (!IS_ERR(msp->clk)) clk_disable_unprepare(msp->clk); - return 0; } @@ -2513,6 +2655,7 @@ static struct platform_driver mv643xx_eth_shared_driver = { .driver = { .name = MV643XX_ETH_SHARED_NAME, .owner = THIS_MODULE, + .of_match_table = of_match_ptr(mv643xx_eth_shared_ids), }, }; @@ -2721,6 +2864,8 @@ static int mv643xx_eth_probe(struct platform_device *pdev) if (!IS_ERR(mp->clk)) { clk_prepare_enable(mp->clk); mp->t_clk = clk_get_rate(mp->clk); + } else if (!IS_ERR(mp->shared->clk)) { + mp->t_clk = clk_get_rate(mp->shared->clk); } set_params(mp, pd); -- cgit v1.2.3-70-g09d2 From 492205050d77bcc4f85f6dc0da6b6fdbca1d6ff7 Mon Sep 17 00:00:00 2001 From: Stefan Roese Date: Thu, 30 May 2013 03:49:20 +0000 Subject: net: Add EMAC ethernet driver found on Allwinner A10 SoC's The Allwinner A10 has an ethernet controller that seem to be developped internally by them. The exact feature set of this controller is unknown, since there is no public documentation for this IP, and this driver is mostly the one published by Allwinner that has been heavily cleaned up. Signed-off-by: Stefan Roese Signed-off-by: Maxime Ripard Tested-by: Richard Genoud Signed-off-by: David S. Miller --- .../bindings/net/allwinner,sun4i-emac.txt | 22 + drivers/net/ethernet/Kconfig | 1 + drivers/net/ethernet/Makefile | 1 + drivers/net/ethernet/allwinner/Kconfig | 36 + drivers/net/ethernet/allwinner/Makefile | 5 + drivers/net/ethernet/allwinner/sun4i-emac.c | 960 +++++++++++++++++++++ drivers/net/ethernet/allwinner/sun4i-emac.h | 108 +++ 7 files changed, 1133 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/allwinner,sun4i-emac.txt create mode 100644 drivers/net/ethernet/allwinner/Kconfig create mode 100644 drivers/net/ethernet/allwinner/Makefile create mode 100644 drivers/net/ethernet/allwinner/sun4i-emac.c create mode 100644 drivers/net/ethernet/allwinner/sun4i-emac.h (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/allwinner,sun4i-emac.txt b/Documentation/devicetree/bindings/net/allwinner,sun4i-emac.txt new file mode 100644 index 00000000000..b90bfcd138f --- /dev/null +++ b/Documentation/devicetree/bindings/net/allwinner,sun4i-emac.txt @@ -0,0 +1,22 @@ +* Allwinner EMAC ethernet controller + +Required properties: +- compatible: should be "allwinner,sun4i-emac". +- reg: address and length of the register set for the device. +- interrupts: interrupt for the device +- phy: A phandle to a phy node defining the PHY address (as the reg + property, a single integer). +- clocks: A phandle to the reference clock for this device + +Optional properties: +- (local-)mac-address: mac address to be used by this driver + +Example: + +emac: ethernet@01c0b000 { + compatible = "allwinner,sun4i-emac"; + reg = <0x01c0b000 0x1000>; + interrupts = <55>; + clocks = <&ahb_gates 17>; + phy = <&phy0>; +}; diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index ed956e08d38..18fd6fbeb10 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -20,6 +20,7 @@ config SUNGEM_PHY source "drivers/net/ethernet/3com/Kconfig" source "drivers/net/ethernet/adaptec/Kconfig" source "drivers/net/ethernet/aeroflex/Kconfig" +source "drivers/net/ethernet/allwinner/Kconfig" source "drivers/net/ethernet/alteon/Kconfig" source "drivers/net/ethernet/amd/Kconfig" source "drivers/net/ethernet/apple/Kconfig" diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index 8268d85f944..009da27b6e2 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_NET_VENDOR_3COM) += 3com/ obj-$(CONFIG_NET_VENDOR_8390) += 8390/ obj-$(CONFIG_NET_VENDOR_ADAPTEC) += adaptec/ obj-$(CONFIG_GRETH) += aeroflex/ +obj-$(CONFIG_NET_VENDOR_ALLWINNER) += allwinner/ obj-$(CONFIG_NET_VENDOR_ALTEON) += alteon/ obj-$(CONFIG_NET_VENDOR_AMD) += amd/ obj-$(CONFIG_NET_VENDOR_APPLE) += apple/ diff --git a/drivers/net/ethernet/allwinner/Kconfig b/drivers/net/ethernet/allwinner/Kconfig new file mode 100644 index 00000000000..66d35324f31 --- /dev/null +++ b/drivers/net/ethernet/allwinner/Kconfig @@ -0,0 +1,36 @@ +# +# Allwinner device configuration +# + +config NET_VENDOR_ALLWINNER + bool "Allwinner devices" + default y + depends on ARCH_SUNXI + ---help--- + If you have a network (Ethernet) card belonging to this + class, say Y and read the Ethernet-HOWTO, available from + . + + Note that the answer to this question doesn't directly + affect the kernel: saying N will just cause the configurator + to skip all the questions about Allwinner cards. If you say Y, + you will be asked for your specific card in the following + questions. + +if NET_VENDOR_ALLWINNER + +config SUN4I_EMAC + tristate "Allwinner A10 EMAC support" + depends on ARCH_SUNXI + depends on OF + select CRC32 + select NET_CORE + select MII + select PHYLIB + ---help--- + Support for Allwinner A10 EMAC ethernet driver. + + To compile this driver as a module, choose M here. The module + will be called sun4i-emac. + +endif # NET_VENDOR_ALLWINNER diff --git a/drivers/net/ethernet/allwinner/Makefile b/drivers/net/ethernet/allwinner/Makefile new file mode 100644 index 00000000000..03129f79651 --- /dev/null +++ b/drivers/net/ethernet/allwinner/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the Allwinner device drivers. +# + +obj-$(CONFIG_SUN4I_EMAC) += sun4i-emac.o diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c new file mode 100644 index 00000000000..b411344e719 --- /dev/null +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -0,0 +1,960 @@ +/* + * Allwinner EMAC Fast Ethernet driver for Linux. + * + * Copyright 2012-2013 Stefan Roese + * Copyright 2013 Maxime Ripard + * + * Based on the Linux driver provided by Allwinner: + * Copyright (C) 1997 Sten Wang + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sun4i-emac.h" + +#define DRV_NAME "sun4i-emac" +#define DRV_VERSION "1.02" + +#define EMAC_MAX_FRAME_LEN 0x0600 + +/* Transmit timeout, default 5 seconds. */ +static int watchdog = 5000; +module_param(watchdog, int, 0400); +MODULE_PARM_DESC(watchdog, "transmit timeout in milliseconds"); + +/* EMAC register address locking. + * + * The EMAC uses an address register to control where data written + * to the data register goes. This means that the address register + * must be preserved over interrupts or similar calls. + * + * During interrupt and other critical calls, a spinlock is used to + * protect the system, but the calls themselves save the address + * in the address register in case they are interrupting another + * access to the device. + * + * For general accesses a lock is provided so that calls which are + * allowed to sleep are serialised so that the address register does + * not need to be saved. This lock also serves to serialise access + * to the EEPROM and PHY access registers which are shared between + * these two devices. + */ + +/* The driver supports the original EMACE, and now the two newer + * devices, EMACA and EMACB. + */ + +struct emac_board_info { + struct clk *clk; + struct device *dev; + struct platform_device *pdev; + spinlock_t lock; + void __iomem *membase; + u32 msg_enable; + struct net_device *ndev; + struct sk_buff *skb_last; + u16 tx_fifo_stat; + + int emacrx_completed_flag; + + struct phy_device *phy_dev; + struct device_node *phy_node; + unsigned int link; + unsigned int speed; + unsigned int duplex; + + phy_interface_t phy_interface; +}; + +static void emac_update_speed(struct net_device *dev) +{ + struct emac_board_info *db = netdev_priv(dev); + unsigned int reg_val; + + /* set EMAC SPEED, depend on PHY */ + reg_val = readl(db->membase + EMAC_MAC_SUPP_REG); + reg_val &= ~(0x1 << 8); + if (db->speed == SPEED_100) + reg_val |= 1 << 8; + writel(reg_val, db->membase + EMAC_MAC_SUPP_REG); +} + +static void emac_update_duplex(struct net_device *dev) +{ + struct emac_board_info *db = netdev_priv(dev); + unsigned int reg_val; + + /* set duplex depend on phy */ + reg_val = readl(db->membase + EMAC_MAC_CTL1_REG); + reg_val &= ~EMAC_MAC_CTL1_DUPLEX_EN; + if (db->duplex) + reg_val |= EMAC_MAC_CTL1_DUPLEX_EN; + writel(reg_val, db->membase + EMAC_MAC_CTL1_REG); +} + +static void emac_handle_link_change(struct net_device *dev) +{ + struct emac_board_info *db = netdev_priv(dev); + struct phy_device *phydev = db->phy_dev; + unsigned long flags; + int status_change = 0; + + if (phydev->link) { + if (db->speed != phydev->speed) { + spin_lock_irqsave(&db->lock, flags); + db->speed = phydev->speed; + emac_update_speed(dev); + spin_unlock_irqrestore(&db->lock, flags); + status_change = 1; + } + + if (db->duplex != phydev->duplex) { + spin_lock_irqsave(&db->lock, flags); + db->duplex = phydev->duplex; + emac_update_duplex(dev); + spin_unlock_irqrestore(&db->lock, flags); + status_change = 1; + } + } + + if (phydev->link != db->link) { + if (!phydev->link) { + db->speed = 0; + db->duplex = -1; + } + db->link = phydev->link; + + status_change = 1; + } + + if (status_change) + phy_print_status(phydev); +} + +static int emac_mdio_probe(struct net_device *dev) +{ + struct emac_board_info *db = netdev_priv(dev); + + /* to-do: PHY interrupts are currently not supported */ + + /* attach the mac to the phy */ + db->phy_dev = of_phy_connect(db->ndev, db->phy_node, + &emac_handle_link_change, 0, + db->phy_interface); + if (!db->phy_dev) { + netdev_err(db->ndev, "could not find the PHY\n"); + return -ENODEV; + } + + /* mask with MAC supported features */ + db->phy_dev->supported &= PHY_BASIC_FEATURES; + db->phy_dev->advertising = db->phy_dev->supported; + + db->link = 0; + db->speed = 0; + db->duplex = -1; + + return 0; +} + +static void emac_mdio_remove(struct net_device *dev) +{ + struct emac_board_info *db = netdev_priv(dev); + + phy_disconnect(db->phy_dev); + db->phy_dev = NULL; +} + +static void emac_reset(struct emac_board_info *db) +{ + dev_dbg(db->dev, "resetting device\n"); + + /* RESET device */ + writel(0, db->membase + EMAC_CTL_REG); + udelay(200); + writel(EMAC_CTL_RESET, db->membase + EMAC_CTL_REG); + udelay(200); +} + +static void emac_outblk_32bit(void __iomem *reg, void *data, int count) +{ + writesl(reg, data, round_up(count, 4) / 4); +} + +static void emac_inblk_32bit(void __iomem *reg, void *data, int count) +{ + readsl(reg, data, round_up(count, 4) / 4); +} + +static int emac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct emac_board_info *dm = netdev_priv(dev); + struct phy_device *phydev = dm->phy_dev; + + if (!netif_running(dev)) + return -EINVAL; + + if (!phydev) + return -ENODEV; + + return phy_mii_ioctl(phydev, rq, cmd); +} + +/* ethtool ops */ +static void emac_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, DRV_NAME, sizeof(DRV_NAME)); + strlcpy(info->version, DRV_VERSION, sizeof(DRV_VERSION)); + strlcpy(info->bus_info, dev_name(&dev->dev), sizeof(info->bus_info)); +} + +static int emac_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct emac_board_info *dm = netdev_priv(dev); + struct phy_device *phydev = dm->phy_dev; + + if (!phydev) + return -ENODEV; + + return phy_ethtool_gset(phydev, cmd); +} + +static int emac_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct emac_board_info *dm = netdev_priv(dev); + struct phy_device *phydev = dm->phy_dev; + + if (!phydev) + return -ENODEV; + + return phy_ethtool_sset(phydev, cmd); +} + +static const struct ethtool_ops emac_ethtool_ops = { + .get_drvinfo = emac_get_drvinfo, + .get_settings = emac_get_settings, + .set_settings = emac_set_settings, + .get_link = ethtool_op_get_link, +}; + +unsigned int emac_setup(struct net_device *ndev) +{ + struct emac_board_info *db = netdev_priv(ndev); + unsigned int reg_val; + + /* set up TX */ + reg_val = readl(db->membase + EMAC_TX_MODE_REG); + + writel(reg_val | EMAC_TX_MODE_ABORTED_FRAME_EN, + db->membase + EMAC_TX_MODE_REG); + + /* set up RX */ + reg_val = readl(db->membase + EMAC_RX_CTL_REG); + + writel(reg_val | EMAC_RX_CTL_PASS_LEN_OOR_EN | + EMAC_RX_CTL_ACCEPT_UNICAST_EN | EMAC_RX_CTL_DA_FILTER_EN | + EMAC_RX_CTL_ACCEPT_MULTICAST_EN | + EMAC_RX_CTL_ACCEPT_BROADCAST_EN, + db->membase + EMAC_RX_CTL_REG); + + /* set MAC */ + /* set MAC CTL0 */ + reg_val = readl(db->membase + EMAC_MAC_CTL0_REG); + writel(reg_val | EMAC_MAC_CTL0_RX_FLOW_CTL_EN | + EMAC_MAC_CTL0_TX_FLOW_CTL_EN, + db->membase + EMAC_MAC_CTL0_REG); + + /* set MAC CTL1 */ + reg_val = readl(db->membase + EMAC_MAC_CTL1_REG); + reg_val |= EMAC_MAC_CTL1_LEN_CHECK_EN; + reg_val |= EMAC_MAC_CTL1_CRC_EN; + reg_val |= EMAC_MAC_CTL1_PAD_EN; + writel(reg_val, db->membase + EMAC_MAC_CTL1_REG); + + /* set up IPGT */ + writel(EMAC_MAC_IPGT_FULL_DUPLEX, db->membase + EMAC_MAC_IPGT_REG); + + /* set up IPGR */ + writel((EMAC_MAC_IPGR_IPG1 << 8) | EMAC_MAC_IPGR_IPG2, + db->membase + EMAC_MAC_IPGR_REG); + + /* set up Collison window */ + writel((EMAC_MAC_CLRT_COLLISION_WINDOW << 8) | EMAC_MAC_CLRT_RM, + db->membase + EMAC_MAC_CLRT_REG); + + /* set up Max Frame Length */ + writel(EMAC_MAX_FRAME_LEN, + db->membase + EMAC_MAC_MAXF_REG); + + return 0; +} + +unsigned int emac_powerup(struct net_device *ndev) +{ + struct emac_board_info *db = netdev_priv(ndev); + unsigned int reg_val; + + /* initial EMAC */ + /* flush RX FIFO */ + reg_val = readl(db->membase + EMAC_RX_CTL_REG); + reg_val |= 0x8; + writel(reg_val, db->membase + EMAC_RX_CTL_REG); + udelay(1); + + /* initial MAC */ + /* soft reset MAC */ + reg_val = readl(db->membase + EMAC_MAC_CTL0_REG); + reg_val &= ~EMAC_MAC_CTL0_SOFT_RESET; + writel(reg_val, db->membase + EMAC_MAC_CTL0_REG); + + /* set MII clock */ + reg_val = readl(db->membase + EMAC_MAC_MCFG_REG); + reg_val &= (~(0xf << 2)); + reg_val |= (0xD << 2); + writel(reg_val, db->membase + EMAC_MAC_MCFG_REG); + + /* clear RX counter */ + writel(0x0, db->membase + EMAC_RX_FBC_REG); + + /* disable all interrupt and clear interrupt status */ + writel(0, db->membase + EMAC_INT_CTL_REG); + reg_val = readl(db->membase + EMAC_INT_STA_REG); + writel(reg_val, db->membase + EMAC_INT_STA_REG); + + udelay(1); + + /* set up EMAC */ + emac_setup(ndev); + + /* set mac_address to chip */ + writel(ndev->dev_addr[0] << 16 | ndev->dev_addr[1] << 8 | ndev-> + dev_addr[2], db->membase + EMAC_MAC_A1_REG); + writel(ndev->dev_addr[3] << 16 | ndev->dev_addr[4] << 8 | ndev-> + dev_addr[5], db->membase + EMAC_MAC_A0_REG); + + mdelay(1); + + return 0; +} + +static int emac_set_mac_address(struct net_device *dev, void *p) +{ + struct sockaddr *addr = p; + struct emac_board_info *db = netdev_priv(dev); + + if (netif_running(dev)) + return -EBUSY; + + memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN); + + writel(dev->dev_addr[0] << 16 | dev->dev_addr[1] << 8 | dev-> + dev_addr[2], db->membase + EMAC_MAC_A1_REG); + writel(dev->dev_addr[3] << 16 | dev->dev_addr[4] << 8 | dev-> + dev_addr[5], db->membase + EMAC_MAC_A0_REG); + + return 0; +} + +/* Initialize emac board */ +static void emac_init_device(struct net_device *dev) +{ + struct emac_board_info *db = netdev_priv(dev); + unsigned long flags; + unsigned int reg_val; + + spin_lock_irqsave(&db->lock, flags); + + emac_update_speed(dev); + emac_update_duplex(dev); + + /* enable RX/TX */ + reg_val = readl(db->membase + EMAC_CTL_REG); + writel(reg_val | EMAC_CTL_RESET | EMAC_CTL_TX_EN | EMAC_CTL_RX_EN, + db->membase + EMAC_CTL_REG); + + /* enable RX/TX0/RX Hlevel interrup */ + reg_val = readl(db->membase + EMAC_INT_CTL_REG); + reg_val |= (0xf << 0) | (0x01 << 8); + writel(reg_val, db->membase + EMAC_INT_CTL_REG); + + spin_unlock_irqrestore(&db->lock, flags); +} + +/* Our watchdog timed out. Called by the networking layer */ +static void emac_timeout(struct net_device *dev) +{ + struct emac_board_info *db = netdev_priv(dev); + unsigned long flags; + + if (netif_msg_timer(db)) + dev_err(db->dev, "tx time out.\n"); + + /* Save previous register address */ + spin_lock_irqsave(&db->lock, flags); + + netif_stop_queue(dev); + emac_reset(db); + emac_init_device(dev); + /* We can accept TX packets again */ + dev->trans_start = jiffies; + netif_wake_queue(dev); + + /* Restore previous register address */ + spin_unlock_irqrestore(&db->lock, flags); +} + +/* Hardware start transmission. + * Send a packet to media from the upper layer. + */ +static int emac_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct emac_board_info *db = netdev_priv(dev); + unsigned long channel; + unsigned long flags; + + channel = db->tx_fifo_stat & 3; + if (channel == 3) + return 1; + + channel = (channel == 1 ? 1 : 0); + + spin_lock_irqsave(&db->lock, flags); + + writel(channel, db->membase + EMAC_TX_INS_REG); + + emac_outblk_32bit(db->membase + EMAC_TX_IO_DATA_REG, + skb->data, skb->len); + dev->stats.tx_bytes += skb->len; + + db->tx_fifo_stat |= 1 << channel; + /* TX control: First packet immediately send, second packet queue */ + if (channel == 0) { + /* set TX len */ + writel(skb->len, db->membase + EMAC_TX_PL0_REG); + /* start translate from fifo to phy */ + writel(readl(db->membase + EMAC_TX_CTL0_REG) | 1, + db->membase + EMAC_TX_CTL0_REG); + + /* save the time stamp */ + dev->trans_start = jiffies; + } else if (channel == 1) { + /* set TX len */ + writel(skb->len, db->membase + EMAC_TX_PL1_REG); + /* start translate from fifo to phy */ + writel(readl(db->membase + EMAC_TX_CTL1_REG) | 1, + db->membase + EMAC_TX_CTL1_REG); + + /* save the time stamp */ + dev->trans_start = jiffies; + } + + if ((db->tx_fifo_stat & 3) == 3) { + /* Second packet */ + netif_stop_queue(dev); + } + + spin_unlock_irqrestore(&db->lock, flags); + + /* free this SKB */ + dev_kfree_skb(skb); + + return NETDEV_TX_OK; +} + +/* EMAC interrupt handler + * receive the packet to upper layer, free the transmitted packet + */ +static void emac_tx_done(struct net_device *dev, struct emac_board_info *db, + unsigned int tx_status) +{ + /* One packet sent complete */ + db->tx_fifo_stat &= ~(tx_status & 3); + if (3 == (tx_status & 3)) + dev->stats.tx_packets += 2; + else + dev->stats.tx_packets++; + + if (netif_msg_tx_done(db)) + dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status); + + netif_wake_queue(dev); +} + +/* Received a packet and pass to upper layer + */ +static void emac_rx(struct net_device *dev) +{ + struct emac_board_info *db = netdev_priv(dev); + struct sk_buff *skb; + u8 *rdptr; + bool good_packet; + static int rxlen_last; + unsigned int reg_val; + u32 rxhdr, rxstatus, rxcount, rxlen; + + /* Check packet ready or not */ + while (1) { + /* race warning: the first packet might arrive with + * the interrupts disabled, but the second will fix + * it + */ + rxcount = readl(db->membase + EMAC_RX_FBC_REG); + + if (netif_msg_rx_status(db)) + dev_dbg(db->dev, "RXCount: %x\n", rxcount); + + if ((db->skb_last != NULL) && (rxlen_last > 0)) { + dev->stats.rx_bytes += rxlen_last; + + /* Pass to upper layer */ + db->skb_last->protocol = eth_type_trans(db->skb_last, + dev); + netif_rx(db->skb_last); + dev->stats.rx_packets++; + db->skb_last = NULL; + rxlen_last = 0; + + reg_val = readl(db->membase + EMAC_RX_CTL_REG); + reg_val &= ~EMAC_RX_CTL_DMA_EN; + writel(reg_val, db->membase + EMAC_RX_CTL_REG); + } + + if (!rxcount) { + db->emacrx_completed_flag = 1; + reg_val = readl(db->membase + EMAC_INT_CTL_REG); + reg_val |= (0xf << 0) | (0x01 << 8); + writel(reg_val, db->membase + EMAC_INT_CTL_REG); + + /* had one stuck? */ + rxcount = readl(db->membase + EMAC_RX_FBC_REG); + if (!rxcount) + return; + } + + reg_val = readl(db->membase + EMAC_RX_IO_DATA_REG); + if (netif_msg_rx_status(db)) + dev_dbg(db->dev, "receive header: %x\n", reg_val); + if (reg_val != EMAC_UNDOCUMENTED_MAGIC) { + /* disable RX */ + reg_val = readl(db->membase + EMAC_CTL_REG); + writel(reg_val & ~EMAC_CTL_RX_EN, + db->membase + EMAC_CTL_REG); + + /* Flush RX FIFO */ + reg_val = readl(db->membase + EMAC_RX_CTL_REG); + writel(reg_val | (1 << 3), + db->membase + EMAC_RX_CTL_REG); + + do { + reg_val = readl(db->membase + EMAC_RX_CTL_REG); + } while (reg_val & (1 << 3)); + + /* enable RX */ + reg_val = readl(db->membase + EMAC_CTL_REG); + writel(reg_val | EMAC_CTL_RX_EN, + db->membase + EMAC_CTL_REG); + reg_val = readl(db->membase + EMAC_INT_CTL_REG); + reg_val |= (0xf << 0) | (0x01 << 8); + writel(reg_val, db->membase + EMAC_INT_CTL_REG); + + db->emacrx_completed_flag = 1; + + return; + } + + /* A packet ready now & Get status/length */ + good_packet = true; + + emac_inblk_32bit(db->membase + EMAC_RX_IO_DATA_REG, + &rxhdr, sizeof(rxhdr)); + + if (netif_msg_rx_status(db)) + dev_dbg(db->dev, "rxhdr: %x\n", *((int *)(&rxhdr))); + + rxlen = EMAC_RX_IO_DATA_LEN(rxhdr); + rxstatus = EMAC_RX_IO_DATA_STATUS(rxhdr); + + if (netif_msg_rx_status(db)) + dev_dbg(db->dev, "RX: status %02x, length %04x\n", + rxstatus, rxlen); + + /* Packet Status check */ + if (rxlen < 0x40) { + good_packet = false; + if (netif_msg_rx_err(db)) + dev_dbg(db->dev, "RX: Bad Packet (runt)\n"); + } + + if (unlikely(!(rxstatus & EMAC_RX_IO_DATA_STATUS_OK))) { + good_packet = false; + + if (rxstatus & EMAC_RX_IO_DATA_STATUS_CRC_ERR) { + if (netif_msg_rx_err(db)) + dev_dbg(db->dev, "crc error\n"); + dev->stats.rx_crc_errors++; + } + + if (rxstatus & EMAC_RX_IO_DATA_STATUS_LEN_ERR) { + if (netif_msg_rx_err(db)) + dev_dbg(db->dev, "length error\n"); + dev->stats.rx_length_errors++; + } + } + + /* Move data from EMAC */ + skb = dev_alloc_skb(rxlen + 4); + if (good_packet && skb) { + skb_reserve(skb, 2); + rdptr = (u8 *) skb_put(skb, rxlen - 4); + + /* Read received packet from RX SRAM */ + if (netif_msg_rx_status(db)) + dev_dbg(db->dev, "RxLen %x\n", rxlen); + + emac_inblk_32bit(db->membase + EMAC_RX_IO_DATA_REG, + rdptr, rxlen); + dev->stats.rx_bytes += rxlen; + + /* Pass to upper layer */ + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + dev->stats.rx_packets++; + } + } +} + +static irqreturn_t emac_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct emac_board_info *db = netdev_priv(dev); + int int_status; + unsigned long flags; + unsigned int reg_val; + + /* A real interrupt coming */ + + /* holders of db->lock must always block IRQs */ + spin_lock_irqsave(&db->lock, flags); + + /* Disable all interrupts */ + writel(0, db->membase + EMAC_INT_CTL_REG); + + /* Got EMAC interrupt status */ + /* Got ISR */ + int_status = readl(db->membase + EMAC_INT_STA_REG); + /* Clear ISR status */ + writel(int_status, db->membase + EMAC_INT_STA_REG); + + if (netif_msg_intr(db)) + dev_dbg(db->dev, "emac interrupt %02x\n", int_status); + + /* Received the coming packet */ + if ((int_status & 0x100) && (db->emacrx_completed_flag == 1)) { + /* carrier lost */ + db->emacrx_completed_flag = 0; + emac_rx(dev); + } + + /* Transmit Interrupt check */ + if (int_status & (0x01 | 0x02)) + emac_tx_done(dev, db, int_status); + + if (int_status & (0x04 | 0x08)) + netdev_info(dev, " ab : %x\n", int_status); + + /* Re-enable interrupt mask */ + if (db->emacrx_completed_flag == 1) { + reg_val = readl(db->membase + EMAC_INT_CTL_REG); + reg_val |= (0xf << 0) | (0x01 << 8); + writel(reg_val, db->membase + EMAC_INT_CTL_REG); + } + spin_unlock_irqrestore(&db->lock, flags); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +/* + * Used by netconsole + */ +static void emac_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + emac_interrupt(dev->irq, dev); + enable_irq(dev->irq); +} +#endif + +/* Open the interface. + * The interface is opened whenever "ifconfig" actives it. + */ +static int emac_open(struct net_device *dev) +{ + struct emac_board_info *db = netdev_priv(dev); + int ret; + + if (netif_msg_ifup(db)) + dev_dbg(db->dev, "enabling %s\n", dev->name); + + if (devm_request_irq(db->dev, dev->irq, &emac_interrupt, + 0, dev->name, dev)) + return -EAGAIN; + + /* Initialize EMAC board */ + emac_reset(db); + emac_init_device(dev); + + ret = emac_mdio_probe(dev); + if (ret < 0) { + netdev_err(dev, "cannot probe MDIO bus\n"); + return ret; + } + + phy_start(db->phy_dev); + netif_start_queue(dev); + + return 0; +} + +static void emac_shutdown(struct net_device *dev) +{ + unsigned int reg_val; + struct emac_board_info *db = netdev_priv(dev); + + /* Disable all interrupt */ + writel(0, db->membase + EMAC_INT_CTL_REG); + + /* clear interupt status */ + reg_val = readl(db->membase + EMAC_INT_STA_REG); + writel(reg_val, db->membase + EMAC_INT_STA_REG); + + /* Disable RX/TX */ + reg_val = readl(db->membase + EMAC_CTL_REG); + reg_val &= ~(EMAC_CTL_TX_EN | EMAC_CTL_RX_EN | EMAC_CTL_RESET); + writel(reg_val, db->membase + EMAC_CTL_REG); +} + +/* Stop the interface. + * The interface is stopped when it is brought. + */ +static int emac_stop(struct net_device *ndev) +{ + struct emac_board_info *db = netdev_priv(ndev); + + if (netif_msg_ifdown(db)) + dev_dbg(db->dev, "shutting down %s\n", ndev->name); + + netif_stop_queue(ndev); + netif_carrier_off(ndev); + + phy_stop(db->phy_dev); + + emac_mdio_remove(ndev); + + emac_shutdown(ndev); + + return 0; +} + +static const struct net_device_ops emac_netdev_ops = { + .ndo_open = emac_open, + .ndo_stop = emac_stop, + .ndo_start_xmit = emac_start_xmit, + .ndo_tx_timeout = emac_timeout, + .ndo_do_ioctl = emac_ioctl, + .ndo_change_mtu = eth_change_mtu, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = emac_set_mac_address, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = emac_poll_controller, +#endif +}; + +/* Search EMAC board, allocate space and register it + */ +static int emac_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct emac_board_info *db; + struct net_device *ndev; + int ret = 0; + const char *mac_addr; + + ndev = alloc_etherdev(sizeof(struct emac_board_info)); + if (!ndev) { + dev_err(&pdev->dev, "could not allocate device.\n"); + return -ENOMEM; + } + + SET_NETDEV_DEV(ndev, &pdev->dev); + + db = netdev_priv(ndev); + memset(db, 0, sizeof(*db)); + + db->dev = &pdev->dev; + db->ndev = ndev; + db->pdev = pdev; + + spin_lock_init(&db->lock); + + db->membase = of_iomap(np, 0); + if (!db->membase) { + dev_err(&pdev->dev, "failed to remap registers\n"); + return -ENOMEM; + goto out; + } + + /* fill in parameters for net-dev structure */ + ndev->base_addr = (unsigned long)db->membase; + ndev->irq = irq_of_parse_and_map(np, 0); + if (ndev->irq == -ENXIO) { + netdev_err(ndev, "No irq resource\n"); + ret = ndev->irq; + goto out; + } + + db->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(db->clk)) + goto out; + + clk_prepare_enable(db->clk); + + db->phy_node = of_parse_phandle(np, "phy", 0); + if (!db->phy_node) { + dev_err(&pdev->dev, "no associated PHY\n"); + ret = -ENODEV; + goto out; + } + + /* Read MAC-address from DT */ + mac_addr = of_get_mac_address(np); + if (mac_addr) + memcpy(ndev->dev_addr, mac_addr, ETH_ALEN); + + /* Check if the MAC address is valid, if not get a random one */ + if (!is_valid_ether_addr(ndev->dev_addr)) { + eth_hw_addr_random(ndev); + dev_warn(&pdev->dev, "using random MAC address %pM\n", + ndev->dev_addr); + } + + db->emacrx_completed_flag = 1; + emac_powerup(ndev); + emac_reset(db); + + ether_setup(ndev); + + ndev->netdev_ops = &emac_netdev_ops; + ndev->watchdog_timeo = msecs_to_jiffies(watchdog); + ndev->ethtool_ops = &emac_ethtool_ops; + +#ifdef CONFIG_NET_POLL_CONTROLLER + ndev->poll_controller = &emac_poll_controller; +#endif + + platform_set_drvdata(pdev, ndev); + + /* Carrier starts down, phylib will bring it up */ + netif_carrier_off(ndev); + + ret = register_netdev(ndev); + if (ret) { + dev_err(&pdev->dev, "Registering netdev failed!\n"); + ret = -ENODEV; + goto out; + } + + dev_info(&pdev->dev, "%s: at %p, IRQ %d MAC: %pM\n", + ndev->name, db->membase, ndev->irq, ndev->dev_addr); + + return 0; + +out: + dev_err(db->dev, "not found (%d).\n", ret); + + free_netdev(ndev); + + return ret; +} + +static int emac_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + unregister_netdev(ndev); + free_netdev(ndev); + + dev_dbg(&pdev->dev, "released and freed device\n"); + return 0; +} + +static int emac_suspend(struct platform_device *dev, pm_message_t state) +{ + struct net_device *ndev = platform_get_drvdata(dev); + + netif_carrier_off(ndev); + netif_device_detach(ndev); + emac_shutdown(ndev); + + return 0; +} + +static int emac_resume(struct platform_device *dev) +{ + struct net_device *ndev = platform_get_drvdata(dev); + struct emac_board_info *db = netdev_priv(ndev); + + emac_reset(db); + emac_init_device(ndev); + netif_device_attach(ndev); + + return 0; +} + +static const struct of_device_id emac_of_match[] = { + {.compatible = "allwinner,sun4i-emac",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, emac_of_match); + +static struct platform_driver emac_driver = { + .driver = { + .name = "sun4i-emac", + .of_match_table = emac_of_match, + }, + .probe = emac_probe, + .remove = emac_remove, + .suspend = emac_suspend, + .resume = emac_resume, +}; + +module_platform_driver(emac_driver); + +MODULE_AUTHOR("Stefan Roese "); +MODULE_AUTHOR("Maxime Ripard "); +MODULE_DESCRIPTION("Allwinner A10 emac network driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.h b/drivers/net/ethernet/allwinner/sun4i-emac.h new file mode 100644 index 00000000000..38c72d9ec60 --- /dev/null +++ b/drivers/net/ethernet/allwinner/sun4i-emac.h @@ -0,0 +1,108 @@ +/* + * Allwinner EMAC Fast Ethernet driver for Linux. + * + * Copyright 2012 Stefan Roese + * Copyright 2013 Maxime Ripard + * + * Based on the Linux driver provided by Allwinner: + * Copyright (C) 1997 Sten Wang + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef _SUN4I_EMAC_H_ +#define _SUN4I_EMAC_H_ + +#define EMAC_CTL_REG (0x00) +#define EMAC_CTL_RESET (1 << 0) +#define EMAC_CTL_TX_EN (1 << 1) +#define EMAC_CTL_RX_EN (1 << 2) +#define EMAC_TX_MODE_REG (0x04) +#define EMAC_TX_MODE_ABORTED_FRAME_EN (1 << 0) +#define EMAC_TX_MODE_DMA_EN (1 << 1) +#define EMAC_TX_FLOW_REG (0x08) +#define EMAC_TX_CTL0_REG (0x0c) +#define EMAC_TX_CTL1_REG (0x10) +#define EMAC_TX_INS_REG (0x14) +#define EMAC_TX_PL0_REG (0x18) +#define EMAC_TX_PL1_REG (0x1c) +#define EMAC_TX_STA_REG (0x20) +#define EMAC_TX_IO_DATA_REG (0x24) +#define EMAC_TX_IO_DATA1_REG (0x28) +#define EMAC_TX_TSVL0_REG (0x2c) +#define EMAC_TX_TSVH0_REG (0x30) +#define EMAC_TX_TSVL1_REG (0x34) +#define EMAC_TX_TSVH1_REG (0x38) +#define EMAC_RX_CTL_REG (0x3c) +#define EMAC_RX_CTL_AUTO_DRQ_EN (1 << 1) +#define EMAC_RX_CTL_DMA_EN (1 << 2) +#define EMAC_RX_CTL_PASS_ALL_EN (1 << 4) +#define EMAC_RX_CTL_PASS_CTL_EN (1 << 5) +#define EMAC_RX_CTL_PASS_CRC_ERR_EN (1 << 6) +#define EMAC_RX_CTL_PASS_LEN_ERR_EN (1 << 7) +#define EMAC_RX_CTL_PASS_LEN_OOR_EN (1 << 8) +#define EMAC_RX_CTL_ACCEPT_UNICAST_EN (1 << 16) +#define EMAC_RX_CTL_DA_FILTER_EN (1 << 17) +#define EMAC_RX_CTL_ACCEPT_MULTICAST_EN (1 << 20) +#define EMAC_RX_CTL_HASH_FILTER_EN (1 << 21) +#define EMAC_RX_CTL_ACCEPT_BROADCAST_EN (1 << 22) +#define EMAC_RX_CTL_SA_FILTER_EN (1 << 24) +#define EMAC_RX_CTL_SA_FILTER_INVERT_EN (1 << 25) +#define EMAC_RX_HASH0_REG (0x40) +#define EMAC_RX_HASH1_REG (0x44) +#define EMAC_RX_STA_REG (0x48) +#define EMAC_RX_IO_DATA_REG (0x4c) +#define EMAC_RX_IO_DATA_LEN(x) (x & 0xffff) +#define EMAC_RX_IO_DATA_STATUS(x) ((x >> 16) & 0xffff) +#define EMAC_RX_IO_DATA_STATUS_CRC_ERR (1 << 4) +#define EMAC_RX_IO_DATA_STATUS_LEN_ERR (3 << 5) +#define EMAC_RX_IO_DATA_STATUS_OK (1 << 7) +#define EMAC_RX_FBC_REG (0x50) +#define EMAC_INT_CTL_REG (0x54) +#define EMAC_INT_STA_REG (0x58) +#define EMAC_MAC_CTL0_REG (0x5c) +#define EMAC_MAC_CTL0_RX_FLOW_CTL_EN (1 << 2) +#define EMAC_MAC_CTL0_TX_FLOW_CTL_EN (1 << 3) +#define EMAC_MAC_CTL0_SOFT_RESET (1 << 15) +#define EMAC_MAC_CTL1_REG (0x60) +#define EMAC_MAC_CTL1_DUPLEX_EN (1 << 0) +#define EMAC_MAC_CTL1_LEN_CHECK_EN (1 << 1) +#define EMAC_MAC_CTL1_HUGE_FRAME_EN (1 << 2) +#define EMAC_MAC_CTL1_DELAYED_CRC_EN (1 << 3) +#define EMAC_MAC_CTL1_CRC_EN (1 << 4) +#define EMAC_MAC_CTL1_PAD_EN (1 << 5) +#define EMAC_MAC_CTL1_PAD_CRC_EN (1 << 6) +#define EMAC_MAC_CTL1_AD_SHORT_FRAME_EN (1 << 7) +#define EMAC_MAC_CTL1_BACKOFF_DIS (1 << 12) +#define EMAC_MAC_IPGT_REG (0x64) +#define EMAC_MAC_IPGT_HALF_DUPLEX (0x12) +#define EMAC_MAC_IPGT_FULL_DUPLEX (0x15) +#define EMAC_MAC_IPGR_REG (0x68) +#define EMAC_MAC_IPGR_IPG1 (0x0c) +#define EMAC_MAC_IPGR_IPG2 (0x12) +#define EMAC_MAC_CLRT_REG (0x6c) +#define EMAC_MAC_CLRT_COLLISION_WINDOW (0x37) +#define EMAC_MAC_CLRT_RM (0x0f) +#define EMAC_MAC_MAXF_REG (0x70) +#define EMAC_MAC_SUPP_REG (0x74) +#define EMAC_MAC_TEST_REG (0x78) +#define EMAC_MAC_MCFG_REG (0x7c) +#define EMAC_MAC_A0_REG (0x98) +#define EMAC_MAC_A1_REG (0x9c) +#define EMAC_MAC_A2_REG (0xa0) +#define EMAC_SAFX_L_REG0 (0xa4) +#define EMAC_SAFX_H_REG0 (0xa8) +#define EMAC_SAFX_L_REG1 (0xac) +#define EMAC_SAFX_H_REG1 (0xb0) +#define EMAC_SAFX_L_REG2 (0xb4) +#define EMAC_SAFX_H_REG2 (0xb8) +#define EMAC_SAFX_L_REG3 (0xbc) +#define EMAC_SAFX_H_REG3 (0xc0) + +#define EMAC_PHY_DUPLEX (1 << 8) + +#define EMAC_EEPROM_MAGIC (0x444d394b) +#define EMAC_UNDOCUMENTED_MAGIC (0x0143414d) +#endif /* _SUN4I_EMAC_H_ */ -- cgit v1.2.3-70-g09d2 From 4bdcb1dd9feb03608e12cfa46aba385035af8ea5 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 30 May 2013 03:49:21 +0000 Subject: net: Add MDIO bus driver for the Allwinner EMAC This patch adds a separate driver for the MDIO interface of the Allwinner ethernet controllers. Signed-off-by: Maxime Ripard Tested-by: Richard Genoud Signed-off-by: David S. Miller --- .../bindings/net/allwinner,sun4i-mdio.txt | 26 +++ drivers/net/phy/Kconfig | 10 ++ drivers/net/phy/Makefile | 1 + drivers/net/phy/mdio-sun4i.c | 194 +++++++++++++++++++++ 4 files changed, 231 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/allwinner,sun4i-mdio.txt create mode 100644 drivers/net/phy/mdio-sun4i.c (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/allwinner,sun4i-mdio.txt b/Documentation/devicetree/bindings/net/allwinner,sun4i-mdio.txt new file mode 100644 index 00000000000..00b9f9a3ec1 --- /dev/null +++ b/Documentation/devicetree/bindings/net/allwinner,sun4i-mdio.txt @@ -0,0 +1,26 @@ +* Allwinner A10 MDIO Ethernet Controller interface + +Required properties: +- compatible: should be "allwinner,sun4i-mdio". +- reg: address and length of the register set for the device. + +Optional properties: +- phy-supply: phandle to a regulator if the PHY needs one + +Example at the SoC level: +mdio@01c0b080 { + compatible = "allwinner,sun4i-mdio"; + reg = <0x01c0b080 0x14>; + #address-cells = <1>; + #size-cells = <0>; +}; + +And at the board level: + +mdio@01c0b080 { + phy-supply = <®_emac_3v3>; + + phy0: ethernet-phy@0 { + reg = <0>; + }; +}; diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 1e11f2bfd9c..3a316b30089 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -144,6 +144,16 @@ config MDIO_OCTEON If in doubt, say Y. +config MDIO_SUN4I + tristate "Allwinner sun4i MDIO interface support" + depends on ARCH_SUNXI + select REGULATOR + select REGULATOR_FIXED_VOLTAGE + help + This driver supports the MDIO interface found in the network + interface units of the Allwinner SoC that have an EMAC (A10, + A12, A10s, etc.) + config MDIO_BUS_MUX tristate depends on OF_MDIO diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 9645e389a58..23a2ab2e847 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -30,3 +30,4 @@ obj-$(CONFIG_AMD_PHY) += amd.o obj-$(CONFIG_MDIO_BUS_MUX) += mdio-mux.o obj-$(CONFIG_MDIO_BUS_MUX_GPIO) += mdio-mux-gpio.o obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o +obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o diff --git a/drivers/net/phy/mdio-sun4i.c b/drivers/net/phy/mdio-sun4i.c new file mode 100644 index 00000000000..61d3f4ebf52 --- /dev/null +++ b/drivers/net/phy/mdio-sun4i.c @@ -0,0 +1,194 @@ +/* + * Allwinner EMAC MDIO interface driver + * + * Copyright 2012-2013 Stefan Roese + * Copyright 2013 Maxime Ripard + * + * Based on the Linux driver provided by Allwinner: + * Copyright (C) 1997 Sten Wang + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EMAC_MAC_MCMD_REG (0x00) +#define EMAC_MAC_MADR_REG (0x04) +#define EMAC_MAC_MWTD_REG (0x08) +#define EMAC_MAC_MRDD_REG (0x0c) +#define EMAC_MAC_MIND_REG (0x10) +#define EMAC_MAC_SSRR_REG (0x14) + +#define MDIO_TIMEOUT (msecs_to_jiffies(100)) + +struct sun4i_mdio_data { + void __iomem *membase; + struct regulator *regulator; +}; + +static int sun4i_mdio_read(struct mii_bus *bus, int mii_id, int regnum) +{ + struct sun4i_mdio_data *data = bus->priv; + unsigned long start_jiffies; + int value; + + /* issue the phy address and reg */ + writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG); + /* pull up the phy io line */ + writel(0x1, data->membase + EMAC_MAC_MCMD_REG); + + /* Wait read complete */ + start_jiffies = jiffies; + while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) { + if (time_after(start_jiffies, + start_jiffies + MDIO_TIMEOUT)) + return -ETIMEDOUT; + msleep(1); + } + + /* push down the phy io line */ + writel(0x0, data->membase + EMAC_MAC_MCMD_REG); + /* and read data */ + value = readl(data->membase + EMAC_MAC_MRDD_REG); + + return value; +} + +static int sun4i_mdio_write(struct mii_bus *bus, int mii_id, int regnum, + u16 value) +{ + struct sun4i_mdio_data *data = bus->priv; + unsigned long start_jiffies; + + /* issue the phy address and reg */ + writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG); + /* pull up the phy io line */ + writel(0x1, data->membase + EMAC_MAC_MCMD_REG); + + /* Wait read complete */ + start_jiffies = jiffies; + while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) { + if (time_after(start_jiffies, + start_jiffies + MDIO_TIMEOUT)) + return -ETIMEDOUT; + msleep(1); + } + + /* push down the phy io line */ + writel(0x0, data->membase + EMAC_MAC_MCMD_REG); + /* and write data */ + writel(value, data->membase + EMAC_MAC_MWTD_REG); + + return 0; +} + +static int sun4i_mdio_reset(struct mii_bus *bus) +{ + return 0; +} + +static int sun4i_mdio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mii_bus *bus; + struct sun4i_mdio_data *data; + int ret, i; + + bus = mdiobus_alloc_size(sizeof(*data)); + if (!bus) + return -ENOMEM; + + bus->name = "sun4i_mii_bus"; + bus->read = &sun4i_mdio_read; + bus->write = &sun4i_mdio_write; + bus->reset = &sun4i_mdio_reset; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); + bus->parent = &pdev->dev; + + bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); + if (!bus->irq) { + ret = -ENOMEM; + goto err_out_free_mdiobus; + } + + for (i = 0; i < PHY_MAX_ADDR; i++) + bus->irq[i] = PHY_POLL; + + data = bus->priv; + data->membase = of_iomap(np, 0); + if (!data->membase) { + ret = -ENOMEM; + goto err_out_free_mdio_irq; + } + + data->regulator = devm_regulator_get(&pdev->dev, "phy"); + if (IS_ERR(data->regulator)) { + if (PTR_ERR(data->regulator) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + dev_info(&pdev->dev, "no regulator found\n"); + } else { + ret = regulator_enable(data->regulator); + if (ret) + goto err_out_free_mdio_irq; + } + + ret = of_mdiobus_register(bus, np); + if (ret < 0) + goto err_out_disable_regulator; + + platform_set_drvdata(pdev, bus); + + return 0; + +err_out_disable_regulator: + regulator_disable(data->regulator); +err_out_free_mdio_irq: + kfree(bus->irq); +err_out_free_mdiobus: + mdiobus_free(bus); + return ret; +} + +static int sun4i_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus); + kfree(bus->irq); + mdiobus_free(bus); + + return 0; +} + +static const struct of_device_id sun4i_mdio_dt_ids[] = { + { .compatible = "allwinner,sun4i-mdio" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_mdio_dt_ids); + +static struct platform_driver sun4i_mdio_driver = { + .probe = sun4i_mdio_probe, + .remove = sun4i_mdio_remove, + .driver = { + .name = "sun4i-mdio", + .of_match_table = sun4i_mdio_dt_ids, + }, +}; + +module_platform_driver(sun4i_mdio_driver); + +MODULE_DESCRIPTION("Allwinner EMAC MDIO interface driver"); +MODULE_AUTHOR("Maxime Ripard "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From c5ceea7a2813fecded01567383cca4d6855164ab Mon Sep 17 00:00:00 2001 From: Mugunthan V N Date: Mon, 3 Jun 2013 20:10:10 +0000 Subject: drivers: net: ethernet: cpsw: add phy-mode support to cpsw driver Adding phy-mode support to cpsw driver and updating the cpsw binding documentation. Signed-off-by: Mugunthan V N Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/cpsw.txt | 6 ++++++ drivers/net/ethernet/ti/cpsw.c | 2 ++ 2 files changed, 8 insertions(+) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/cpsw.txt b/Documentation/devicetree/bindings/net/cpsw.txt index 4f2ca6b4a18..05d660e4ac6 100644 --- a/Documentation/devicetree/bindings/net/cpsw.txt +++ b/Documentation/devicetree/bindings/net/cpsw.txt @@ -28,6 +28,8 @@ Optional properties: Slave Properties: Required properties: - phy_id : Specifies slave phy id +- phy-mode : The interface between the SoC and the PHY (a string + that of_get_phy_mode() can understand) - mac-address : Specifies slave MAC address Optional properties: @@ -58,11 +60,13 @@ Examples: cpts_clock_shift = <29>; cpsw_emac0: slave@0 { phy_id = <&davinci_mdio>, <0>; + phy-mode = "rgmii-txid"; /* Filled in by U-Boot */ mac-address = [ 00 00 00 00 00 00 ]; }; cpsw_emac1: slave@1 { phy_id = <&davinci_mdio>, <1>; + phy-mode = "rgmii-txid"; /* Filled in by U-Boot */ mac-address = [ 00 00 00 00 00 00 ]; }; @@ -84,11 +88,13 @@ Examples: cpts_clock_shift = <29>; cpsw_emac0: slave@0 { phy_id = <&davinci_mdio>, <0>; + phy-mode = "rgmii-txid"; /* Filled in by U-Boot */ mac-address = [ 00 00 00 00 00 00 ]; }; cpsw_emac1: slave@1 { phy_id = <&davinci_mdio>, <1>; + phy-mode = "rgmii-txid"; /* Filled in by U-Boot */ mac-address = [ 00 00 00 00 00 00 ]; }; diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c index 89a4c40d6d8..a45f64eef87 100644 --- a/drivers/net/ethernet/ti/cpsw.c +++ b/drivers/net/ethernet/ti/cpsw.c @@ -1554,6 +1554,8 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data, if (mac_addr) memcpy(slave_data->mac_addr, mac_addr, ETH_ALEN); + slave_data->phy_if = of_get_phy_mode(slave_node); + if (data->dual_emac) { if (of_property_read_u32(slave_node, "dual_emac_res_vlan", &prop)) { -- cgit v1.2.3-70-g09d2 From e4f2379db6c6823c5d4a4c2c912df00c65de51d7 Mon Sep 17 00:00:00 2001 From: Alexey Brodkin Date: Mon, 24 Jun 2013 09:54:27 +0400 Subject: ethernet/arc/arc_emac - Add new driver Driver for non-standard on-chip ethernet device ARC EMAC 10/100, instantiated in some legacy ARC (Synopsys) FPGA Boards such as ARCAngel4/ML50x. Signed-off-by: Alexey Brodkin Cc: Andy Shevchenko Cc: Francois Romieu Cc: Joe Perches Cc: Vineet Gupta Cc: Mischa Jonker Cc: Arnd Bergmann Cc: Grant Likely Cc: Rob Herring Cc: Paul Gortmaker Cc: "David S. Miller" Cc: linux-kernel@vger.kernel.org Cc: devicetree-discuss@lists.ozlabs.org Cc: Florian Fainelli Cc: David Laight Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/arc_emac.txt | 38 + drivers/net/ethernet/Kconfig | 1 + drivers/net/ethernet/Makefile | 1 + drivers/net/ethernet/arc/Kconfig | 31 + drivers/net/ethernet/arc/Makefile | 6 + drivers/net/ethernet/arc/emac.h | 214 ++++++ drivers/net/ethernet/arc/emac_main.c | 806 +++++++++++++++++++++ drivers/net/ethernet/arc/emac_mdio.c | 152 ++++ 8 files changed, 1249 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/arc_emac.txt create mode 100644 drivers/net/ethernet/arc/Kconfig create mode 100644 drivers/net/ethernet/arc/Makefile create mode 100644 drivers/net/ethernet/arc/emac.h create mode 100644 drivers/net/ethernet/arc/emac_main.c create mode 100644 drivers/net/ethernet/arc/emac_mdio.c (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/arc_emac.txt b/Documentation/devicetree/bindings/net/arc_emac.txt new file mode 100644 index 00000000000..bcbc3f00915 --- /dev/null +++ b/Documentation/devicetree/bindings/net/arc_emac.txt @@ -0,0 +1,38 @@ +* Synopsys ARC EMAC 10/100 Ethernet driver (EMAC) + +Required properties: +- compatible: Should be "snps,arc-emac" +- reg: Address and length of the register set for the device +- interrupts: Should contain the EMAC interrupts +- clock-frequency: CPU frequency. It is needed to calculate and set polling +period of EMAC. +- max-speed: Maximum supported data-rate in Mbit/s. In some HW configurations +bandwidth of external memory controller might be a limiting factor. That's why +it's required to specify which data-rate is supported on current SoC or FPGA. +For example if only 10 Mbit/s is supported (10BASE-T) set "10". If 100 Mbit/s is +supported (100BASE-TX) set "100". +- phy: PHY device attached to the EMAC via MDIO bus + +Child nodes of the driver are the individual PHY devices connected to the +MDIO bus. They must have a "reg" property given the PHY address on the MDIO bus. + +Optional properties: +- mac-address: 6 bytes, mac address + +Examples: + + ethernet@c0fc2000 { + compatible = "snps,arc-emac"; + reg = <0xc0fc2000 0x3c>; + interrupts = <6>; + mac-address = [ 00 11 22 33 44 55 ]; + clock-frequency = <80000000>; + max-speed = <100>; + phy = <&phy0>; + + #address-cells = <1>; + #size-cells = <0>; + phy0: ethernet-phy@0 { + reg = <1>; + }; + }; diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index a989669b353..2037080c504 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -24,6 +24,7 @@ source "drivers/net/ethernet/allwinner/Kconfig" source "drivers/net/ethernet/alteon/Kconfig" source "drivers/net/ethernet/amd/Kconfig" source "drivers/net/ethernet/apple/Kconfig" +source "drivers/net/ethernet/arc/Kconfig" source "drivers/net/ethernet/atheros/Kconfig" source "drivers/net/ethernet/cadence/Kconfig" source "drivers/net/ethernet/adi/Kconfig" diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index 009da27b6e2..390bd0bfaa2 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_NET_VENDOR_ALLWINNER) += allwinner/ obj-$(CONFIG_NET_VENDOR_ALTEON) += alteon/ obj-$(CONFIG_NET_VENDOR_AMD) += amd/ obj-$(CONFIG_NET_VENDOR_APPLE) += apple/ +obj-$(CONFIG_NET_VENDOR_ARC) += arc/ obj-$(CONFIG_NET_VENDOR_ATHEROS) += atheros/ obj-$(CONFIG_NET_CADENCE) += cadence/ obj-$(CONFIG_NET_BFIN) += adi/ diff --git a/drivers/net/ethernet/arc/Kconfig b/drivers/net/ethernet/arc/Kconfig new file mode 100644 index 00000000000..514c57fd26f --- /dev/null +++ b/drivers/net/ethernet/arc/Kconfig @@ -0,0 +1,31 @@ +# +# ARC EMAC network device configuration +# + +config NET_VENDOR_ARC + bool "ARC devices" + default y + ---help--- + If you have a network (Ethernet) card belonging to this class, say Y + and read the Ethernet-HOWTO, available from + . + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about ARC cards. If you say Y, you will be asked for + your specific card in the following questions. + +if NET_VENDOR_ARC + +config ARC_EMAC + tristate "ARC EMAC support" + select MII + select PHYLIB + depends on OF_IRQ + depends on OF_NET + ---help--- + On some legacy ARC (Synopsys) FPGA boards such as ARCAngel4/ML50x + non-standard on-chip ethernet device ARC EMAC 10/100 is used. + Say Y here if you have such a board. If unsure, say N. + +endif # NET_VENDOR_ARC diff --git a/drivers/net/ethernet/arc/Makefile b/drivers/net/ethernet/arc/Makefile new file mode 100644 index 00000000000..00c8657637d --- /dev/null +++ b/drivers/net/ethernet/arc/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the ARC network device drivers. +# + +arc_emac-objs := emac_main.o emac_mdio.o +obj-$(CONFIG_ARC_EMAC) += arc_emac.o diff --git a/drivers/net/ethernet/arc/emac.h b/drivers/net/ethernet/arc/emac.h new file mode 100644 index 00000000000..dc08678bf9a --- /dev/null +++ b/drivers/net/ethernet/arc/emac.h @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2004-2013 Synopsys, Inc. (www.synopsys.com) + * + * Registers and bits definitions of ARC EMAC + */ + +#ifndef ARC_EMAC_H +#define ARC_EMAC_H + +#include +#include +#include +#include + +/* STATUS and ENABLE Register bit masks */ +#define TXINT_MASK (1<<0) /* Transmit interrupt */ +#define RXINT_MASK (1<<1) /* Receive interrupt */ +#define ERR_MASK (1<<2) /* Error interrupt */ +#define TXCH_MASK (1<<3) /* Transmit chaining error interrupt */ +#define MSER_MASK (1<<4) /* Missed packet counter error */ +#define RXCR_MASK (1<<8) /* RXCRCERR counter rolled over */ +#define RXFR_MASK (1<<9) /* RXFRAMEERR counter rolled over */ +#define RXFL_MASK (1<<10) /* RXOFLOWERR counter rolled over */ +#define MDIO_MASK (1<<12) /* MDIO complete interrupt */ +#define TXPL_MASK (1<<31) /* Force polling of BD by EMAC */ + +/* CONTROL Register bit masks */ +#define EN_MASK (1<<0) /* VMAC enable */ +#define TXRN_MASK (1<<3) /* TX enable */ +#define RXRN_MASK (1<<4) /* RX enable */ +#define DSBC_MASK (1<<8) /* Disable receive broadcast */ +#define ENFL_MASK (1<<10) /* Enable Full-duplex */ +#define PROM_MASK (1<<11) /* Promiscuous mode */ + +/* Buffer descriptor INFO bit masks */ +#define OWN_MASK (1<<31) /* 0-CPU owns buffer, 1-EMAC owns buffer */ +#define FIRST_MASK (1<<16) /* First buffer in chain */ +#define LAST_MASK (1<<17) /* Last buffer in chain */ +#define LEN_MASK 0x000007FF /* last 11 bits */ +#define CRLS (1<<21) +#define DEFR (1<<22) +#define DROP (1<<23) +#define RTRY (1<<24) +#define LTCL (1<<28) +#define UFLO (1<<29) + +#define FOR_EMAC OWN_MASK +#define FOR_CPU 0 + +/* ARC EMAC register set combines entries for MAC and MDIO */ +enum { + R_ID = 0, + R_STATUS, + R_ENABLE, + R_CTRL, + R_POLLRATE, + R_RXERR, + R_MISS, + R_TX_RING, + R_RX_RING, + R_ADDRL, + R_ADDRH, + R_LAFL, + R_LAFH, + R_MDIO, +}; + +#define TX_TIMEOUT (400*HZ/1000) /* Transmission timeout */ + +#define ARC_EMAC_NAPI_WEIGHT 40 /* Workload for NAPI */ + +#define EMAC_BUFFER_SIZE 1536 /* EMAC buffer size */ + +/** + * struct arc_emac_bd - EMAC buffer descriptor (BD). + * + * @info: Contains status information on the buffer itself. + * @data: 32-bit byte addressable pointer to the packet data. + */ +struct arc_emac_bd { + __le32 info; + dma_addr_t data; +}; + +/* Number of Rx/Tx BD's */ +#define RX_BD_NUM 128 +#define TX_BD_NUM 128 + +#define RX_RING_SZ (RX_BD_NUM * sizeof(struct arc_emac_bd)) +#define TX_RING_SZ (TX_BD_NUM * sizeof(struct arc_emac_bd)) + +/** + * struct buffer_state - Stores Rx/Tx buffer state. + * @sk_buff: Pointer to socket buffer. + * @addr: Start address of DMA-mapped memory region. + * @len: Length of DMA-mapped memory region. + */ +struct buffer_state { + struct sk_buff *skb; + DEFINE_DMA_UNMAP_ADDR(addr); + DEFINE_DMA_UNMAP_LEN(len); +}; + +/** + * struct arc_emac_priv - Storage of EMAC's private information. + * @dev: Pointer to the current device. + * @ndev: Pointer to the current network device. + * @phy_dev: Pointer to attached PHY device. + * @bus: Pointer to the current MII bus. + * @regs: Base address of EMAC memory-mapped control registers. + * @napi: Structure for NAPI. + * @stats: Network device statistics. + * @rxbd: Pointer to Rx BD ring. + * @txbd: Pointer to Tx BD ring. + * @rxbd_dma: DMA handle for Rx BD ring. + * @txbd_dma: DMA handle for Tx BD ring. + * @rx_buff: Storage for Rx buffers states. + * @tx_buff: Storage for Tx buffers states. + * @txbd_curr: Index of Tx BD to use on the next "ndo_start_xmit". + * @txbd_dirty: Index of Tx BD to free on the next Tx interrupt. + * @last_rx_bd: Index of the last Rx BD we've got from EMAC. + * @link: PHY's last seen link state. + * @duplex: PHY's last set duplex mode. + * @speed: PHY's last set speed. + * @max_speed: Maximum supported by current system network data-rate. + */ +struct arc_emac_priv { + /* Devices */ + struct device *dev; + struct net_device *ndev; + struct phy_device *phy_dev; + struct mii_bus *bus; + + void __iomem *regs; + + struct napi_struct napi; + struct net_device_stats stats; + + struct arc_emac_bd *rxbd; + struct arc_emac_bd *txbd; + + dma_addr_t rxbd_dma; + dma_addr_t txbd_dma; + + struct buffer_state rx_buff[RX_BD_NUM]; + struct buffer_state tx_buff[TX_BD_NUM]; + unsigned int txbd_curr; + unsigned int txbd_dirty; + + unsigned int last_rx_bd; + + unsigned int link; + unsigned int duplex; + unsigned int speed; + unsigned int max_speed; +}; + +/** + * arc_reg_set - Sets EMAC register with provided value. + * @priv: Pointer to ARC EMAC private data structure. + * @reg: Register offset from base address. + * @value: Value to set in register. + */ +static inline void arc_reg_set(struct arc_emac_priv *priv, int reg, int value) +{ + iowrite32(value, priv->regs + reg * sizeof(int)); +} + +/** + * arc_reg_get - Gets value of specified EMAC register. + * @priv: Pointer to ARC EMAC private data structure. + * @reg: Register offset from base address. + * + * returns: Value of requested register. + */ +static inline unsigned int arc_reg_get(struct arc_emac_priv *priv, int reg) +{ + return ioread32(priv->regs + reg * sizeof(int)); +} + +/** + * arc_reg_or - Applies mask to specified EMAC register - ("reg" | "mask"). + * @priv: Pointer to ARC EMAC private data structure. + * @reg: Register offset from base address. + * @mask: Mask to apply to specified register. + * + * This function reads initial register value, then applies provided mask + * to it and then writes register back. + */ +static inline void arc_reg_or(struct arc_emac_priv *priv, int reg, int mask) +{ + unsigned int value = arc_reg_get(priv, reg); + arc_reg_set(priv, reg, value | mask); +} + +/** + * arc_reg_clr - Applies mask to specified EMAC register - ("reg" & ~"mask"). + * @priv: Pointer to ARC EMAC private data structure. + * @reg: Register offset from base address. + * @mask: Mask to apply to specified register. + * + * This function reads initial register value, then applies provided mask + * to it and then writes register back. + */ +static inline void arc_reg_clr(struct arc_emac_priv *priv, int reg, int mask) +{ + unsigned int value = arc_reg_get(priv, reg); + arc_reg_set(priv, reg, value & ~mask); +} + +int arc_mdio_probe(struct platform_device *pdev, struct arc_emac_priv *priv); +int arc_mdio_remove(struct arc_emac_priv *priv); + +#endif /* ARC_EMAC_H */ diff --git a/drivers/net/ethernet/arc/emac_main.c b/drivers/net/ethernet/arc/emac_main.c new file mode 100644 index 00000000000..20345f6bf89 --- /dev/null +++ b/drivers/net/ethernet/arc/emac_main.c @@ -0,0 +1,806 @@ +/* + * Copyright (C) 2004-2013 Synopsys, Inc. (www.synopsys.com) + * + * 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. + * + * Driver for the ARC EMAC 10100 (hardware revision 5) + * + * Contributors: + * Amit Bhor + * Sameer Dhavale + * Vineet Gupta + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "emac.h" + +#define DRV_NAME "arc_emac" +#define DRV_VERSION "1.0" + +/** + * arc_emac_adjust_link - Adjust the PHY link duplex. + * @ndev: Pointer to the net_device structure. + * + * This function is called to change the duplex setting after auto negotiation + * is done by the PHY. + */ +static void arc_emac_adjust_link(struct net_device *ndev) +{ + struct arc_emac_priv *priv = netdev_priv(ndev); + struct phy_device *phy_dev = priv->phy_dev; + unsigned int reg, state_changed = 0; + + if (priv->link != phy_dev->link) { + priv->link = phy_dev->link; + state_changed = 1; + } + + if (priv->speed != phy_dev->speed) { + priv->speed = phy_dev->speed; + state_changed = 1; + } + + if (priv->duplex != phy_dev->duplex) { + reg = arc_reg_get(priv, R_CTRL); + + if (DUPLEX_FULL == phy_dev->duplex) + reg |= ENFL_MASK; + else + reg &= ~ENFL_MASK; + + arc_reg_set(priv, R_CTRL, reg); + priv->duplex = phy_dev->duplex; + state_changed = 1; + } + + if (state_changed) + phy_print_status(phy_dev); +} + +/** + * arc_emac_get_settings - Get PHY settings. + * @ndev: Pointer to net_device structure. + * @cmd: Pointer to ethtool_cmd structure. + * + * This implements ethtool command for getting PHY settings. If PHY could + * not be found, the function returns -ENODEV. This function calls the + * relevant PHY ethtool API to get the PHY settings. + * Issue "ethtool ethX" under linux prompt to execute this function. + */ +static int arc_emac_get_settings(struct net_device *ndev, + struct ethtool_cmd *cmd) +{ + struct arc_emac_priv *priv = netdev_priv(ndev); + + return phy_ethtool_gset(priv->phy_dev, cmd); +} + +/** + * arc_emac_set_settings - Set PHY settings as passed in the argument. + * @ndev: Pointer to net_device structure. + * @cmd: Pointer to ethtool_cmd structure. + * + * This implements ethtool command for setting various PHY settings. If PHY + * could not be found, the function returns -ENODEV. This function calls the + * relevant PHY ethtool API to set the PHY. + * Issue e.g. "ethtool -s ethX speed 1000" under linux prompt to execute this + * function. + */ +static int arc_emac_set_settings(struct net_device *ndev, + struct ethtool_cmd *cmd) +{ + struct arc_emac_priv *priv = netdev_priv(ndev); + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + return phy_ethtool_sset(priv->phy_dev, cmd); +} + +/** + * arc_emac_get_drvinfo - Get EMAC driver information. + * @ndev: Pointer to net_device structure. + * @info: Pointer to ethtool_drvinfo structure. + * + * This implements ethtool command for getting the driver information. + * Issue "ethtool -i ethX" under linux prompt to execute this function. + */ +static void arc_emac_get_drvinfo(struct net_device *ndev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_VERSION, sizeof(info->version)); +} + +static const struct ethtool_ops arc_emac_ethtool_ops = { + .get_settings = arc_emac_get_settings, + .set_settings = arc_emac_set_settings, + .get_drvinfo = arc_emac_get_drvinfo, + .get_link = ethtool_op_get_link, +}; + +#define FIRST_OR_LAST_MASK (FIRST_MASK | LAST_MASK) + +/** + * arc_emac_tx_clean - clears processed by EMAC Tx BDs. + * @ndev: Pointer to the network device. + */ +static void arc_emac_tx_clean(struct net_device *ndev) +{ + struct arc_emac_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &priv->stats; + unsigned int i; + + for (i = 0; i < TX_BD_NUM; i++) { + unsigned int *txbd_dirty = &priv->txbd_dirty; + struct arc_emac_bd *txbd = &priv->txbd[*txbd_dirty]; + struct buffer_state *tx_buff = &priv->tx_buff[*txbd_dirty]; + struct sk_buff *skb = tx_buff->skb; + unsigned int info = le32_to_cpu(txbd->info); + + *txbd_dirty = (*txbd_dirty + 1) % TX_BD_NUM; + + if ((info & FOR_EMAC) || !txbd->data) + break; + + if (unlikely(info & (DROP | DEFR | LTCL | UFLO))) { + stats->tx_errors++; + stats->tx_dropped++; + + if (info & DEFR) + stats->tx_carrier_errors++; + + if (info & LTCL) + stats->collisions++; + + if (info & UFLO) + stats->tx_fifo_errors++; + } else if (likely(info & FIRST_OR_LAST_MASK)) { + stats->tx_packets++; + stats->tx_bytes += skb->len; + } + + dma_unmap_single(&ndev->dev, dma_unmap_addr(&tx_buff, addr), + dma_unmap_len(&tx_buff, len), DMA_TO_DEVICE); + + /* return the sk_buff to system */ + dev_kfree_skb_irq(skb); + + txbd->data = 0; + txbd->info = 0; + + if (netif_queue_stopped(ndev)) + netif_wake_queue(ndev); + } +} + +/** + * arc_emac_rx - processing of Rx packets. + * @ndev: Pointer to the network device. + * @budget: How many BDs to process on 1 call. + * + * returns: Number of processed BDs + * + * Iterate through Rx BDs and deliver received packages to upper layer. + */ +static int arc_emac_rx(struct net_device *ndev, int budget) +{ + struct arc_emac_priv *priv = netdev_priv(ndev); + unsigned int work_done; + + for (work_done = 0; work_done <= budget; work_done++) { + unsigned int *last_rx_bd = &priv->last_rx_bd; + struct net_device_stats *stats = &priv->stats; + struct buffer_state *rx_buff = &priv->rx_buff[*last_rx_bd]; + struct arc_emac_bd *rxbd = &priv->rxbd[*last_rx_bd]; + unsigned int buflen = EMAC_BUFFER_SIZE; + unsigned int pktlen, info = le32_to_cpu(rxbd->info); + struct sk_buff *skb; + dma_addr_t addr; + + if (unlikely((info & OWN_MASK) == FOR_EMAC)) + break; + + /* Make a note that we saw a packet at this BD. + * So next time, driver starts from this + 1 + */ + *last_rx_bd = (*last_rx_bd + 1) % RX_BD_NUM; + + if (unlikely((info & FIRST_OR_LAST_MASK) != + FIRST_OR_LAST_MASK)) { + /* We pre-allocate buffers of MTU size so incoming + * packets won't be split/chained. + */ + if (net_ratelimit()) + netdev_err(ndev, "incomplete packet received\n"); + + /* Return ownership to EMAC */ + rxbd->info = cpu_to_le32(FOR_EMAC | buflen); + stats->rx_errors++; + stats->rx_length_errors++; + continue; + } + + pktlen = info & LEN_MASK; + stats->rx_packets++; + stats->rx_bytes += pktlen; + skb = rx_buff->skb; + skb_put(skb, pktlen); + skb->dev = ndev; + skb->protocol = eth_type_trans(skb, ndev); + + dma_unmap_single(&ndev->dev, dma_unmap_addr(&rx_buff, addr), + dma_unmap_len(&rx_buff, len), DMA_FROM_DEVICE); + + /* Prepare the BD for next cycle */ + rx_buff->skb = netdev_alloc_skb_ip_align(ndev, buflen); + if (unlikely(!rx_buff->skb)) { + stats->rx_errors++; + /* Because receive_skb is below, increment rx_dropped */ + stats->rx_dropped++; + continue; + } + + /* receive_skb only if new skb was allocated to avoid holes */ + netif_receive_skb(skb); + + addr = dma_map_single(&ndev->dev, (void *)rx_buff->skb->data, + buflen, DMA_FROM_DEVICE); + if (dma_mapping_error(&ndev->dev, addr)) { + if (net_ratelimit()) + netdev_err(ndev, "cannot dma map\n"); + dev_kfree_skb(rx_buff->skb); + stats->rx_errors++; + continue; + } + dma_unmap_addr_set(&rx_buff, mapping, addr); + dma_unmap_len_set(&rx_buff, len, buflen); + + rxbd->data = cpu_to_le32(rx_buff->skb->data); + + /* Make sure pointer to data buffer is set */ + wmb(); + + /* Return ownership to EMAC */ + rxbd->info = cpu_to_le32(FOR_EMAC | buflen); + } + + return work_done; +} + +/** + * arc_emac_poll - NAPI poll handler. + * @napi: Pointer to napi_struct structure. + * @budget: How many BDs to process on 1 call. + * + * returns: Number of processed BDs + */ +static int arc_emac_poll(struct napi_struct *napi, int budget) +{ + struct net_device *ndev = napi->dev; + struct arc_emac_priv *priv = netdev_priv(ndev); + unsigned int work_done; + + arc_emac_tx_clean(ndev); + + work_done = arc_emac_rx(ndev, budget); + if (work_done < budget) { + napi_complete(napi); + arc_reg_or(priv, R_ENABLE, RXINT_MASK); + } + + return work_done; +} + +/** + * arc_emac_intr - Global interrupt handler for EMAC. + * @irq: irq number. + * @dev_instance: device instance. + * + * returns: IRQ_HANDLED for all cases. + * + * ARC EMAC has only 1 interrupt line, and depending on bits raised in + * STATUS register we may tell what is a reason for interrupt to fire. + */ +static irqreturn_t arc_emac_intr(int irq, void *dev_instance) +{ + struct net_device *ndev = dev_instance; + struct arc_emac_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &priv->stats; + unsigned int status; + + status = arc_reg_get(priv, R_STATUS); + status &= ~MDIO_MASK; + + /* Reset all flags except "MDIO complete" */ + arc_reg_set(priv, R_STATUS, status); + + if (status & RXINT_MASK) { + if (likely(napi_schedule_prep(&priv->napi))) { + arc_reg_clr(priv, R_ENABLE, RXINT_MASK); + __napi_schedule(&priv->napi); + } + } + + if (status & ERR_MASK) { + /* MSER/RXCR/RXFR/RXFL interrupt fires on corresponding + * 8-bit error counter overrun. + */ + + if (status & MSER_MASK) { + stats->rx_missed_errors += 0x100; + stats->rx_errors += 0x100; + } + + if (status & RXCR_MASK) { + stats->rx_crc_errors += 0x100; + stats->rx_errors += 0x100; + } + + if (status & RXFR_MASK) { + stats->rx_frame_errors += 0x100; + stats->rx_errors += 0x100; + } + + if (status & RXFL_MASK) { + stats->rx_over_errors += 0x100; + stats->rx_errors += 0x100; + } + } + + return IRQ_HANDLED; +} + +/** + * arc_emac_open - Open the network device. + * @ndev: Pointer to the network device. + * + * returns: 0, on success or non-zero error value on failure. + * + * This function sets the MAC address, requests and enables an IRQ + * for the EMAC device and starts the Tx queue. + * It also connects to the phy device. + */ +static int arc_emac_open(struct net_device *ndev) +{ + struct arc_emac_priv *priv = netdev_priv(ndev); + struct phy_device *phy_dev = priv->phy_dev; + struct arc_emac_bd *bd; + struct sk_buff *skb; + int i; + + phy_dev->autoneg = AUTONEG_ENABLE; + phy_dev->speed = 0; + phy_dev->duplex = 0; + phy_dev->advertising = phy_dev->supported; + + if (priv->max_speed > 100) { + phy_dev->advertising &= PHY_GBIT_FEATURES; + } else if (priv->max_speed <= 100) { + phy_dev->advertising &= PHY_BASIC_FEATURES; + if (priv->max_speed <= 10) { + phy_dev->advertising &= ~SUPPORTED_100baseT_Half; + phy_dev->advertising &= ~SUPPORTED_100baseT_Full; + } + } + + /* Allocate and set buffers for Rx BD's */ + bd = priv->rxbd; + for (i = 0; i < RX_BD_NUM; i++) { + skb = netdev_alloc_skb_ip_align(ndev, EMAC_BUFFER_SIZE); + if (unlikely(!skb)) + return -ENOMEM; + + priv->rx_buff[i].skb = skb; + bd->data = cpu_to_le32(skb->data); + + /* Make sure pointer to data buffer is set */ + wmb(); + + /* Set ownership to EMAC */ + bd->info = cpu_to_le32(FOR_EMAC | EMAC_BUFFER_SIZE); + bd++; + } + + priv->last_rx_bd = 0; + + /* Clean Tx BD's */ + memset(priv->txbd, 0, TX_RING_SZ); + + /* Initialize logical address filter */ + arc_reg_set(priv, R_LAFL, 0); + arc_reg_set(priv, R_LAFH, 0); + + /* Set BD ring pointers for device side */ + arc_reg_set(priv, R_RX_RING, (unsigned int)priv->rxbd_dma); + arc_reg_set(priv, R_TX_RING, (unsigned int)priv->txbd_dma); + + /* Enable interrupts */ + arc_reg_set(priv, R_ENABLE, RXINT_MASK | ERR_MASK); + + /* Set CONTROL */ + arc_reg_set(priv, R_CTRL, + (RX_BD_NUM << 24) | /* RX BD table length */ + (TX_BD_NUM << 16) | /* TX BD table length */ + TXRN_MASK | RXRN_MASK); + + napi_enable(&priv->napi); + + /* Enable EMAC */ + arc_reg_or(priv, R_CTRL, EN_MASK); + + phy_start_aneg(priv->phy_dev); + + netif_start_queue(ndev); + + return 0; +} + +/** + * arc_emac_stop - Close the network device. + * @ndev: Pointer to the network device. + * + * This function stops the Tx queue, disables interrupts and frees the IRQ for + * the EMAC device. + * It also disconnects the PHY device associated with the EMAC device. + */ +static int arc_emac_stop(struct net_device *ndev) +{ + struct arc_emac_priv *priv = netdev_priv(ndev); + + napi_disable(&priv->napi); + netif_stop_queue(ndev); + + /* Disable interrupts */ + arc_reg_clr(priv, R_ENABLE, RXINT_MASK | ERR_MASK); + + /* Disable EMAC */ + arc_reg_clr(priv, R_CTRL, EN_MASK); + + return 0; +} + +/** + * arc_emac_stats - Get system network statistics. + * @ndev: Pointer to net_device structure. + * + * Returns the address of the device statistics structure. + * Statistics are updated in interrupt handler. + */ +static struct net_device_stats *arc_emac_stats(struct net_device *ndev) +{ + struct arc_emac_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &priv->stats; + unsigned long miss, rxerr; + u8 rxcrc, rxfram, rxoflow; + + rxerr = arc_reg_get(priv, R_RXERR); + miss = arc_reg_get(priv, R_MISS); + + rxcrc = rxerr; + rxfram = rxerr >> 8; + rxoflow = rxerr >> 16; + + stats->rx_errors += miss; + stats->rx_errors += rxcrc + rxfram + rxoflow; + + stats->rx_over_errors += rxoflow; + stats->rx_frame_errors += rxfram; + stats->rx_crc_errors += rxcrc; + stats->rx_missed_errors += miss; + + return stats; +} + +/** + * arc_emac_tx - Starts the data transmission. + * @skb: sk_buff pointer that contains data to be Transmitted. + * @ndev: Pointer to net_device structure. + * + * returns: NETDEV_TX_OK, on success + * NETDEV_TX_BUSY, if any of the descriptors are not free. + * + * This function is invoked from upper layers to initiate transmission. + */ +static int arc_emac_tx(struct sk_buff *skb, struct net_device *ndev) +{ + struct arc_emac_priv *priv = netdev_priv(ndev); + unsigned int len, *txbd_curr = &priv->txbd_curr; + struct net_device_stats *stats = &priv->stats; + __le32 *info = &priv->txbd[*txbd_curr].info; + dma_addr_t addr; + + if (skb_padto(skb, ETH_ZLEN)) + return NETDEV_TX_OK; + + len = max_t(unsigned int, ETH_ZLEN, skb->len); + + /* EMAC still holds this buffer in its possession. + * CPU must not modify this buffer descriptor + */ + if (unlikely((le32_to_cpu(*info) & OWN_MASK) == FOR_EMAC)) { + netif_stop_queue(ndev); + return NETDEV_TX_BUSY; + } + + addr = dma_map_single(&ndev->dev, (void *)skb->data, len, + DMA_TO_DEVICE); + + if (unlikely(dma_mapping_error(&ndev->dev, addr))) { + stats->tx_dropped++; + stats->tx_errors++; + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + dma_unmap_addr_set(&priv->tx_buff[*txbd_curr], mapping, addr); + dma_unmap_len_set(&priv->tx_buff[*txbd_curr], len, len); + + priv->tx_buff[*txbd_curr].skb = skb; + priv->txbd[*txbd_curr].data = cpu_to_le32(skb->data); + + /* Make sure pointer to data buffer is set */ + wmb(); + + *info = cpu_to_le32(FOR_EMAC | FIRST_OR_LAST_MASK | len); + + /* Increment index to point to the next BD */ + *txbd_curr = (*txbd_curr + 1) % TX_BD_NUM; + + /* Get "info" of the next BD */ + info = &priv->txbd[*txbd_curr].info; + + /* Check if if Tx BD ring is full - next BD is still owned by EMAC */ + if (unlikely((le32_to_cpu(*info) & OWN_MASK) == FOR_EMAC)) + netif_stop_queue(ndev); + + arc_reg_set(priv, R_STATUS, TXPL_MASK); + + skb_tx_timestamp(skb); + + return NETDEV_TX_OK; +} + +/** + * arc_emac_set_address - Set the MAC address for this device. + * @ndev: Pointer to net_device structure. + * @p: 6 byte Address to be written as MAC address. + * + * This function copies the HW address from the sockaddr structure to the + * net_device structure and updates the address in HW. + * + * returns: -EBUSY if the net device is busy or 0 if the address is set + * successfully. + */ +static int arc_emac_set_address(struct net_device *ndev, void *p) +{ + struct arc_emac_priv *priv = netdev_priv(ndev); + struct sockaddr *addr = p; + unsigned int addr_low, addr_hi; + + if (netif_running(ndev)) + return -EBUSY; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(ndev->dev_addr, addr->sa_data, ndev->addr_len); + + addr_low = le32_to_cpu(*(__le32 *) &ndev->dev_addr[0]); + addr_hi = le16_to_cpu(*(__le16 *) &ndev->dev_addr[4]); + + arc_reg_set(priv, R_ADDRL, addr_low); + arc_reg_set(priv, R_ADDRH, addr_hi); + + return 0; +} + +static const struct net_device_ops arc_emac_netdev_ops = { + .ndo_open = arc_emac_open, + .ndo_stop = arc_emac_stop, + .ndo_start_xmit = arc_emac_tx, + .ndo_set_mac_address = arc_emac_set_address, + .ndo_get_stats = arc_emac_stats, +}; + +static int arc_emac_probe(struct platform_device *pdev) +{ + struct resource res_regs, res_irq; + struct device_node *phy_node; + struct arc_emac_priv *priv; + struct net_device *ndev; + const char *mac_addr; + unsigned int id, clock_frequency; + int err; + + if (!pdev->dev.of_node) + return -ENODEV; + + /* Get PHY from device tree */ + phy_node = of_parse_phandle(pdev->dev.of_node, "phy", 0); + if (!phy_node) { + dev_err(&pdev->dev, "failed to retrieve phy description from device tree\n"); + return -ENODEV; + } + + /* Get EMAC registers base address from device tree */ + err = of_address_to_resource(pdev->dev.of_node, 0, &res_regs); + if (err) { + dev_err(&pdev->dev, "failed to retrieve registers base from device tree\n"); + return -ENODEV; + } + + /* Get CPU clock frequency from device tree */ + if (of_property_read_u32(pdev->dev.of_node, "clock-frequency", + &clock_frequency)) { + dev_err(&pdev->dev, "failed to retrieve from device tree\n"); + return -EINVAL; + } + + /* Get IRQ from device tree */ + err = of_irq_to_resource(pdev->dev.of_node, 0, &res_irq); + if (!err) { + dev_err(&pdev->dev, "failed to retrieve value from device tree\n"); + return -ENODEV; + } + + ndev = alloc_etherdev(sizeof(struct arc_emac_priv)); + if (!ndev) + return -ENOMEM; + + SET_NETDEV_DEV(ndev, &pdev->dev); + + ndev->netdev_ops = &arc_emac_netdev_ops; + ndev->ethtool_ops = &arc_emac_ethtool_ops; + ndev->watchdog_timeo = TX_TIMEOUT; + /* FIXME :: no multicast support yet */ + ndev->flags &= ~IFF_MULTICAST; + + priv = netdev_priv(ndev); + priv->dev = &pdev->dev; + priv->ndev = ndev; + + priv->regs = devm_ioremap_resource(&pdev->dev, &res_regs); + if (IS_ERR(priv->regs)) { + err = PTR_ERR(priv->regs); + goto out; + } + dev_dbg(&pdev->dev, "Registers base address is 0x%p\n", priv->regs); + + id = arc_reg_get(priv, R_ID); + + /* Check for EMAC revision 5 or 7, magic number */ + if (!(id == 0x0005fd02 || id == 0x0007fd02)) { + dev_err(&pdev->dev, "ARC EMAC not detected, id=0x%x\n", id); + err = -ENODEV; + goto out; + } + dev_info(&pdev->dev, "ARC EMAC detected with id: 0x%x\n", id); + + /* Set poll rate so that it polls every 1 ms */ + arc_reg_set(priv, R_POLLRATE, clock_frequency / 1000000); + + /* Get max speed of operation from device tree */ + if (of_property_read_u32(pdev->dev.of_node, "max-speed", + &priv->max_speed)) { + dev_err(&pdev->dev, "failed to retrieve from device tree\n"); + err = -EINVAL; + goto out; + } + + ndev->irq = res_irq.start; + dev_info(&pdev->dev, "IRQ is %d\n", ndev->irq); + + /* Register interrupt handler for device */ + err = devm_request_irq(&pdev->dev, ndev->irq, arc_emac_intr, 0, + ndev->name, ndev); + if (err) { + dev_err(&pdev->dev, "could not allocate IRQ\n"); + goto out; + } + + /* Get MAC address from device tree */ + mac_addr = of_get_mac_address(pdev->dev.of_node); + + if (!mac_addr || !is_valid_ether_addr(mac_addr)) + eth_hw_addr_random(ndev); + else + memcpy(ndev->dev_addr, mac_addr, ETH_ALEN); + + dev_info(&pdev->dev, "MAC address is now %pM\n", ndev->dev_addr); + + /* Do 1 allocation instead of 2 separate ones for Rx and Tx BD rings */ + priv->rxbd = dmam_alloc_coherent(&pdev->dev, RX_RING_SZ + TX_RING_SZ, + &priv->rxbd_dma, GFP_KERNEL); + + if (!priv->rxbd) { + dev_err(&pdev->dev, "failed to allocate data buffers\n"); + err = -ENOMEM; + goto out; + } + + priv->txbd = priv->rxbd + RX_BD_NUM; + + priv->txbd_dma = priv->rxbd_dma + RX_RING_SZ; + dev_dbg(&pdev->dev, "EMAC Device addr: Rx Ring [0x%x], Tx Ring[%x]\n", + (unsigned int)priv->rxbd_dma, (unsigned int)priv->txbd_dma); + + err = arc_mdio_probe(pdev, priv); + if (err) { + dev_err(&pdev->dev, "failed to probe MII bus\n"); + goto out; + } + + priv->phy_dev = of_phy_connect(ndev, phy_node, arc_emac_adjust_link, 0, + PHY_INTERFACE_MODE_MII); + if (!priv->phy_dev) { + dev_err(&pdev->dev, "of_phy_connect() failed\n"); + err = -ENODEV; + goto out; + } + + dev_info(&pdev->dev, "connected to %s phy with id 0x%x\n", + priv->phy_dev->drv->name, priv->phy_dev->phy_id); + + netif_napi_add(ndev, &priv->napi, arc_emac_poll, ARC_EMAC_NAPI_WEIGHT); + + err = register_netdev(ndev); + if (err) { + netif_napi_del(&priv->napi); + dev_err(&pdev->dev, "failed to register network device\n"); + goto out; + } + + return 0; + +out: + free_netdev(ndev); + return err; +} + +static int arc_emac_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct arc_emac_priv *priv = netdev_priv(ndev); + + phy_disconnect(priv->phy_dev); + priv->phy_dev = NULL; + arc_mdio_remove(priv); + unregister_netdev(ndev); + netif_napi_del(&priv->napi); + free_netdev(ndev); + + return 0; +} + +static const struct of_device_id arc_emac_dt_ids[] = { + { .compatible = "snps,arc-emac" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, arc_emac_dt_ids); + +static struct platform_driver arc_emac_driver = { + .probe = arc_emac_probe, + .remove = arc_emac_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = arc_emac_dt_ids, + }, +}; + +module_platform_driver(arc_emac_driver); + +MODULE_AUTHOR("Alexey Brodkin "); +MODULE_DESCRIPTION("ARC EMAC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/arc/emac_mdio.c b/drivers/net/ethernet/arc/emac_mdio.c new file mode 100644 index 00000000000..26ba2423f33 --- /dev/null +++ b/drivers/net/ethernet/arc/emac_mdio.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2004-2013 Synopsys, Inc. (www.synopsys.com) + * + * MDIO implementation for ARC EMAC + */ + +#include +#include +#include + +#include "emac.h" + +/* Number of seconds we wait for "MDIO complete" flag to appear */ +#define ARC_MDIO_COMPLETE_POLL_COUNT 1 + +/** + * arc_mdio_complete_wait - Waits until MDIO transaction is completed. + * @priv: Pointer to ARC EMAC private data structure. + * + * returns: 0 on success, -ETIMEDOUT on a timeout. + */ +static int arc_mdio_complete_wait(struct arc_emac_priv *priv) +{ + unsigned int i; + + for (i = 0; i < ARC_MDIO_COMPLETE_POLL_COUNT * 40; i++) { + unsigned int status = arc_reg_get(priv, R_STATUS); + + status &= MDIO_MASK; + + if (status) { + /* Reset "MDIO complete" flag */ + arc_reg_set(priv, R_STATUS, status); + return 0; + } + + msleep(25); + } + + return -ETIMEDOUT; +} + +/** + * arc_mdio_read - MDIO interface read function. + * @bus: Pointer to MII bus structure. + * @phy_addr: Address of the PHY device. + * @reg_num: PHY register to read. + * + * returns: The register contents on success, -ETIMEDOUT on a timeout. + * + * Reads the contents of the requested register from the requested PHY + * address. + */ +static int arc_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) +{ + struct arc_emac_priv *priv = bus->priv; + unsigned int value; + int error; + + arc_reg_set(priv, R_MDIO, + 0x60020000 | (phy_addr << 23) | (reg_num << 18)); + + error = arc_mdio_complete_wait(priv); + if (error < 0) + return error; + + value = arc_reg_get(priv, R_MDIO) & 0xffff; + + dev_dbg(priv->dev, "arc_mdio_read(phy_addr=%i, reg_num=%x) = %x\n", + phy_addr, reg_num, value); + + return value; +} + +/** + * arc_mdio_write - MDIO interface write function. + * @bus: Pointer to MII bus structure. + * @phy_addr: Address of the PHY device. + * @reg_num: PHY register to write to. + * @value: Value to be written into the register. + * + * returns: 0 on success, -ETIMEDOUT on a timeout. + * + * Writes the value to the requested register. + */ +static int arc_mdio_write(struct mii_bus *bus, int phy_addr, + int reg_num, u16 value) +{ + struct arc_emac_priv *priv = bus->priv; + + dev_dbg(priv->dev, + "arc_mdio_write(phy_addr=%i, reg_num=%x, value=%x)\n", + phy_addr, reg_num, value); + + arc_reg_set(priv, R_MDIO, + 0x50020000 | (phy_addr << 23) | (reg_num << 18) | value); + + return arc_mdio_complete_wait(priv); +} + +/** + * arc_mdio_probe - MDIO probe function. + * @pdev: Pointer to platform device. + * @priv: Pointer to ARC EMAC private data structure. + * + * returns: 0 on success, -ENOMEM when mdiobus_alloc + * (to allocate memory for MII bus structure) fails. + * + * Sets up and registers the MDIO interface. + */ +int arc_mdio_probe(struct platform_device *pdev, struct arc_emac_priv *priv) +{ + struct mii_bus *bus; + int error; + + bus = mdiobus_alloc(); + if (!bus) + return -ENOMEM; + + priv->bus = bus; + bus->priv = priv; + bus->parent = priv->dev; + bus->name = "Synopsys MII Bus", + bus->read = &arc_mdio_read; + bus->write = &arc_mdio_write; + + snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name); + + error = of_mdiobus_register(bus, pdev->dev.of_node); + if (error) { + dev_err(priv->dev, "cannot register MDIO bus %s\n", bus->name); + mdiobus_free(bus); + return error; + } + + return 0; +} + +/** + * arc_mdio_remove - MDIO remove function. + * @priv: Pointer to ARC EMAC private data structure. + * + * Unregisters the MDIO and frees any associate memory for MII bus. + */ +int arc_mdio_remove(struct arc_emac_priv *priv) +{ + mdiobus_unregister(priv->bus); + mdiobus_free(priv->bus); + priv->bus = NULL; + + return 0; +} -- cgit v1.2.3-70-g09d2 From b7c4114b07bbacfe0aee1d04ad1ade9e42309620 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 10 Jun 2013 23:12:57 -0300 Subject: can: flexcan: Use a regulator to control the CAN transceiver Instead of using a GPIO to turn on/off the CAN transceiver, it is better to use a regulator as some systems may use a PMIC to power the CAN transceiver. Acked-by: Shawn Guo Signed-off-by: Fabio Estevam Signed-off-by: Marc Kleine-Budde --- .../devicetree/bindings/net/can/fsl-flexcan.txt | 2 + arch/arm/boot/dts/imx28-evk.dts | 12 ++++++ arch/arm/mach-mxs/mach-mxs.c | 50 +--------------------- drivers/net/can/flexcan.c | 25 +++++------ 4 files changed, 28 insertions(+), 61 deletions(-) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/can/fsl-flexcan.txt b/Documentation/devicetree/bindings/net/can/fsl-flexcan.txt index 8ff324eaa88..56d6cc336e1 100644 --- a/Documentation/devicetree/bindings/net/can/fsl-flexcan.txt +++ b/Documentation/devicetree/bindings/net/can/fsl-flexcan.txt @@ -16,6 +16,8 @@ Optional properties: - clock-frequency : The oscillator frequency driving the flexcan device +- xceiver-supply: Regulator that powers the CAN transceiver + Example: can@1c000 { diff --git a/arch/arm/boot/dts/imx28-evk.dts b/arch/arm/boot/dts/imx28-evk.dts index 3637bf3b1d5..1f0d38d7b16 100644 --- a/arch/arm/boot/dts/imx28-evk.dts +++ b/arch/arm/boot/dts/imx28-evk.dts @@ -155,12 +155,14 @@ can0: can@80032000 { pinctrl-names = "default"; pinctrl-0 = <&can0_pins_a>; + xceiver-supply = <®_can_3v3>; status = "okay"; }; can1: can@80034000 { pinctrl-names = "default"; pinctrl-0 = <&can1_pins_a>; + xceiver-supply = <®_can_3v3>; status = "okay"; }; }; @@ -319,6 +321,16 @@ gpio = <&gpio3 30 0>; enable-active-high; }; + + reg_can_3v3: can-3v3 { + compatible = "regulator-fixed"; + regulator-name = "can-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + gpio = <&gpio2 13 0>; + enable-active-high; + }; + }; sound { diff --git a/arch/arm/mach-mxs/mach-mxs.c b/arch/arm/mach-mxs/mach-mxs.c index 5b62b6489d4..97b8a44101c 100644 --- a/arch/arm/mach-mxs/mach-mxs.c +++ b/arch/arm/mach-mxs/mach-mxs.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -60,41 +59,6 @@ static inline void __mxs_togl(u32 mask, void __iomem *reg) __raw_writel(mask, reg + MXS_TOG_ADDR); } -/* - * MX28EVK_FLEXCAN_SWITCH is shared between both flexcan controllers - */ -#define MX28EVK_FLEXCAN_SWITCH MXS_GPIO_NR(2, 13) - -static int flexcan0_en, flexcan1_en; - -static void mx28evk_flexcan_switch(void) -{ - if (flexcan0_en || flexcan1_en) - gpio_set_value(MX28EVK_FLEXCAN_SWITCH, 1); - else - gpio_set_value(MX28EVK_FLEXCAN_SWITCH, 0); -} - -static void mx28evk_flexcan0_switch(int enable) -{ - flexcan0_en = enable; - mx28evk_flexcan_switch(); -} - -static void mx28evk_flexcan1_switch(int enable) -{ - flexcan1_en = enable; - mx28evk_flexcan_switch(); -} - -static struct flexcan_platform_data flexcan_pdata[2]; - -static struct of_dev_auxdata mxs_auxdata_lookup[] __initdata = { - OF_DEV_AUXDATA("fsl,imx28-flexcan", 0x80032000, NULL, &flexcan_pdata[0]), - OF_DEV_AUXDATA("fsl,imx28-flexcan", 0x80034000, NULL, &flexcan_pdata[1]), - { /* sentinel */ } -}; - #define OCOTP_WORD_OFFSET 0x20 #define OCOTP_WORD_COUNT 0x20 @@ -254,15 +218,6 @@ static void __init imx28_evk_init(void) mxs_saif_clkmux_select(MXS_DIGCTL_SAIF_CLKMUX_EXTMSTR0); } -static void __init imx28_evk_post_init(void) -{ - if (!gpio_request_one(MX28EVK_FLEXCAN_SWITCH, GPIOF_DIR_OUT, - "flexcan-switch")) { - flexcan_pdata[0].transceiver_switch = mx28evk_flexcan0_switch; - flexcan_pdata[1].transceiver_switch = mx28evk_flexcan1_switch; - } -} - static int apx4devkit_phy_fixup(struct phy_device *phy) { phy->dev_flags |= MICREL_PHY_50MHZ_CLK; @@ -374,13 +329,10 @@ static void __init mxs_machine_init(void) cfa10049_init(); of_platform_populate(NULL, of_default_bus_match_table, - mxs_auxdata_lookup, NULL); + NULL, NULL); if (of_machine_is_compatible("karo,tx28")) tx28_post_init(); - - if (of_machine_is_compatible("fsl,imx28-evk")) - imx28_evk_post_init(); } #define MX23_CLKCTRL_RESET_OFFSET 0x120 diff --git a/drivers/net/can/flexcan.c b/drivers/net/can/flexcan.c index f873b9f8d4d..7b0be0910f4 100644 --- a/drivers/net/can/flexcan.c +++ b/drivers/net/can/flexcan.c @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -37,6 +36,7 @@ #include #include #include +#include #define DRV_NAME "flexcan" @@ -211,6 +211,7 @@ struct flexcan_priv { struct clk *clk_per; struct flexcan_platform_data *pdata; const struct flexcan_devtype_data *devtype_data; + struct regulator *reg_xceiver; }; static struct flexcan_devtype_data fsl_p1010_devtype_data = { @@ -258,15 +259,6 @@ static inline void flexcan_write(u32 val, void __iomem *addr) } #endif -/* - * Swtich transceiver on or off - */ -static void flexcan_transceiver_switch(const struct flexcan_priv *priv, int on) -{ - if (priv->pdata && priv->pdata->transceiver_switch) - priv->pdata->transceiver_switch(on); -} - static inline int flexcan_has_and_handle_berr(const struct flexcan_priv *priv, u32 reg_esr) { @@ -799,7 +791,11 @@ static int flexcan_chip_start(struct net_device *dev) if (priv->devtype_data->features & FLEXCAN_HAS_V10_FEATURES) flexcan_write(0x0, ®s->rxfgmask); - flexcan_transceiver_switch(priv, 1); + if (priv->reg_xceiver) { + err = regulator_enable(priv->reg_xceiver); + if (err) + goto out; + } /* synchronize with the can bus */ reg_mcr = flexcan_read(®s->mcr); @@ -842,7 +838,8 @@ static void flexcan_chip_stop(struct net_device *dev) reg |= FLEXCAN_MCR_MDIS | FLEXCAN_MCR_HALT; flexcan_write(reg, ®s->mcr); - flexcan_transceiver_switch(priv, 0); + if (priv->reg_xceiver) + regulator_disable(priv->reg_xceiver); priv->can.state = CAN_STATE_STOPPED; return; @@ -1084,6 +1081,10 @@ static int flexcan_probe(struct platform_device *pdev) priv->pdata = pdev->dev.platform_data; priv->devtype_data = devtype_data; + priv->reg_xceiver = devm_regulator_get(&pdev->dev, "xceiver"); + if (IS_ERR(priv->reg_xceiver)) + priv->reg_xceiver = NULL; + netif_napi_add(dev, &priv->napi, flexcan_poll, FLEXCAN_NAPI_WEIGHT); dev_set_drvdata(&pdev->dev, dev); -- cgit v1.2.3-70-g09d2 From 25c83b5c2e82560406d5265f42cd147f1eb441d0 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 4 Jul 2013 10:35:41 +0100 Subject: dt:net:stmmac: Add support to dwmac version 3.610 and 3.710 This patch adds dt support to dwmac version 3.610 and 3.710 these versions are integrated in STiH415 and STiH416 ARM A9 SOCs. To support these IP version, some of the device tree properties are extended. Signed-off-by: Srinivas Kandagatla Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/stmmac.txt | 4 ++++ .../net/ethernet/stmicro/stmmac/stmmac_platform.c | 26 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/stmmac.txt b/Documentation/devicetree/bindings/net/stmmac.txt index 060bbf098ef..e1ddfcc46a4 100644 --- a/Documentation/devicetree/bindings/net/stmmac.txt +++ b/Documentation/devicetree/bindings/net/stmmac.txt @@ -12,6 +12,10 @@ Required properties: property - phy-mode: String, operation mode of the PHY interface. Supported values are: "mii", "rmii", "gmii", "rgmii". +- snps,phy-addr phy address to connect to. +- snps,pbl Programmable Burst Length +- snps,fixed-burst Program the DMA to use the fixed burst mode +- snps,mixed-burst Program the DMA to use the mixed burst mode Optional properties: - mac-address: 6 bytes, mac address diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index adfb9a3ce6c..03de76c7a17 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -34,12 +34,20 @@ static int stmmac_probe_config_dt(struct platform_device *pdev, const char **mac) { struct device_node *np = pdev->dev.of_node; + struct stmmac_dma_cfg *dma_cfg; if (!np) return -ENODEV; *mac = of_get_mac_address(np); plat->interface = of_get_phy_mode(np); + + plat->bus_id = of_alias_get_id(np, "ethernet"); + if (plat->bus_id < 0) + plat->bus_id = 0; + + of_property_read_u32(np, "snps,phy-addr", &plat->phy_addr); + plat->mdio_bus_data = devm_kzalloc(&pdev->dev, sizeof(struct stmmac_mdio_bus_data), GFP_KERNEL); @@ -56,6 +64,22 @@ static int stmmac_probe_config_dt(struct platform_device *pdev, plat->pmt = 1; } + if (of_device_is_compatible(np, "snps,dwmac-3.610") || + of_device_is_compatible(np, "snps,dwmac-3.710")) { + plat->enh_desc = 1; + plat->bugged_jumbo = 1; + plat->force_sf_dma_mode = 1; + } + + dma_cfg = devm_kzalloc(&pdev->dev, sizeof(*dma_cfg), GFP_KERNEL); + if (!dma_cfg) + return -ENOMEM; + + plat->dma_cfg = dma_cfg; + of_property_read_u32(np, "snps,pbl", &dma_cfg->pbl); + dma_cfg->fixed_burst = of_property_read_bool(np, "snps,fixed-burst"); + dma_cfg->mixed_burst = of_property_read_bool(np, "snps,mixed-burst"); + return 0; } #else @@ -228,7 +252,9 @@ static const struct dev_pm_ops stmmac_pltfr_pm_ops; static const struct of_device_id stmmac_dt_ids[] = { { .compatible = "st,spear600-gmac"}, + { .compatible = "snps,dwmac-3.610"}, { .compatible = "snps,dwmac-3.70a"}, + { .compatible = "snps,dwmac-3.710"}, { .compatible = "snps,dwmac"}, { /* sentinel */ } }; -- cgit v1.2.3-70-g09d2 From 0e0764715d8116484d808f5b3985ca043080788e Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 4 Jul 2013 10:35:48 +0100 Subject: dt:net:stmmac: Add dt specific phy reset callback support. This patch adds phy reset callback support for stmmac driver via device trees. It adds three new properties to gmac device tree bindings to define the reset signal via gpio. With this patch users can conveniently pass reset gpio number with pre, pulse and post delay in micro secs via DTs. active low: _________ ____________ | | | | |_______________| active high: ________________ | | | | ________| |___________ Signed-off-by: Srinivas Kandagatla Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/stmmac.txt | 6 +++ drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c | 48 ++++++++++++++++++++++- include/linux/stmmac.h | 4 ++ 3 files changed, 56 insertions(+), 2 deletions(-) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/net/stmmac.txt b/Documentation/devicetree/bindings/net/stmmac.txt index e1ddfcc46a4..261c563b5f0 100644 --- a/Documentation/devicetree/bindings/net/stmmac.txt +++ b/Documentation/devicetree/bindings/net/stmmac.txt @@ -13,6 +13,12 @@ Required properties: - phy-mode: String, operation mode of the PHY interface. Supported values are: "mii", "rmii", "gmii", "rgmii". - snps,phy-addr phy address to connect to. +- snps,reset-gpio gpio number for phy reset. +- snps,reset-active-low boolean flag to indicate if phy reset is active low. +- snps,reset-delays-us is triplet of delays + The 1st cell is reset pre-delay in micro seconds. + The 2nd cell is reset pulse in micro seconds. + The 3rd cell is reset post-delay in micro seconds. - snps,pbl Programmable Burst Length - snps,fixed-burst Program the DMA to use the fixed burst mode - snps,mixed-burst Program the DMA to use the mixed burst mode diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c index cc15039eaa4..fe7bc990386 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c @@ -27,6 +27,9 @@ #include #include #include +#include +#include + #include #include "stmmac.h" @@ -131,10 +134,46 @@ static int stmmac_mdio_reset(struct mii_bus *bus) struct net_device *ndev = bus->priv; struct stmmac_priv *priv = netdev_priv(ndev); unsigned int mii_address = priv->hw->mii.addr; + struct stmmac_mdio_bus_data *data = priv->plat->mdio_bus_data; + +#ifdef CONFIG_OF + if (priv->device->of_node) { + int reset_gpio, active_low; + + if (data->reset_gpio < 0) { + struct device_node *np = priv->device->of_node; + if (!np) + return 0; + + data->reset_gpio = of_get_named_gpio(np, + "snps,reset-gpio", 0); + if (data->reset_gpio < 0) + return 0; + + data->active_low = of_property_read_bool(np, + "snps,reset-active-low"); + of_property_read_u32_array(np, + "snps,reset-delays-us", data->delays, 3); + } + + reset_gpio = data->reset_gpio; + active_low = data->active_low; + + if (!gpio_request(reset_gpio, "mdio-reset")) { + gpio_direction_output(reset_gpio, active_low ? 1 : 0); + udelay(data->delays[0]); + gpio_set_value(reset_gpio, active_low ? 0 : 1); + udelay(data->delays[1]); + gpio_set_value(reset_gpio, active_low ? 1 : 0); + udelay(data->delays[2]); + gpio_free(reset_gpio); + } + } +#endif - if (priv->plat->mdio_bus_data->phy_reset) { + if (data->phy_reset) { pr_debug("stmmac_mdio_reset: calling phy_reset\n"); - priv->plat->mdio_bus_data->phy_reset(priv->plat->bsp_priv); + data->phy_reset(priv->plat->bsp_priv); } /* This is a workaround for problems with the STE101P PHY. @@ -172,6 +211,11 @@ int stmmac_mdio_register(struct net_device *ndev) else irqlist = priv->mii_irq; +#ifdef CONFIG_OF + if (priv->device->of_node) + mdio_bus_data->reset_gpio = -1; +#endif + new_bus->name = "stmmac"; new_bus->read = &stmmac_mdio_read; new_bus->write = &stmmac_mdio_write; diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index c1b3ed3fb78..9e495d31516 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -80,6 +80,10 @@ struct stmmac_mdio_bus_data { unsigned int phy_mask; int *irqs; int probed_phy_irq; +#ifdef CONFIG_OF + int reset_gpio, active_low; + u32 delays[3]; +#endif }; struct stmmac_dma_cfg { -- cgit v1.2.3-70-g09d2