From 8c5ad1d6179df31eea1b736c88296db6e3ffd25d Mon Sep 17 00:00:00 2001 From: Andrew Lunn Date: Sat, 4 Jun 2016 21:17:09 +0200 Subject: net: dsa: Document new binding Add the new binding to the documentation of the existing binding. Mark the old binding as deprecated. Signed-off-by: Andrew Lunn Signed-off-by: Florian Fainelli Reviewed-by: Vivien Didelot Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/dsa/dsa.txt | 278 +++++++++++++++++++++- 1 file changed, 276 insertions(+), 2 deletions(-) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/dsa/dsa.txt b/Documentation/devicetree/bindings/net/dsa/dsa.txt index 9f4807f90c31..9bbbe7f87d67 100644 --- a/Documentation/devicetree/bindings/net/dsa/dsa.txt +++ b/Documentation/devicetree/bindings/net/dsa/dsa.txt @@ -1,5 +1,279 @@ -Marvell Distributed Switch Architecture Device Tree Bindings ------------------------------------------------------------- +Distributed Switch Architecture Device Tree Bindings +---------------------------------------------------- + +Two bindings exist, one of which has been deprecated due to +limitations. + +Current Binding +--------------- + +Switches are true Linux devices and can be probes by any means. Once +probed, they register to the DSA framework, passing a node +pointer. This node is expected to fulfil the following binding, and +may contain additional properties as required by the device it is +embedded within. + +Required properties: + +- ports : A container for child nodes representing switch ports. + +Optional properties: + +- dsa,member : A two element list indicates which DSA cluster, and position + within the cluster a switch takes. <0 0> is cluster 0, + switch 0. <0 1> is cluster 0, switch 1. <1 0> is cluster 1, + switch 0. A switch not part of any cluster (single device + hanging off a CPU port) must not specify this property + +The ports container has the following properties + +Required properties: + +- #address-cells : Must be 1 +- #size-cells : Must be 0 + +Each port children node must have the following mandatory properties: +- reg : Describes the port address in the switch +- label : Describes the label associated with this port, which + will become the netdev name. Special labels are + "cpu" to indicate a CPU port and "dsa" to + indicate an uplink/downlink port between switches in + the cluster. + +A port labelled "dsa" has the following mandatory property: + +- link : Should be a list of phandles to other switch's DSA + port. This port is used as the outgoing port + towards the phandle ports. The full routing + information must be given, not just the one hop + routes to neighbouring switches. + +A port labelled "cpu" has the following mandatory property: + +- ethernet : Should be a phandle to a valid Ethernet device node. + This host device is what the switch port is + connected to. + +Port child nodes may also contain the following optional standardised +properties, described in binding documents: + +- phy-handle : Phandle to a PHY on an MDIO bus. See + Documentation/devicetree/bindings/net/ethernet.txt + for details. + +- phy-mode : See + Documentation/devicetree/bindings/net/ethernet.txt + for details. + +- fixed-link : Fixed-link subnode describing a link to a non-MDIO + managed entity. See + Documentation/devicetree/bindings/net/fixed-link.txt + for details. + +Example + +The following example shows three switches on three MDIO busses, +linked into one DSA cluster. + +&mdio1 { + #address-cells = <1>; + #size-cells = <0>; + + switch0: switch0@0 { + compatible = "marvell,mv88e6085"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + dsa,member = <0 0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "lan0"; + }; + + port@1 { + reg = <1>; + label = "lan1"; + }; + + port@2 { + reg = <2>; + label = "lan2"; + }; + + switch0port5: port@5 { + reg = <5>; + label = "dsa"; + phy-mode = "rgmii-txid"; + link = <&switch1port6 + &switch2port9>; + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + port@6 { + reg = <6>; + label = "cpu"; + ethernet = <&fec1>; + fixed-link { + speed = <100>; + full-duplex; + }; + }; + }; + }; +}; + +&mdio2 { + #address-cells = <1>; + #size-cells = <0>; + + switch1: switch1@0 { + compatible = "marvell,mv88e6085"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + dsa,member = <0 1>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "lan3"; + phy-handle = <&switch1phy0>; + }; + + port@1 { + reg = <1>; + label = "lan4"; + phy-handle = <&switch1phy1>; + }; + + port@2 { + reg = <2>; + label = "lan5"; + phy-handle = <&switch1phy2>; + }; + + switch1port5: port@5 { + reg = <5>; + label = "dsa"; + link = <&switch2port9>; + phy-mode = "rgmii-txid"; + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + + switch1port6: port@6 { + reg = <6>; + label = "dsa"; + phy-mode = "rgmii-txid"; + link = <&switch0port5>; + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + }; + mdio-bus { + #address-cells = <1>; + #size-cells = <0>; + switch1phy0: switch1phy0@0 { + reg = <0>; + }; + switch1phy1: switch1phy0@1 { + reg = <1>; + }; + switch1phy2: switch1phy0@2 { + reg = <2>; + }; + }; + }; +}; + +&mdio4 { + #address-cells = <1>; + #size-cells = <0>; + + switch2: switch2@0 { + compatible = "marvell,mv88e6085"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + dsa,member = <0 2>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + label = "lan6"; + }; + + port@1 { + reg = <1>; + label = "lan7"; + }; + + port@2 { + reg = <2>; + label = "lan8"; + }; + + port@3 { + reg = <3>; + label = "optical3"; + fixed-link { + speed = <1000>; + full-duplex; + link-gpios = <&gpio6 2 + GPIO_ACTIVE_HIGH>; + }; + }; + + port@4 { + reg = <4>; + label = "optical4"; + fixed-link { + speed = <1000>; + full-duplex; + link-gpios = <&gpio6 3 + GPIO_ACTIVE_HIGH>; + }; + }; + + switch2port9: port@9 { + reg = <9>; + label = "dsa"; + phy-mode = "rgmii-txid"; + link = <&switch1port5 + &switch0port5>; + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + }; + }; +}; + +Deprecated Binding +------------------ + +The deprecated binding makes use of a platform device to represent the +switches. The switches themselves are not Linux devices, and make use +of an MDIO bus for management. Required properties: - compatible : Should be "marvell,dsa" -- cgit v1.2.3 From 967dd82ffc52e9d8ea0defde094f9a39a3f4eeed Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Thu, 9 Jun 2016 18:23:53 -0700 Subject: net: dsa: b53: Add support for Broadcom RoboSwitch This patch adds support for Broadcom's BCM53xx switch family, also known as RoboSwitch. Some of these switches are ubiquituous, found in home routers, Wi-Fi routers, DSL and cable modem gateways and other networking related products. This drivers adds the library driver (b53_common.c) as well as a few bus glue drivers for MDIO, SPI, Switch Register Access Block (SRAB) and memory-mapped I/O into a SoC's address space (Broadcom BCM63xx/33xx). Basic operations are supported to bring the Layer 1/2 up and running, but not much more at this point, subsequent patches add the remaining features. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/dsa/b53.txt | 88 ++ MAINTAINERS | 8 + drivers/net/dsa/Kconfig | 2 + drivers/net/dsa/Makefile | 2 + drivers/net/dsa/b53/Kconfig | 33 + drivers/net/dsa/b53/Makefile | 6 + drivers/net/dsa/b53/b53_common.c | 1158 +++++++++++++++++++++ drivers/net/dsa/b53/b53_mdio.c | 381 +++++++ drivers/net/dsa/b53/b53_mmap.c | 260 +++++ drivers/net/dsa/b53/b53_priv.h | 322 ++++++ drivers/net/dsa/b53/b53_regs.h | 358 +++++++ drivers/net/dsa/b53/b53_spi.c | 331 ++++++ drivers/net/dsa/b53/b53_srab.c | 415 ++++++++ include/linux/platform_data/b53.h | 33 + 14 files changed, 3397 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/dsa/b53.txt create mode 100644 drivers/net/dsa/b53/Kconfig create mode 100644 drivers/net/dsa/b53/Makefile create mode 100644 drivers/net/dsa/b53/b53_common.c create mode 100644 drivers/net/dsa/b53/b53_mdio.c create mode 100644 drivers/net/dsa/b53/b53_mmap.c create mode 100644 drivers/net/dsa/b53/b53_priv.h create mode 100644 drivers/net/dsa/b53/b53_regs.h create mode 100644 drivers/net/dsa/b53/b53_spi.c create mode 100644 drivers/net/dsa/b53/b53_srab.c create mode 100644 include/linux/platform_data/b53.h (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/dsa/b53.txt b/Documentation/devicetree/bindings/net/dsa/b53.txt new file mode 100644 index 000000000000..ca752db14dff --- /dev/null +++ b/Documentation/devicetree/bindings/net/dsa/b53.txt @@ -0,0 +1,88 @@ +Broadcom BCM53xx Ethernet switches +================================== + +Required properties: + +- compatible: For external switch chips, compatible string must be exactly one + of: "brcm,bcm5325" + "brcm,bcm53115" + "brcm,bcm53125" + "brcm,bcm53128" + "brcm,bcm5365" + "brcm,bcm5395" + "brcm,bcm5397" + "brcm,bcm5398" + + For the BCM5310x SoCs with an integrated switch, must be one of: + "brcm,bcm53010-srab" + "brcm,bcm53011-srab" + "brcm,bcm53012-srab" + "brcm,bcm53018-srab" + "brcm,bcm53019-srab" and the mandatory "brcm,bcm5301x-srab" string + + For the BCM63xx/33xx SoCs with an integrated switch, must be one of: + "brcm,bcm3384-switch" + "brcm,bcm6328-switch" + "brcm,bcm6368-switch" and the mandatory "brcm,bcm63xx-switch" + +See Documentation/devicetree/bindings/dsa/dsa.txt for a list of additional +required and optional properties. + +Examples: + +Ethernet switch connected via MDIO to the host, CPU port wired to eth0: + + eth0: ethernet@10001000 { + compatible = "brcm,unimac"; + reg = <0x10001000 0x1000>; + + fixed-link { + speed = <1000>; + duplex-full; + }; + }; + + mdio0: mdio@10000000 { + compatible = "brcm,unimac-mdio"; + #address-cells = <1>; + #size-cells = <0>; + + switch0: ethernet-switch@30 { + compatible = "brcm,bcm53125"; + #address-cells = <1>; + #size-cells = <0>; + + ports { + port0@0 { + reg = <0>; + label = "lan1"; + }; + + port1@1 { + reg = <1>; + label = "lan2"; + }; + + port5@5 { + reg = <5>; + label = "cable-modem"; + fixed-link { + speed = <1000>; + duplex-full; + }; + phy-mode = "rgmii-txid"; + }; + + port8@8 { + reg = <8>; + label = "cpu"; + fixed-link { + speed = <1000>; + duplex-full; + }; + phy-mode = "rgmii-txid"; + ethernet = <ð0>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 16e1500d7c52..b29a08884eed 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2454,6 +2454,14 @@ L: netdev@vger.kernel.org S: Supported F: drivers/net/ethernet/broadcom/b44.* +BROADCOM B53 ETHERNET SWITCH DRIVER +M: Florian Fainelli +L: netdev@vger.kernel.org +L: openwrt-devel@lists.openwrt.org (subscribers-only) +S: Supported +F: drivers/net/dsa/b53/* +F: include/linux/platform_data/b53.h + BROADCOM GENET ETHERNET DRIVER M: Florian Fainelli L: netdev@vger.kernel.org diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 200663c43ce9..be481e15ec9b 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -28,4 +28,6 @@ config NET_DSA_BCM_SF2 This enables support for the Broadcom Starfighter 2 Ethernet switch chips. +source "drivers/net/dsa/b53/Kconfig" + endmenu diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index 76b751dd9efd..97bc70a7f3c7 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -1,3 +1,5 @@ obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o obj-$(CONFIG_NET_DSA_MV88E6XXX) += mv88e6xxx.o obj-$(CONFIG_NET_DSA_BCM_SF2) += bcm_sf2.o + +obj-y += b53/ diff --git a/drivers/net/dsa/b53/Kconfig b/drivers/net/dsa/b53/Kconfig new file mode 100644 index 000000000000..27f32a50df57 --- /dev/null +++ b/drivers/net/dsa/b53/Kconfig @@ -0,0 +1,33 @@ +menuconfig B53 + tristate "Broadcom BCM53xx managed switch support" + depends on NET_DSA + help + This driver adds support for Broadcom managed switch chips. It supports + BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX + integrated switches. + +config B53_SPI_DRIVER + tristate "B53 SPI connected switch driver" + depends on B53 && SPI + help + Select to enable support for registering switches configured through SPI. + +config B53_MDIO_DRIVER + tristate "B53 MDIO connected switch driver" + depends on B53 + help + Select to enable support for registering switches configured through MDIO. + +config B53_MMAP_DRIVER + tristate "B53 MMAP connected switch driver" + depends on B53 && HAS_IOMEM + help + Select to enable support for memory-mapped switches like the BCM63XX + integrated switches. + +config B53_SRAB_DRIVER + tristate "B53 SRAB connected switch driver" + depends on B53 && HAS_IOMEM + help + Select to enable support for memory-mapped Switch Register Access + Bridge Registers (SRAB) like it is found on the BCM53010 diff --git a/drivers/net/dsa/b53/Makefile b/drivers/net/dsa/b53/Makefile new file mode 100644 index 000000000000..7e6f9a8bfd75 --- /dev/null +++ b/drivers/net/dsa/b53/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_B53) += b53_common.o + +obj-$(CONFIG_B53_SPI_DRIVER) += b53_spi.o +obj-$(CONFIG_B53_MDIO_DRIVER) += b53_mdio.o +obj-$(CONFIG_B53_MMAP_DRIVER) += b53_mmap.o +obj-$(CONFIG_B53_SRAB_DRIVER) += b53_srab.o diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c new file mode 100644 index 000000000000..6f0337d6dfa4 --- /dev/null +++ b/drivers/net/dsa/b53/b53_common.c @@ -0,0 +1,1158 @@ +/* + * B53 switch driver main logic + * + * Copyright (C) 2011-2013 Jonas Gorski + * Copyright (C) 2016 Florian Fainelli + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "b53_regs.h" +#include "b53_priv.h" + +struct b53_mib_desc { + u8 size; + u8 offset; + const char *name; +}; + +/* BCM5365 MIB counters */ +static const struct b53_mib_desc b53_mibs_65[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x44, "RxOctets" }, + { 4, 0x4c, "RxUndersizePkts" }, + { 4, 0x50, "RxPausePkts" }, + { 4, 0x54, "Pkts64Octets" }, + { 4, 0x58, "Pkts65to127Octets" }, + { 4, 0x5c, "Pkts128to255Octets" }, + { 4, 0x60, "Pkts256to511Octets" }, + { 4, 0x64, "Pkts512to1023Octets" }, + { 4, 0x68, "Pkts1024to1522Octets" }, + { 4, 0x6c, "RxOversizePkts" }, + { 4, 0x70, "RxJabbers" }, + { 4, 0x74, "RxAlignmentErrors" }, + { 4, 0x78, "RxFCSErrors" }, + { 8, 0x7c, "RxGoodOctets" }, + { 4, 0x84, "RxDropPkts" }, + { 4, 0x88, "RxUnicastPkts" }, + { 4, 0x8c, "RxMulticastPkts" }, + { 4, 0x90, "RxBroadcastPkts" }, + { 4, 0x94, "RxSAChanges" }, + { 4, 0x98, "RxFragments" }, +}; + +#define B53_MIBS_65_SIZE ARRAY_SIZE(b53_mibs_65) + +/* BCM63xx MIB counters */ +static const struct b53_mib_desc b53_mibs_63xx[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x0c, "TxQoSPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x3c, "TxQoSOctets" }, + { 8, 0x44, "RxOctets" }, + { 4, 0x4c, "RxUndersizePkts" }, + { 4, 0x50, "RxPausePkts" }, + { 4, 0x54, "Pkts64Octets" }, + { 4, 0x58, "Pkts65to127Octets" }, + { 4, 0x5c, "Pkts128to255Octets" }, + { 4, 0x60, "Pkts256to511Octets" }, + { 4, 0x64, "Pkts512to1023Octets" }, + { 4, 0x68, "Pkts1024to1522Octets" }, + { 4, 0x6c, "RxOversizePkts" }, + { 4, 0x70, "RxJabbers" }, + { 4, 0x74, "RxAlignmentErrors" }, + { 4, 0x78, "RxFCSErrors" }, + { 8, 0x7c, "RxGoodOctets" }, + { 4, 0x84, "RxDropPkts" }, + { 4, 0x88, "RxUnicastPkts" }, + { 4, 0x8c, "RxMulticastPkts" }, + { 4, 0x90, "RxBroadcastPkts" }, + { 4, 0x94, "RxSAChanges" }, + { 4, 0x98, "RxFragments" }, + { 4, 0xa0, "RxSymbolErrors" }, + { 4, 0xa4, "RxQoSPkts" }, + { 8, 0xa8, "RxQoSOctets" }, + { 4, 0xb0, "Pkts1523to2047Octets" }, + { 4, 0xb4, "Pkts2048to4095Octets" }, + { 4, 0xb8, "Pkts4096to8191Octets" }, + { 4, 0xbc, "Pkts8192to9728Octets" }, + { 4, 0xc0, "RxDiscarded" }, +}; + +#define B53_MIBS_63XX_SIZE ARRAY_SIZE(b53_mibs_63xx) + +/* MIB counters */ +static const struct b53_mib_desc b53_mibs[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x50, "RxOctets" }, + { 4, 0x58, "RxUndersizePkts" }, + { 4, 0x5c, "RxPausePkts" }, + { 4, 0x60, "Pkts64Octets" }, + { 4, 0x64, "Pkts65to127Octets" }, + { 4, 0x68, "Pkts128to255Octets" }, + { 4, 0x6c, "Pkts256to511Octets" }, + { 4, 0x70, "Pkts512to1023Octets" }, + { 4, 0x74, "Pkts1024to1522Octets" }, + { 4, 0x78, "RxOversizePkts" }, + { 4, 0x7c, "RxJabbers" }, + { 4, 0x80, "RxAlignmentErrors" }, + { 4, 0x84, "RxFCSErrors" }, + { 8, 0x88, "RxGoodOctets" }, + { 4, 0x90, "RxDropPkts" }, + { 4, 0x94, "RxUnicastPkts" }, + { 4, 0x98, "RxMulticastPkts" }, + { 4, 0x9c, "RxBroadcastPkts" }, + { 4, 0xa0, "RxSAChanges" }, + { 4, 0xa4, "RxFragments" }, + { 4, 0xa8, "RxJumboPkts" }, + { 4, 0xac, "RxSymbolErrors" }, + { 4, 0xc0, "RxDiscarded" }, +}; + +#define B53_MIBS_SIZE ARRAY_SIZE(b53_mibs) + +static int b53_do_vlan_op(struct b53_device *dev, u8 op) +{ + unsigned int i; + + b53_write8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], VTA_START_CMD | op); + + for (i = 0; i < 10; i++) { + u8 vta; + + b53_read8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], &vta); + if (!(vta & VTA_START_CMD)) + return 0; + + usleep_range(100, 200); + } + + return -EIO; +} + +static void b53_set_vlan_entry(struct b53_device *dev, u16 vid, u16 members, + u16 untag) +{ + if (is5325(dev)) { + u32 entry = 0; + + if (members) { + entry = ((untag & VA_UNTAG_MASK_25) << VA_UNTAG_S_25) | + members; + if (dev->core_rev >= 3) + entry |= VA_VALID_25_R4 | vid << VA_VID_HIGH_S; + else + entry |= VA_VALID_25; + } + + b53_write32(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_25, entry); + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, vid | + VTA_RW_STATE_WR | VTA_RW_OP_EN); + } else if (is5365(dev)) { + u16 entry = 0; + + if (members) + entry = ((untag & VA_UNTAG_MASK_65) << VA_UNTAG_S_65) | + members | VA_VALID_65; + + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_65, entry); + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_65, vid | + VTA_RW_STATE_WR | VTA_RW_OP_EN); + } else { + b53_write16(dev, B53_ARLIO_PAGE, dev->vta_regs[1], vid); + b53_write32(dev, B53_ARLIO_PAGE, dev->vta_regs[2], + (untag << VTE_UNTAG_S) | members); + + b53_do_vlan_op(dev, VTA_CMD_WRITE); + } +} + +void b53_set_forwarding(struct b53_device *dev, int enable) +{ + u8 mgmt; + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (enable) + mgmt |= SM_SW_FWD_EN; + else + mgmt &= ~SM_SW_FWD_EN; + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); +} + +static void b53_enable_vlan(struct b53_device *dev, int enable) +{ + u8 mgmt, vc0, vc1, vc4 = 0, vc5; + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, &vc0); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, &vc1); + + if (is5325(dev) || is5365(dev)) { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, &vc5); + } else if (is63xx(dev)) { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, &vc5); + } else { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, &vc5); + } + + mgmt &= ~SM_SW_FWD_MODE; + + if (enable) { + vc0 |= VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID; + vc1 |= VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN; + vc4 &= ~VC4_ING_VID_CHECK_MASK; + vc4 |= VC4_ING_VID_VIO_DROP << VC4_ING_VID_CHECK_S; + vc5 |= VC5_DROP_VTABLE_MISS; + + if (is5325(dev)) + vc0 &= ~VC0_RESERVED_1; + + if (is5325(dev) || is5365(dev)) + vc1 |= VC1_RX_MCST_TAG_EN; + + if (!is5325(dev) && !is5365(dev)) { + if (dev->allow_vid_4095) + vc5 |= VC5_VID_FFF_EN; + else + vc5 &= ~VC5_VID_FFF_EN; + } + } else { + vc0 &= ~(VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID); + vc1 &= ~(VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN); + vc4 &= ~VC4_ING_VID_CHECK_MASK; + vc5 &= ~VC5_DROP_VTABLE_MISS; + + if (is5325(dev) || is5365(dev)) + vc4 |= VC4_ING_VID_VIO_FWD << VC4_ING_VID_CHECK_S; + else + vc4 |= VC4_ING_VID_VIO_TO_IMP << VC4_ING_VID_CHECK_S; + + if (is5325(dev) || is5365(dev)) + vc1 &= ~VC1_RX_MCST_TAG_EN; + + if (!is5325(dev) && !is5365(dev)) + vc5 &= ~VC5_VID_FFF_EN; + } + + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, vc0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, vc1); + + if (is5325(dev) || is5365(dev)) { + /* enable the high 8 bit vid check on 5325 */ + if (is5325(dev) && enable) + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, + VC3_HIGH_8BIT_EN); + else + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); + + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, vc5); + } else if (is63xx(dev)) { + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3_63XX, 0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, vc5); + } else { + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, vc5); + } + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); +} + +static int b53_set_jumbo(struct b53_device *dev, bool enable, bool allow_10_100) +{ + u32 port_mask = 0; + u16 max_size = JMS_MIN_SIZE; + + if (is5325(dev) || is5365(dev)) + return -EINVAL; + + if (enable) { + port_mask = dev->enabled_ports; + max_size = JMS_MAX_SIZE; + if (allow_10_100) + port_mask |= JPM_10_100_JUMBO_EN; + } + + b53_write32(dev, B53_JUMBO_PAGE, dev->jumbo_pm_reg, port_mask); + return b53_write16(dev, B53_JUMBO_PAGE, dev->jumbo_size_reg, max_size); +} + +static int b53_flush_arl(struct b53_device *dev) +{ + unsigned int i; + + b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, + FAST_AGE_DONE | FAST_AGE_DYNAMIC | FAST_AGE_STATIC); + + for (i = 0; i < 10; i++) { + u8 fast_age_ctrl; + + b53_read8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, + &fast_age_ctrl); + + if (!(fast_age_ctrl & FAST_AGE_DONE)) + goto out; + + msleep(1); + } + + return -ETIMEDOUT; +out: + /* Only age dynamic entries (default behavior) */ + b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, FAST_AGE_DYNAMIC); + return 0; +} + +static int b53_enable_port(struct dsa_switch *ds, int port, + struct phy_device *phy) +{ + struct b53_device *dev = ds_to_priv(ds); + + /* Clear the Rx and Tx disable bits and set to no spanning tree */ + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(port), 0); + + return 0; +} + +static void b53_disable_port(struct dsa_switch *ds, int port, + struct phy_device *phy) +{ + struct b53_device *dev = ds_to_priv(ds); + u8 reg; + + /* Disable Tx/Rx for the port */ + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(port), ®); + reg |= PORT_CTRL_RX_DISABLE | PORT_CTRL_TX_DISABLE; + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(port), reg); +} + +static void b53_enable_cpu_port(struct b53_device *dev) +{ + unsigned int cpu_port = dev->cpu_port; + u8 port_ctrl; + + /* BCM5325 CPU port is at 8 */ + if ((is5325(dev) || is5365(dev)) && cpu_port == B53_CPU_PORT_25) + cpu_port = B53_CPU_PORT; + + port_ctrl = PORT_CTRL_RX_BCST_EN | + PORT_CTRL_RX_MCST_EN | + PORT_CTRL_RX_UCST_EN; + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(cpu_port), port_ctrl); +} + +static void b53_enable_mib(struct b53_device *dev) +{ + u8 gc; + + b53_read8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); + gc &= ~(GC_RESET_MIB | GC_MIB_AC_EN); + b53_write8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc); +} + +static int b53_configure_vlan(struct b53_device *dev) +{ + int i; + + /* clear all vlan entries */ + if (is5325(dev) || is5365(dev)) { + for (i = 1; i < dev->num_vlans; i++) + b53_set_vlan_entry(dev, i, 0, 0); + } else { + b53_do_vlan_op(dev, VTA_CMD_CLEAR); + } + + b53_enable_vlan(dev, false); + + b53_for_each_port(dev, i) + b53_write16(dev, B53_VLAN_PAGE, + B53_VLAN_PORT_DEF_TAG(i), 1); + + if (!is5325(dev) && !is5365(dev)) + b53_set_jumbo(dev, dev->enable_jumbo, false); + + return 0; +} + +static void b53_switch_reset_gpio(struct b53_device *dev) +{ + int gpio = dev->reset_gpio; + + if (gpio < 0) + return; + + /* Reset sequence: RESET low(50ms)->high(20ms) + */ + gpio_set_value(gpio, 0); + mdelay(50); + + gpio_set_value(gpio, 1); + mdelay(20); + + dev->current_page = 0xff; +} + +static int b53_switch_reset(struct b53_device *dev) +{ + u8 mgmt; + + b53_switch_reset_gpio(dev); + + if (is539x(dev)) { + b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x83); + b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x00); + } + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (!(mgmt & SM_SW_FWD_EN)) { + mgmt &= ~SM_SW_FWD_MODE; + mgmt |= SM_SW_FWD_EN; + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (!(mgmt & SM_SW_FWD_EN)) { + dev_err(dev->dev, "Failed to enable switch!\n"); + return -EINVAL; + } + } + + b53_enable_mib(dev); + + return b53_flush_arl(dev); +} + +static int b53_phy_read16(struct dsa_switch *ds, int addr, int reg) +{ + struct b53_device *priv = ds_to_priv(ds); + u16 value = 0; + int ret; + + if (priv->ops->phy_read16) + ret = priv->ops->phy_read16(priv, addr, reg, &value); + else + ret = b53_read16(priv, B53_PORT_MII_PAGE(addr), + reg * 2, &value); + + return ret ? ret : value; +} + +static int b53_phy_write16(struct dsa_switch *ds, int addr, int reg, u16 val) +{ + struct b53_device *priv = ds_to_priv(ds); + + if (priv->ops->phy_write16) + return priv->ops->phy_write16(priv, addr, reg, val); + + return b53_write16(priv, B53_PORT_MII_PAGE(addr), reg * 2, val); +} + +static int b53_reset_switch(struct b53_device *priv) +{ + /* reset vlans */ + priv->enable_jumbo = false; + + memset(priv->ports, 0, sizeof(*priv->ports) * priv->num_ports); + + return b53_switch_reset(priv); +} + +static int b53_apply_config(struct b53_device *priv) +{ + /* disable switching */ + b53_set_forwarding(priv, 0); + + b53_configure_vlan(priv); + + /* enable switching */ + b53_set_forwarding(priv, 1); + + return 0; +} + +static void b53_reset_mib(struct b53_device *priv) +{ + u8 gc; + + b53_read8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); + + b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc | GC_RESET_MIB); + msleep(1); + b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc & ~GC_RESET_MIB); + msleep(1); +} + +static const struct b53_mib_desc *b53_get_mib(struct b53_device *dev) +{ + if (is5365(dev)) + return b53_mibs_65; + else if (is63xx(dev)) + return b53_mibs_63xx; + else + return b53_mibs; +} + +static unsigned int b53_get_mib_size(struct b53_device *dev) +{ + if (is5365(dev)) + return B53_MIBS_65_SIZE; + else if (is63xx(dev)) + return B53_MIBS_63XX_SIZE; + else + return B53_MIBS_SIZE; +} + +static void b53_get_strings(struct dsa_switch *ds, int port, uint8_t *data) +{ + struct b53_device *dev = ds_to_priv(ds); + const struct b53_mib_desc *mibs = b53_get_mib(dev); + unsigned int mib_size = b53_get_mib_size(dev); + unsigned int i; + + for (i = 0; i < mib_size; i++) + memcpy(data + i * ETH_GSTRING_LEN, + mibs[i].name, ETH_GSTRING_LEN); +} + +static void b53_get_ethtool_stats(struct dsa_switch *ds, int port, + uint64_t *data) +{ + struct b53_device *dev = ds_to_priv(ds); + const struct b53_mib_desc *mibs = b53_get_mib(dev); + unsigned int mib_size = b53_get_mib_size(dev); + const struct b53_mib_desc *s; + unsigned int i; + u64 val = 0; + + if (is5365(dev) && port == 5) + port = 8; + + mutex_lock(&dev->stats_mutex); + + for (i = 0; i < mib_size; i++) { + s = &mibs[i]; + + if (mibs->size == 8) { + b53_read64(dev, B53_MIB_PAGE(port), s->offset, &val); + } else { + u32 val32; + + b53_read32(dev, B53_MIB_PAGE(port), s->offset, + &val32); + val = val32; + } + data[i] = (u64)val; + } + + mutex_unlock(&dev->stats_mutex); +} + +static int b53_get_sset_count(struct dsa_switch *ds) +{ + struct b53_device *dev = ds_to_priv(ds); + + return b53_get_mib_size(dev); +} + +static int b53_set_addr(struct dsa_switch *ds, u8 *addr) +{ + return 0; +} + +static int b53_setup(struct dsa_switch *ds) +{ + struct b53_device *dev = ds_to_priv(ds); + unsigned int port; + int ret; + + ret = b53_reset_switch(dev); + if (ret) { + dev_err(ds->dev, "failed to reset switch\n"); + return ret; + } + + b53_reset_mib(dev); + + ret = b53_apply_config(dev); + if (ret) + dev_err(ds->dev, "failed to apply configuration\n"); + + for (port = 0; port < dev->num_ports; port++) { + if (BIT(port) & ds->enabled_port_mask) + b53_enable_port(ds, port, NULL); + else if (dsa_is_cpu_port(ds, port)) + b53_enable_cpu_port(dev); + else + b53_disable_port(ds, port, NULL); + } + + return ret; +} + +static void b53_adjust_link(struct dsa_switch *ds, int port, + struct phy_device *phydev) +{ + struct b53_device *dev = ds_to_priv(ds); + u8 rgmii_ctrl = 0, reg = 0, off; + + if (!phy_is_pseudo_fixed_link(phydev)) + return; + + /* Override the port settings */ + if (port == dev->cpu_port) { + off = B53_PORT_OVERRIDE_CTRL; + reg = PORT_OVERRIDE_EN; + } else { + off = B53_GMII_PORT_OVERRIDE_CTRL(port); + reg = GMII_PO_EN; + } + + /* Set the link UP */ + if (phydev->link) + reg |= PORT_OVERRIDE_LINK; + + if (phydev->duplex == DUPLEX_FULL) + reg |= PORT_OVERRIDE_FULL_DUPLEX; + + switch (phydev->speed) { + case 2000: + reg |= PORT_OVERRIDE_SPEED_2000M; + /* fallthrough */ + case SPEED_1000: + reg |= PORT_OVERRIDE_SPEED_1000M; + break; + case SPEED_100: + reg |= PORT_OVERRIDE_SPEED_100M; + break; + case SPEED_10: + reg |= PORT_OVERRIDE_SPEED_10M; + break; + default: + dev_err(ds->dev, "unknown speed: %d\n", phydev->speed); + return; + } + + /* Enable flow control on BCM5301x's CPU port */ + if (is5301x(dev) && port == dev->cpu_port) + reg |= PORT_OVERRIDE_RX_FLOW | PORT_OVERRIDE_TX_FLOW; + + if (phydev->pause) { + if (phydev->asym_pause) + reg |= PORT_OVERRIDE_TX_FLOW; + reg |= PORT_OVERRIDE_RX_FLOW; + } + + b53_write8(dev, B53_CTRL_PAGE, off, reg); + + if (is531x5(dev) && phy_interface_is_rgmii(phydev)) { + if (port == 8) + off = B53_RGMII_CTRL_IMP; + else + off = B53_RGMII_CTRL_P(port); + + /* Configure the port RGMII clock delay by DLL disabled and + * tx_clk aligned timing (restoring to reset defaults) + */ + b53_read8(dev, B53_CTRL_PAGE, off, &rgmii_ctrl); + rgmii_ctrl &= ~(RGMII_CTRL_DLL_RXC | RGMII_CTRL_DLL_TXC | + RGMII_CTRL_TIMING_SEL); + + /* PHY_INTERFACE_MODE_RGMII_TXID means TX internal delay, make + * sure that we enable the port TX clock internal delay to + * account for this internal delay that is inserted, otherwise + * the switch won't be able to receive correctly. + * + * PHY_INTERFACE_MODE_RGMII means that we are not introducing + * any delay neither on transmission nor reception, so the + * BCM53125 must also be configured accordingly to account for + * the lack of delay and introduce + * + * The BCM53125 switch has its RX clock and TX clock control + * swapped, hence the reason why we modify the TX clock path in + * the "RGMII" case + */ + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) + rgmii_ctrl |= RGMII_CTRL_DLL_TXC; + if (phydev->interface == PHY_INTERFACE_MODE_RGMII) + rgmii_ctrl |= RGMII_CTRL_DLL_TXC | RGMII_CTRL_DLL_RXC; + rgmii_ctrl |= RGMII_CTRL_TIMING_SEL; + b53_write8(dev, B53_CTRL_PAGE, off, rgmii_ctrl); + + dev_info(ds->dev, "Configured port %d for %s\n", port, + phy_modes(phydev->interface)); + } + + /* configure MII port if necessary */ + if (is5325(dev)) { + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + ®); + + /* reverse mii needs to be enabled */ + if (!(reg & PORT_OVERRIDE_RV_MII_25)) { + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + reg | PORT_OVERRIDE_RV_MII_25); + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + ®); + + if (!(reg & PORT_OVERRIDE_RV_MII_25)) { + dev_err(ds->dev, + "Failed to enable reverse MII mode\n"); + return; + } + } + } else if (is5301x(dev)) { + if (port != dev->cpu_port) { + u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(dev->cpu_port); + u8 gmii_po; + + b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); + gmii_po |= GMII_PO_LINK | + GMII_PO_RX_FLOW | + GMII_PO_TX_FLOW | + GMII_PO_EN | + GMII_PO_SPEED_2000M; + b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); + } + } +} + +static struct dsa_switch_driver b53_switch_ops = { + .tag_protocol = DSA_TAG_PROTO_NONE, + .setup = b53_setup, + .set_addr = b53_set_addr, + .get_strings = b53_get_strings, + .get_ethtool_stats = b53_get_ethtool_stats, + .get_sset_count = b53_get_sset_count, + .phy_read = b53_phy_read16, + .phy_write = b53_phy_write16, + .adjust_link = b53_adjust_link, + .port_enable = b53_enable_port, + .port_disable = b53_disable_port, +}; + +struct b53_chip_data { + u32 chip_id; + const char *dev_name; + u16 vlans; + u16 enabled_ports; + u8 cpu_port; + u8 vta_regs[3]; + u8 duplex_reg; + u8 jumbo_pm_reg; + u8 jumbo_size_reg; +}; + +#define B53_VTA_REGS \ + { B53_VT_ACCESS, B53_VT_INDEX, B53_VT_ENTRY } +#define B53_VTA_REGS_9798 \ + { B53_VT_ACCESS_9798, B53_VT_INDEX_9798, B53_VT_ENTRY_9798 } +#define B53_VTA_REGS_63XX \ + { B53_VT_ACCESS_63XX, B53_VT_INDEX_63XX, B53_VT_ENTRY_63XX } + +static const struct b53_chip_data b53_switch_chips[] = { + { + .chip_id = BCM5325_DEVICE_ID, + .dev_name = "BCM5325", + .vlans = 16, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, + .duplex_reg = B53_DUPLEX_STAT_FE, + }, + { + .chip_id = BCM5365_DEVICE_ID, + .dev_name = "BCM5365", + .vlans = 256, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, + .duplex_reg = B53_DUPLEX_STAT_FE, + }, + { + .chip_id = BCM5395_DEVICE_ID, + .dev_name = "BCM5395", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, + { + .chip_id = BCM5397_DEVICE_ID, + .dev_name = "BCM5397", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_9798, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, + { + .chip_id = BCM5398_DEVICE_ID, + .dev_name = "BCM5398", + .vlans = 4096, + .enabled_ports = 0x7f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_9798, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, + { + .chip_id = BCM53115_DEVICE_ID, + .dev_name = "BCM53115", + .vlans = 4096, + .enabled_ports = 0x1f, + .vta_regs = B53_VTA_REGS, + .cpu_port = B53_CPU_PORT, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, + { + .chip_id = BCM53125_DEVICE_ID, + .dev_name = "BCM53125", + .vlans = 4096, + .enabled_ports = 0xff, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, + { + .chip_id = BCM53128_DEVICE_ID, + .dev_name = "BCM53128", + .vlans = 4096, + .enabled_ports = 0x1ff, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, + { + .chip_id = BCM63XX_DEVICE_ID, + .dev_name = "BCM63xx", + .vlans = 4096, + .enabled_ports = 0, /* pdata must provide them */ + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_63XX, + .duplex_reg = B53_DUPLEX_STAT_63XX, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX, + }, + { + .chip_id = BCM53010_DEVICE_ID, + .dev_name = "BCM53010", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, + { + .chip_id = BCM53011_DEVICE_ID, + .dev_name = "BCM53011", + .vlans = 4096, + .enabled_ports = 0x1bf, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, + { + .chip_id = BCM53012_DEVICE_ID, + .dev_name = "BCM53012", + .vlans = 4096, + .enabled_ports = 0x1bf, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, + { + .chip_id = BCM53018_DEVICE_ID, + .dev_name = "BCM53018", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, + { + .chip_id = BCM53019_DEVICE_ID, + .dev_name = "BCM53019", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, +}; + +static int b53_switch_init(struct b53_device *dev) +{ + struct dsa_switch *ds = dev->ds; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(b53_switch_chips); i++) { + const struct b53_chip_data *chip = &b53_switch_chips[i]; + + if (chip->chip_id == dev->chip_id) { + if (!dev->enabled_ports) + dev->enabled_ports = chip->enabled_ports; + dev->name = chip->dev_name; + dev->duplex_reg = chip->duplex_reg; + dev->vta_regs[0] = chip->vta_regs[0]; + dev->vta_regs[1] = chip->vta_regs[1]; + dev->vta_regs[2] = chip->vta_regs[2]; + dev->jumbo_pm_reg = chip->jumbo_pm_reg; + ds->drv = &b53_switch_ops; + dev->cpu_port = chip->cpu_port; + dev->num_vlans = chip->vlans; + break; + } + } + + /* check which BCM5325x version we have */ + if (is5325(dev)) { + u8 vc4; + + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); + + /* check reserved bits */ + switch (vc4 & 3) { + case 1: + /* BCM5325E */ + break; + case 3: + /* BCM5325F - do not use port 4 */ + dev->enabled_ports &= ~BIT(4); + break; + default: +/* On the BCM47XX SoCs this is the supported internal switch.*/ +#ifndef CONFIG_BCM47XX + /* BCM5325M */ + return -EINVAL; +#else + break; +#endif + } + } else if (dev->chip_id == BCM53115_DEVICE_ID) { + u64 strap_value; + + b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value); + /* use second IMP port if GMII is enabled */ + if (strap_value & SV_GMII_CTRL_115) + dev->cpu_port = 5; + } + + /* cpu port is always last */ + dev->num_ports = dev->cpu_port + 1; + dev->enabled_ports |= BIT(dev->cpu_port); + + dev->ports = devm_kzalloc(dev->dev, + sizeof(struct b53_port) * dev->num_ports, + GFP_KERNEL); + if (!dev->ports) + return -ENOMEM; + + dev->reset_gpio = b53_switch_get_reset_gpio(dev); + if (dev->reset_gpio >= 0) { + ret = devm_gpio_request_one(dev->dev, dev->reset_gpio, + GPIOF_OUT_INIT_HIGH, "robo_reset"); + if (ret) + return ret; + } + + return 0; +} + +struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, + void *priv) +{ + struct dsa_switch *ds; + struct b53_device *dev; + + ds = devm_kzalloc(base, sizeof(*ds) + sizeof(*dev), GFP_KERNEL); + if (!ds) + return NULL; + + dev = (struct b53_device *)(ds + 1); + + ds->priv = dev; + ds->dev = base; + dev->dev = base; + + dev->ds = ds; + dev->priv = priv; + dev->ops = ops; + mutex_init(&dev->reg_mutex); + mutex_init(&dev->stats_mutex); + + return dev; +} +EXPORT_SYMBOL(b53_switch_alloc); + +int b53_switch_detect(struct b53_device *dev) +{ + u32 id32; + u16 tmp; + u8 id8; + int ret; + + ret = b53_read8(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id8); + if (ret) + return ret; + + switch (id8) { + case 0: + /* BCM5325 and BCM5365 do not have this register so reads + * return 0. But the read operation did succeed, so assume this + * is one of them. + * + * Next check if we can write to the 5325's VTA register; for + * 5365 it is read only. + */ + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, 0xf); + b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, &tmp); + + if (tmp == 0xf) + dev->chip_id = BCM5325_DEVICE_ID; + else + dev->chip_id = BCM5365_DEVICE_ID; + break; + case BCM5395_DEVICE_ID: + case BCM5397_DEVICE_ID: + case BCM5398_DEVICE_ID: + dev->chip_id = id8; + break; + default: + ret = b53_read32(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id32); + if (ret) + return ret; + + switch (id32) { + case BCM53115_DEVICE_ID: + case BCM53125_DEVICE_ID: + case BCM53128_DEVICE_ID: + case BCM53010_DEVICE_ID: + case BCM53011_DEVICE_ID: + case BCM53012_DEVICE_ID: + case BCM53018_DEVICE_ID: + case BCM53019_DEVICE_ID: + dev->chip_id = id32; + break; + default: + pr_err("unsupported switch detected (BCM53%02x/BCM%x)\n", + id8, id32); + return -ENODEV; + } + } + + if (dev->chip_id == BCM5325_DEVICE_ID) + return b53_read8(dev, B53_STAT_PAGE, B53_REV_ID_25, + &dev->core_rev); + else + return b53_read8(dev, B53_MGMT_PAGE, B53_REV_ID, + &dev->core_rev); +} +EXPORT_SYMBOL(b53_switch_detect); + +int b53_switch_register(struct b53_device *dev) +{ + int ret; + + if (dev->pdata) { + dev->chip_id = dev->pdata->chip_id; + dev->enabled_ports = dev->pdata->enabled_ports; + } + + if (!dev->chip_id && b53_switch_detect(dev)) + return -EINVAL; + + ret = b53_switch_init(dev); + if (ret) + return ret; + + pr_info("found switch: %s, rev %i\n", dev->name, dev->core_rev); + + return dsa_register_switch(dev->ds, dev->ds->dev->of_node); +} +EXPORT_SYMBOL(b53_switch_register); + +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 switch library"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/dsa/b53/b53_mdio.c b/drivers/net/dsa/b53/b53_mdio.c new file mode 100644 index 000000000000..c6cf7cf57cc3 --- /dev/null +++ b/drivers/net/dsa/b53/b53_mdio.c @@ -0,0 +1,381 @@ +/* + * B53 register access through MII registers + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "b53_priv.h" + +/* MII registers */ +#define REG_MII_PAGE 0x10 /* MII Page register */ +#define REG_MII_ADDR 0x11 /* MII Address register */ +#define REG_MII_DATA0 0x18 /* MII Data register 0 */ +#define REG_MII_DATA1 0x19 /* MII Data register 1 */ +#define REG_MII_DATA2 0x1a /* MII Data register 2 */ +#define REG_MII_DATA3 0x1b /* MII Data register 3 */ + +#define REG_MII_PAGE_ENABLE BIT(0) +#define REG_MII_ADDR_WRITE BIT(0) +#define REG_MII_ADDR_READ BIT(1) + +static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op) +{ + int i; + u16 v; + int ret; + struct mii_bus *bus = dev->priv; + + if (dev->current_page != page) { + /* set page number */ + v = (page << 8) | REG_MII_PAGE_ENABLE; + ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_PAGE, v); + if (ret) + return ret; + dev->current_page = page; + } + + /* set register address */ + v = (reg << 8) | op; + ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, REG_MII_ADDR, v); + if (ret) + return ret; + + /* check if operation completed */ + for (i = 0; i < 5; ++i) { + v = mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_ADDR); + if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ))) + break; + usleep_range(10, 100); + } + + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_DATA0) & 0xff; + + return 0; +} + +static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, REG_MII_DATA0); + + return 0; +} + +static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, REG_MII_DATA0); + *val |= mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_DATA1) << 16; + + return 0; +} + +static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + struct mii_bus *bus = dev->priv; + u64 temp = 0; + int i; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + for (i = 2; i >= 0; i--) { + temp <<= 16; + temp |= mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_DATA0 + i); + } + + *val = temp; + + return 0; +} + +static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + struct mii_bus *bus = dev->priv; + u64 temp = 0; + int i; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + for (i = 3; i >= 0; i--) { + temp <<= 16; + temp |= mdiobus_read_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_DATA0 + i); + } + + *val = temp; + + return 0; +} + +static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_DATA0, value); + if (ret) + return ret; + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_DATA0, value); + if (ret) + return ret; + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + struct mii_bus *bus = dev->priv; + unsigned int i; + u32 temp = value; + + for (i = 0; i < 2; i++) { + int ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + struct mii_bus *bus = dev->priv; + unsigned int i; + u64 temp = value; + + for (i = 0; i < 3; i++) { + int ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + struct mii_bus *bus = dev->priv; + unsigned int i; + u64 temp = value; + + for (i = 0; i < 4; i++) { + int ret = mdiobus_write_nested(bus, BRCM_PSEUDO_PHY_ADDR, + REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_phy_read16(struct b53_device *dev, int addr, int reg, + u16 *value) +{ + struct mii_bus *bus = dev->priv; + + *value = mdiobus_read_nested(bus, addr, reg); + + return 0; +} + +static int b53_mdio_phy_write16(struct b53_device *dev, int addr, int reg, + u16 value) +{ + struct mii_bus *bus = dev->bus; + + return mdiobus_write_nested(bus, addr, reg, value); +} + +static struct b53_io_ops b53_mdio_ops = { + .read8 = b53_mdio_read8, + .read16 = b53_mdio_read16, + .read32 = b53_mdio_read32, + .read48 = b53_mdio_read48, + .read64 = b53_mdio_read64, + .write8 = b53_mdio_write8, + .write16 = b53_mdio_write16, + .write32 = b53_mdio_write32, + .write48 = b53_mdio_write48, + .write64 = b53_mdio_write64, + .phy_read16 = b53_mdio_phy_read16, + .phy_write16 = b53_mdio_phy_write16, +}; + +#define B53_BRCM_OUI_1 0x0143bc00 +#define B53_BRCM_OUI_2 0x03625c00 +#define B53_BRCM_OUI_3 0x00406000 + +static int b53_mdio_probe(struct mdio_device *mdiodev) +{ + struct b53_device *dev; + u32 phy_id; + int ret; + + /* allow the generic PHY driver to take over the non-management MDIO + * addresses + */ + if (mdiodev->addr != BRCM_PSEUDO_PHY_ADDR && mdiodev->addr != 0) { + dev_err(&mdiodev->dev, "leaving address %d to PHY\n", + mdiodev->addr); + return -ENODEV; + } + + /* read the first port's id */ + phy_id = mdiobus_read(mdiodev->bus, 0, 2) << 16; + phy_id |= mdiobus_read(mdiodev->bus, 0, 3); + + /* BCM5325, BCM539x (OUI_1) + * BCM53125, BCM53128 (OUI_2) + * BCM5365 (OUI_3) + */ + if ((phy_id & 0xfffffc00) != B53_BRCM_OUI_1 && + (phy_id & 0xfffffc00) != B53_BRCM_OUI_2 && + (phy_id & 0xfffffc00) != B53_BRCM_OUI_3) { + dev_err(&mdiodev->dev, "Unsupported device: 0x%08x\n", phy_id); + return -ENODEV; + } + + dev = b53_switch_alloc(&mdiodev->dev, &b53_mdio_ops, mdiodev->bus); + if (!dev) + return -ENOMEM; + + /* we don't use page 0xff, so force a page set */ + dev->current_page = 0xff; + dev->bus = mdiodev->bus; + + dev_set_drvdata(&mdiodev->dev, dev); + + ret = b53_switch_register(dev); + if (ret) { + dev_err(&mdiodev->dev, "failed to register switch: %i\n", ret); + return ret; + } + + return ret; +} + +static void b53_mdio_remove(struct mdio_device *mdiodev) +{ + struct b53_device *dev = dev_get_drvdata(&mdiodev->dev); + struct dsa_switch *ds = dev->ds; + + dsa_unregister_switch(ds); +} + +static const struct of_device_id b53_of_match[] = { + { .compatible = "brcm,bcm5325" }, + { .compatible = "brcm,bcm53115" }, + { .compatible = "brcm,bcm53125" }, + { .compatible = "brcm,bcm53128" }, + { .compatible = "brcm,bcm5365" }, + { .compatible = "brcm,bcm5395" }, + { .compatible = "brcm,bcm5397" }, + { .compatible = "brcm,bcm5398" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, b53_of_match); + +static struct mdio_driver b53_mdio_driver = { + .probe = b53_mdio_probe, + .remove = b53_mdio_remove, + .mdiodrv.driver = { + .name = "bcm53xx", + .of_match_table = b53_of_match, + }, +}; + +static int __init b53_mdio_driver_register(void) +{ + return mdio_driver_register(&b53_mdio_driver); +} +module_init(b53_mdio_driver_register); + +static void __exit b53_mdio_driver_unregister(void) +{ + mdio_driver_unregister(&b53_mdio_driver); +} +module_exit(b53_mdio_driver_unregister); + +MODULE_DESCRIPTION("B53 MDIO access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/dsa/b53/b53_mmap.c b/drivers/net/dsa/b53/b53_mmap.c new file mode 100644 index 000000000000..f115ee25c0e8 --- /dev/null +++ b/drivers/net/dsa/b53/b53_mmap.c @@ -0,0 +1,260 @@ +/* + * B53 register access through memory mapped registers + * + * Copyright (C) 2012-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "b53_priv.h" + +struct b53_mmap_priv { + void __iomem *regs; +}; + +static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + u8 __iomem *regs = dev->priv; + + *val = readb(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) && dev->pdata && + dev->pdata->big_endian) + *val = __raw_readw(regs + (page << 8) + reg); + else + *val = readw(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) && dev->pdata && + dev->pdata->big_endian) + *val = __raw_readl(regs + (page << 8) + reg); + else + *val = readl(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (reg % 4) { + u16 lo; + u32 hi; + + b53_mmap_read16(dev, page, reg, &lo); + b53_mmap_read32(dev, page, reg + 2, &hi); + + *val = ((u64)hi << 16) | lo; + } else { + u32 lo; + u16 hi; + + b53_mmap_read32(dev, page, reg, &lo); + b53_mmap_read16(dev, page, reg + 4, &hi); + + *val = ((u64)hi << 32) | lo; + } + + return 0; +} + +static int b53_mmap_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + u32 hi, lo; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + b53_mmap_read32(dev, page, reg, &lo); + b53_mmap_read32(dev, page, reg + 4, &hi); + + *val = ((u64)hi << 32) | lo; + + return 0; +} + +static int b53_mmap_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + u8 __iomem *regs = dev->priv; + + writeb(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) && dev->pdata && + dev->pdata->big_endian) + __raw_writew(value, regs + (page << 8) + reg); + else + writew(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) && dev->pdata && + dev->pdata->big_endian) + __raw_writel(value, regs + (page << 8) + reg); + else + writel(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (reg % 4) { + u32 hi = (u32)(value >> 16); + u16 lo = (u16)value; + + b53_mmap_write16(dev, page, reg, lo); + b53_mmap_write32(dev, page, reg + 2, hi); + } else { + u16 hi = (u16)(value >> 32); + u32 lo = (u32)value; + + b53_mmap_write32(dev, page, reg, lo); + b53_mmap_write16(dev, page, reg + 4, hi); + } + + return 0; +} + +static int b53_mmap_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + u32 hi, lo; + + hi = upper_32_bits(value); + lo = lower_32_bits(value); + + if (WARN_ON(reg % 4)) + return -EINVAL; + + b53_mmap_write32(dev, page, reg, lo); + b53_mmap_write32(dev, page, reg + 4, hi); + + return 0; +} + +static struct b53_io_ops b53_mmap_ops = { + .read8 = b53_mmap_read8, + .read16 = b53_mmap_read16, + .read32 = b53_mmap_read32, + .read48 = b53_mmap_read48, + .read64 = b53_mmap_read64, + .write8 = b53_mmap_write8, + .write16 = b53_mmap_write16, + .write32 = b53_mmap_write32, + .write48 = b53_mmap_write48, + .write64 = b53_mmap_write64, +}; + +static int b53_mmap_probe(struct platform_device *pdev) +{ + struct b53_platform_data *pdata = pdev->dev.platform_data; + struct b53_device *dev; + + if (!pdata) + return -EINVAL; + + dev = b53_switch_alloc(&pdev->dev, &b53_mmap_ops, pdata->regs); + if (!dev) + return -ENOMEM; + + if (pdata) + dev->pdata = pdata; + + platform_set_drvdata(pdev, dev); + + return b53_switch_register(dev); +} + +static int b53_mmap_remove(struct platform_device *pdev) +{ + struct b53_device *dev = platform_get_drvdata(pdev); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static const struct of_device_id b53_mmap_of_table[] = { + { .compatible = "brcm,bcm3384-switch" }, + { .compatible = "brcm,bcm6328-switch" }, + { .compatible = "brcm,bcm6368-switch" }, + { .compatible = "brcm,bcm63xx-switch" }, + { /* sentinel */ }, +}; + +static struct platform_driver b53_mmap_driver = { + .probe = b53_mmap_probe, + .remove = b53_mmap_remove, + .driver = { + .name = "b53-switch", + .of_match_table = b53_mmap_of_table, + }, +}; + +module_platform_driver(b53_mmap_driver); +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 MMAP access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h new file mode 100644 index 000000000000..c4844155546b --- /dev/null +++ b/drivers/net/dsa/b53/b53_priv.h @@ -0,0 +1,322 @@ +/* + * B53 common definitions + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_PRIV_H +#define __B53_PRIV_H + +#include +#include +#include +#include + +struct b53_device; + +struct b53_io_ops { + int (*read8)(struct b53_device *dev, u8 page, u8 reg, u8 *value); + int (*read16)(struct b53_device *dev, u8 page, u8 reg, u16 *value); + int (*read32)(struct b53_device *dev, u8 page, u8 reg, u32 *value); + int (*read48)(struct b53_device *dev, u8 page, u8 reg, u64 *value); + int (*read64)(struct b53_device *dev, u8 page, u8 reg, u64 *value); + int (*write8)(struct b53_device *dev, u8 page, u8 reg, u8 value); + int (*write16)(struct b53_device *dev, u8 page, u8 reg, u16 value); + int (*write32)(struct b53_device *dev, u8 page, u8 reg, u32 value); + int (*write48)(struct b53_device *dev, u8 page, u8 reg, u64 value); + int (*write64)(struct b53_device *dev, u8 page, u8 reg, u64 value); + int (*phy_read16)(struct b53_device *dev, int addr, int reg, u16 *value); + int (*phy_write16)(struct b53_device *dev, int addr, int reg, u16 value); +}; + +enum { + BCM5325_DEVICE_ID = 0x25, + BCM5365_DEVICE_ID = 0x65, + BCM5395_DEVICE_ID = 0x95, + BCM5397_DEVICE_ID = 0x97, + BCM5398_DEVICE_ID = 0x98, + BCM53115_DEVICE_ID = 0x53115, + BCM53125_DEVICE_ID = 0x53125, + BCM53128_DEVICE_ID = 0x53128, + BCM63XX_DEVICE_ID = 0x6300, + BCM53010_DEVICE_ID = 0x53010, + BCM53011_DEVICE_ID = 0x53011, + BCM53012_DEVICE_ID = 0x53012, + BCM53018_DEVICE_ID = 0x53018, + BCM53019_DEVICE_ID = 0x53019, +}; + +#define B53_N_PORTS 9 +#define B53_N_PORTS_25 6 + +struct b53_port { +}; + +struct b53_device { + struct dsa_switch *ds; + struct b53_platform_data *pdata; + const char *name; + + struct mutex reg_mutex; + struct mutex stats_mutex; + const struct b53_io_ops *ops; + + /* chip specific data */ + u32 chip_id; + u8 core_rev; + u8 vta_regs[3]; + u8 duplex_reg; + u8 jumbo_pm_reg; + u8 jumbo_size_reg; + int reset_gpio; + + /* used ports mask */ + u16 enabled_ports; + unsigned int cpu_port; + + /* connect specific data */ + u8 current_page; + struct device *dev; + + /* Master MDIO bus we got probed from */ + struct mii_bus *bus; + + /* Slave MDIO bus we created */ + struct mii_bus *slave_bus; + void *priv; + + /* run time configuration */ + unsigned enable_jumbo:1; + unsigned allow_vid_4095:1; + unsigned int num_vlans; + unsigned int num_ports; + struct b53_port *ports; +}; + +#define b53_for_each_port(dev, i) \ + for (i = 0; i < B53_N_PORTS; i++) \ + if (dev->enabled_ports & BIT(i)) + + +static inline int is5325(struct b53_device *dev) +{ + return dev->chip_id == BCM5325_DEVICE_ID; +} + +static inline int is5365(struct b53_device *dev) +{ +#ifdef CONFIG_BCM47XX + return dev->chip_id == BCM5365_DEVICE_ID; +#else + return 0; +#endif +} + +static inline int is5397_98(struct b53_device *dev) +{ + return dev->chip_id == BCM5397_DEVICE_ID || + dev->chip_id == BCM5398_DEVICE_ID; +} + +static inline int is539x(struct b53_device *dev) +{ + return dev->chip_id == BCM5395_DEVICE_ID || + dev->chip_id == BCM5397_DEVICE_ID || + dev->chip_id == BCM5398_DEVICE_ID; +} + +static inline int is531x5(struct b53_device *dev) +{ + return dev->chip_id == BCM53115_DEVICE_ID || + dev->chip_id == BCM53125_DEVICE_ID || + dev->chip_id == BCM53128_DEVICE_ID; +} + +static inline int is63xx(struct b53_device *dev) +{ +#ifdef CONFIG_BCM63XX + return dev->chip_id == BCM63XX_DEVICE_ID; +#else + return 0; +#endif +} + +static inline int is5301x(struct b53_device *dev) +{ + return dev->chip_id == BCM53010_DEVICE_ID || + dev->chip_id == BCM53011_DEVICE_ID || + dev->chip_id == BCM53012_DEVICE_ID || + dev->chip_id == BCM53018_DEVICE_ID || + dev->chip_id == BCM53019_DEVICE_ID; +} + +#define B53_CPU_PORT_25 5 +#define B53_CPU_PORT 8 + +static inline int is_cpu_port(struct b53_device *dev, int port) +{ + return dev->cpu_port; +} + +struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, + void *priv); + +int b53_switch_detect(struct b53_device *dev); + +int b53_switch_register(struct b53_device *dev); + +static inline void b53_switch_remove(struct b53_device *dev) +{ + dsa_unregister_switch(dev->ds); +} + +static inline int b53_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read8(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read16(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read32(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read48(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read64(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write8(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write16(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write32(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write48(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write64(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +#ifdef CONFIG_BCM47XX + +#include +#include +#include +static inline int b53_switch_get_reset_gpio(struct b53_device *dev) +{ + enum bcm47xx_board board = bcm47xx_board_get(); + + switch (board) { + case BCM47XX_BOARD_LINKSYS_WRT300NV11: + case BCM47XX_BOARD_LINKSYS_WRT310NV1: + return 8; + default: + return bcm47xx_nvram_gpio_pin("robo_reset"); + } +} +#else +static inline int b53_switch_get_reset_gpio(struct b53_device *dev) +{ + return -ENOENT; +} +#endif +#endif diff --git a/drivers/net/dsa/b53/b53_regs.h b/drivers/net/dsa/b53/b53_regs.h new file mode 100644 index 000000000000..ccf8af7717d3 --- /dev/null +++ b/drivers/net/dsa/b53/b53_regs.h @@ -0,0 +1,358 @@ +/* + * B53 register definitions + * + * Copyright (C) 2004 Broadcom Corporation + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_REGS_H +#define __B53_REGS_H + +/* Management Port (SMP) Page offsets */ +#define B53_CTRL_PAGE 0x00 /* Control */ +#define B53_STAT_PAGE 0x01 /* Status */ +#define B53_MGMT_PAGE 0x02 /* Management Mode */ +#define B53_MIB_AC_PAGE 0x03 /* MIB Autocast */ +#define B53_ARLCTRL_PAGE 0x04 /* ARL Control */ +#define B53_ARLIO_PAGE 0x05 /* ARL Access */ +#define B53_FRAMEBUF_PAGE 0x06 /* Management frame access */ +#define B53_MEM_ACCESS_PAGE 0x08 /* Memory access */ + +/* PHY Registers */ +#define B53_PORT_MII_PAGE(i) (0x10 + (i)) /* Port i MII Registers */ +#define B53_IM_PORT_PAGE 0x18 /* Inverse MII Port (to EMAC) */ +#define B53_ALL_PORT_PAGE 0x19 /* All ports MII (broadcast) */ + +/* MIB registers */ +#define B53_MIB_PAGE(i) (0x20 + (i)) + +/* Quality of Service (QoS) Registers */ +#define B53_QOS_PAGE 0x30 + +/* Port VLAN Page */ +#define B53_PVLAN_PAGE 0x31 + +/* VLAN Registers */ +#define B53_VLAN_PAGE 0x34 + +/* Jumbo Frame Registers */ +#define B53_JUMBO_PAGE 0x40 + +/* CFP Configuration Registers Page */ +#define B53_CFP_PAGE 0xa1 + +/************************************************************************* + * Control Page registers + *************************************************************************/ + +/* Port Control Register (8 bit) */ +#define B53_PORT_CTRL(i) (0x00 + (i)) +#define PORT_CTRL_RX_DISABLE BIT(0) +#define PORT_CTRL_TX_DISABLE BIT(1) +#define PORT_CTRL_RX_BCST_EN BIT(2) /* Broadcast RX (P8 only) */ +#define PORT_CTRL_RX_MCST_EN BIT(3) /* Multicast RX (P8 only) */ +#define PORT_CTRL_RX_UCST_EN BIT(4) /* Unicast RX (P8 only) */ +#define PORT_CTRL_STP_STATE_S 5 +#define PORT_CTRL_STP_STATE_MASK (0x7 << PORT_CTRL_STP_STATE_S) + +/* SMP Control Register (8 bit) */ +#define B53_SMP_CTRL 0x0a + +/* Switch Mode Control Register (8 bit) */ +#define B53_SWITCH_MODE 0x0b +#define SM_SW_FWD_MODE BIT(0) /* 1 = Managed Mode */ +#define SM_SW_FWD_EN BIT(1) /* Forwarding Enable */ + +/* IMP Port state override register (8 bit) */ +#define B53_PORT_OVERRIDE_CTRL 0x0e +#define PORT_OVERRIDE_LINK BIT(0) +#define PORT_OVERRIDE_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */ +#define PORT_OVERRIDE_SPEED_S 2 +#define PORT_OVERRIDE_SPEED_10M (0 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_SPEED_100M (1 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_SPEED_1000M (2 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_RV_MII_25 BIT(4) /* BCM5325 only */ +#define PORT_OVERRIDE_RX_FLOW BIT(4) +#define PORT_OVERRIDE_TX_FLOW BIT(5) +#define PORT_OVERRIDE_SPEED_2000M BIT(6) /* BCM5301X only, requires setting 1000M */ +#define PORT_OVERRIDE_EN BIT(7) /* Use the register contents */ + +/* Power-down mode control */ +#define B53_PD_MODE_CTRL_25 0x0f + +/* IP Multicast control (8 bit) */ +#define B53_IP_MULTICAST_CTRL 0x21 +#define B53_IPMC_FWD_EN BIT(1) +#define B53_UC_FWD_EN BIT(6) +#define B53_MC_FWD_EN BIT(7) + +/* (16 bit) */ +#define B53_UC_FLOOD_MASK 0x32 +#define B53_MC_FLOOD_MASK 0x34 +#define B53_IPMC_FLOOD_MASK 0x36 + +/* + * Override Ports 0-7 State on devices with xMII interfaces (8 bit) + * + * For port 8 still use B53_PORT_OVERRIDE_CTRL + * Please note that not all ports are available on every hardware, e.g. BCM5301X + * don't include overriding port 6, BCM63xx also have some limitations. + */ +#define B53_GMII_PORT_OVERRIDE_CTRL(i) (0x58 + (i)) +#define GMII_PO_LINK BIT(0) +#define GMII_PO_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */ +#define GMII_PO_SPEED_S 2 +#define GMII_PO_SPEED_10M (0 << GMII_PO_SPEED_S) +#define GMII_PO_SPEED_100M (1 << GMII_PO_SPEED_S) +#define GMII_PO_SPEED_1000M (2 << GMII_PO_SPEED_S) +#define GMII_PO_RX_FLOW BIT(4) +#define GMII_PO_TX_FLOW BIT(5) +#define GMII_PO_EN BIT(6) /* Use the register contents */ +#define GMII_PO_SPEED_2000M BIT(7) /* BCM5301X only, requires setting 1000M */ + +#define B53_RGMII_CTRL_IMP 0x60 +#define RGMII_CTRL_ENABLE_GMII BIT(7) +#define RGMII_CTRL_TIMING_SEL BIT(2) +#define RGMII_CTRL_DLL_RXC BIT(1) +#define RGMII_CTRL_DLL_TXC BIT(0) + +#define B53_RGMII_CTRL_P(i) (B53_RGMII_CTRL_IMP + (i)) + +/* Software reset register (8 bit) */ +#define B53_SOFTRESET 0x79 +#define SW_RST BIT(7) +#define EN_SW_RST BIT(4) + +/* Fast Aging Control register (8 bit) */ +#define B53_FAST_AGE_CTRL 0x88 +#define FAST_AGE_STATIC BIT(0) +#define FAST_AGE_DYNAMIC BIT(1) +#define FAST_AGE_PORT BIT(2) +#define FAST_AGE_VLAN BIT(3) +#define FAST_AGE_STP BIT(4) +#define FAST_AGE_MC BIT(5) +#define FAST_AGE_DONE BIT(7) + +/************************************************************************* + * Status Page registers + *************************************************************************/ + +/* Link Status Summary Register (16bit) */ +#define B53_LINK_STAT 0x00 + +/* Link Status Change Register (16 bit) */ +#define B53_LINK_STAT_CHANGE 0x02 + +/* Port Speed Summary Register (16 bit for FE, 32 bit for GE) */ +#define B53_SPEED_STAT 0x04 +#define SPEED_PORT_FE(reg, port) (((reg) >> (port)) & 1) +#define SPEED_PORT_GE(reg, port) (((reg) >> 2 * (port)) & 3) +#define SPEED_STAT_10M 0 +#define SPEED_STAT_100M 1 +#define SPEED_STAT_1000M 2 + +/* Duplex Status Summary (16 bit) */ +#define B53_DUPLEX_STAT_FE 0x06 +#define B53_DUPLEX_STAT_GE 0x08 +#define B53_DUPLEX_STAT_63XX 0x0c + +/* Revision ID register for BCM5325 */ +#define B53_REV_ID_25 0x50 + +/* Strap Value (48 bit) */ +#define B53_STRAP_VALUE 0x70 +#define SV_GMII_CTRL_115 BIT(27) + +/************************************************************************* + * Management Mode Page Registers + *************************************************************************/ + +/* Global Management Config Register (8 bit) */ +#define B53_GLOBAL_CONFIG 0x00 +#define GC_RESET_MIB 0x01 +#define GC_RX_BPDU_EN 0x02 +#define GC_MIB_AC_HDR_EN 0x10 +#define GC_MIB_AC_EN 0x20 +#define GC_FRM_MGMT_PORT_M 0xC0 +#define GC_FRM_MGMT_PORT_04 0x00 +#define GC_FRM_MGMT_PORT_MII 0x80 + +/* Broadcom Header control register (8 bit) */ +#define B53_BRCM_HDR 0x03 +#define BRCM_HDR_P8_EN BIT(0) /* Enable tagging on port 8 */ +#define BRCM_HDR_P5_EN BIT(1) /* Enable tagging on port 5 */ + +/* Device ID register (8 or 32 bit) */ +#define B53_DEVICE_ID 0x30 + +/* Revision ID register (8 bit) */ +#define B53_REV_ID 0x40 + +/************************************************************************* + * ARL Access Page Registers + *************************************************************************/ + +/* VLAN Table Access Register (8 bit) */ +#define B53_VT_ACCESS 0x80 +#define B53_VT_ACCESS_9798 0x60 /* for BCM5397/BCM5398 */ +#define B53_VT_ACCESS_63XX 0x60 /* for BCM6328/62/68 */ +#define VTA_CMD_WRITE 0 +#define VTA_CMD_READ 1 +#define VTA_CMD_CLEAR 2 +#define VTA_START_CMD BIT(7) + +/* VLAN Table Index Register (16 bit) */ +#define B53_VT_INDEX 0x81 +#define B53_VT_INDEX_9798 0x61 +#define B53_VT_INDEX_63XX 0x62 + +/* VLAN Table Entry Register (32 bit) */ +#define B53_VT_ENTRY 0x83 +#define B53_VT_ENTRY_9798 0x63 +#define B53_VT_ENTRY_63XX 0x64 +#define VTE_MEMBERS 0x1ff +#define VTE_UNTAG_S 9 +#define VTE_UNTAG (0x1ff << 9) + +/************************************************************************* + * Port VLAN Registers + *************************************************************************/ + +/* Port VLAN mask (16 bit) IMP port is always 8, also on 5325 & co */ +#define B53_PVLAN_PORT_MASK(i) ((i) * 2) + +/************************************************************************* + * 802.1Q Page Registers + *************************************************************************/ + +/* Global QoS Control (8 bit) */ +#define B53_QOS_GLOBAL_CTL 0x00 + +/* Enable 802.1Q for individual Ports (16 bit) */ +#define B53_802_1P_EN 0x04 + +/************************************************************************* + * VLAN Page Registers + *************************************************************************/ + +/* VLAN Control 0 (8 bit) */ +#define B53_VLAN_CTRL0 0x00 +#define VC0_8021PF_CTRL_MASK 0x3 +#define VC0_8021PF_CTRL_NONE 0x0 +#define VC0_8021PF_CTRL_CHANGE_PRI 0x1 +#define VC0_8021PF_CTRL_CHANGE_VID 0x2 +#define VC0_8021PF_CTRL_CHANGE_BOTH 0x3 +#define VC0_8021QF_CTRL_MASK 0xc +#define VC0_8021QF_CTRL_CHANGE_PRI 0x1 +#define VC0_8021QF_CTRL_CHANGE_VID 0x2 +#define VC0_8021QF_CTRL_CHANGE_BOTH 0x3 +#define VC0_RESERVED_1 BIT(1) +#define VC0_DROP_VID_MISS BIT(4) +#define VC0_VID_HASH_VID BIT(5) +#define VC0_VID_CHK_EN BIT(6) /* Use VID,DA or VID,SA */ +#define VC0_VLAN_EN BIT(7) /* 802.1Q VLAN Enabled */ + +/* VLAN Control 1 (8 bit) */ +#define B53_VLAN_CTRL1 0x01 +#define VC1_RX_MCST_TAG_EN BIT(1) +#define VC1_RX_MCST_FWD_EN BIT(2) +#define VC1_RX_MCST_UNTAG_EN BIT(3) + +/* VLAN Control 2 (8 bit) */ +#define B53_VLAN_CTRL2 0x02 + +/* VLAN Control 3 (8 bit when BCM5325, 16 bit else) */ +#define B53_VLAN_CTRL3 0x03 +#define B53_VLAN_CTRL3_63XX 0x04 +#define VC3_MAXSIZE_1532 BIT(6) /* 5325 only */ +#define VC3_HIGH_8BIT_EN BIT(7) /* 5325 only */ + +/* VLAN Control 4 (8 bit) */ +#define B53_VLAN_CTRL4 0x05 +#define B53_VLAN_CTRL4_25 0x04 +#define B53_VLAN_CTRL4_63XX 0x06 +#define VC4_ING_VID_CHECK_S 6 +#define VC4_ING_VID_CHECK_MASK (0x3 << VC4_ING_VID_CHECK_S) +#define VC4_ING_VID_VIO_FWD 0 /* forward, but do not learn */ +#define VC4_ING_VID_VIO_DROP 1 /* drop VID violations */ +#define VC4_NO_ING_VID_CHK 2 /* do not check */ +#define VC4_ING_VID_VIO_TO_IMP 3 /* redirect to MII port */ + +/* VLAN Control 5 (8 bit) */ +#define B53_VLAN_CTRL5 0x06 +#define B53_VLAN_CTRL5_25 0x05 +#define B53_VLAN_CTRL5_63XX 0x07 +#define VC5_VID_FFF_EN BIT(2) +#define VC5_DROP_VTABLE_MISS BIT(3) + +/* VLAN Control 6 (8 bit) */ +#define B53_VLAN_CTRL6 0x07 +#define B53_VLAN_CTRL6_63XX 0x08 + +/* VLAN Table Access Register (16 bit) */ +#define B53_VLAN_TABLE_ACCESS_25 0x06 /* BCM5325E/5350 */ +#define B53_VLAN_TABLE_ACCESS_65 0x08 /* BCM5365 */ +#define VTA_VID_LOW_MASK_25 0xf +#define VTA_VID_LOW_MASK_65 0xff +#define VTA_VID_HIGH_S_25 4 +#define VTA_VID_HIGH_S_65 8 +#define VTA_VID_HIGH_MASK_25 (0xff << VTA_VID_HIGH_S_25E) +#define VTA_VID_HIGH_MASK_65 (0xf << VTA_VID_HIGH_S_65) +#define VTA_RW_STATE BIT(12) +#define VTA_RW_STATE_RD 0 +#define VTA_RW_STATE_WR BIT(12) +#define VTA_RW_OP_EN BIT(13) + +/* VLAN Read/Write Registers for (16/32 bit) */ +#define B53_VLAN_WRITE_25 0x08 +#define B53_VLAN_WRITE_65 0x0a +#define B53_VLAN_READ 0x0c +#define VA_MEMBER_MASK 0x3f +#define VA_UNTAG_S_25 6 +#define VA_UNTAG_MASK_25 0x3f +#define VA_UNTAG_S_65 7 +#define VA_UNTAG_MASK_65 0x1f +#define VA_VID_HIGH_S 12 +#define VA_VID_HIGH_MASK (0xffff << VA_VID_HIGH_S) +#define VA_VALID_25 BIT(20) +#define VA_VALID_25_R4 BIT(24) +#define VA_VALID_65 BIT(14) + +/* VLAN Port Default Tag (16 bit) */ +#define B53_VLAN_PORT_DEF_TAG(i) (0x10 + 2 * (i)) + +/************************************************************************* + * Jumbo Frame Page Registers + *************************************************************************/ + +/* Jumbo Enable Port Mask (bit i == port i enabled) (32 bit) */ +#define B53_JUMBO_PORT_MASK 0x01 +#define B53_JUMBO_PORT_MASK_63XX 0x04 +#define JPM_10_100_JUMBO_EN BIT(24) /* GigE always enabled */ + +/* Good Frame Max Size without 802.1Q TAG (16 bit) */ +#define B53_JUMBO_MAX_SIZE 0x05 +#define B53_JUMBO_MAX_SIZE_63XX 0x08 +#define JMS_MIN_SIZE 1518 +#define JMS_MAX_SIZE 9724 + +/************************************************************************* + * CFP Configuration Page Registers + *************************************************************************/ + +/* CFP Control Register with ports map (8 bit) */ +#define B53_CFP_CTRL 0x00 + +#endif /* !__B53_REGS_H */ diff --git a/drivers/net/dsa/b53/b53_spi.c b/drivers/net/dsa/b53/b53_spi.c new file mode 100644 index 000000000000..2bda0b5f1578 --- /dev/null +++ b/drivers/net/dsa/b53/b53_spi.c @@ -0,0 +1,331 @@ +/* + * B53 register access through SPI + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "b53_priv.h" + +#define B53_SPI_DATA 0xf0 + +#define B53_SPI_STATUS 0xfe +#define B53_SPI_CMD_SPIF BIT(7) +#define B53_SPI_CMD_RACK BIT(5) + +#define B53_SPI_CMD_READ 0x00 +#define B53_SPI_CMD_WRITE 0x01 +#define B53_SPI_CMD_NORMAL 0x60 +#define B53_SPI_CMD_FAST 0x10 + +#define B53_SPI_PAGE_SELECT 0xff + +static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val, + unsigned int len) +{ + u8 txbuf[2]; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ; + txbuf[1] = reg; + + return spi_write_then_read(spi, txbuf, 2, val, len); +} + +static inline int b53_spi_clear_status(struct spi_device *spi) +{ + unsigned int i; + u8 rxbuf; + int ret; + + for (i = 0; i < 10; i++) { + ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); + if (ret) + return ret; + + if (!(rxbuf & B53_SPI_CMD_SPIF)) + break; + + mdelay(1); + } + + if (i == 10) + return -EIO; + + return 0; +} + +static inline int b53_spi_set_page(struct spi_device *spi, u8 page) +{ + u8 txbuf[3]; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = B53_SPI_PAGE_SELECT; + txbuf[2] = page; + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page) +{ + int ret = b53_spi_clear_status(spi); + + if (ret) + return ret; + + return b53_spi_set_page(spi, page); +} + +static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg) +{ + u8 rxbuf; + int retry_count; + int ret; + + ret = b53_spi_read_reg(spi, reg, &rxbuf, 1); + if (ret) + return ret; + + for (retry_count = 0; retry_count < 10; retry_count++) { + ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); + if (ret) + return ret; + + if (rxbuf & B53_SPI_CMD_RACK) + break; + + mdelay(1); + } + + if (retry_count == 10) + return -EIO; + + return 0; +} + +static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data, + unsigned int len) +{ + struct spi_device *spi = dev->priv; + int ret; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + ret = b53_spi_prepare_reg_read(spi, reg); + if (ret) + return ret; + + return b53_spi_read_reg(spi, B53_SPI_DATA, data, len); +} + +static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + return b53_spi_read(dev, page, reg, val, 1); +} + +static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 2); + + if (!ret) + *val = le16_to_cpu(*val); + + return ret; +} + +static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 4); + + if (!ret) + *val = le32_to_cpu(*val); + + return ret; +} + +static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + *val = 0; + ret = b53_spi_read(dev, page, reg, (u8 *)val, 6); + if (!ret) + *val = le64_to_cpu(*val); + + return ret; +} + +static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 8); + + if (!ret) + *val = le64_to_cpu(*val); + + return ret; +} + +static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[3]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + txbuf[2] = value; + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[4]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le16(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[6]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le32(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[10]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le64(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf) - 2); +} + +static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[10]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le64(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static struct b53_io_ops b53_spi_ops = { + .read8 = b53_spi_read8, + .read16 = b53_spi_read16, + .read32 = b53_spi_read32, + .read48 = b53_spi_read48, + .read64 = b53_spi_read64, + .write8 = b53_spi_write8, + .write16 = b53_spi_write16, + .write32 = b53_spi_write32, + .write48 = b53_spi_write48, + .write64 = b53_spi_write64, +}; + +static int b53_spi_probe(struct spi_device *spi) +{ + struct b53_device *dev; + int ret; + + dev = b53_switch_alloc(&spi->dev, &b53_spi_ops, spi); + if (!dev) + return -ENOMEM; + + if (spi->dev.platform_data) + dev->pdata = spi->dev.platform_data; + + ret = b53_switch_register(dev); + if (ret) + return ret; + + spi_set_drvdata(spi, dev); + + return 0; +} + +static int b53_spi_remove(struct spi_device *spi) +{ + struct b53_device *dev = spi_get_drvdata(spi); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static struct spi_driver b53_spi_driver = { + .driver = { + .name = "b53-switch", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = b53_spi_probe, + .remove = b53_spi_remove, +}; + +module_spi_driver(b53_spi_driver); + +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 SPI access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/dsa/b53/b53_srab.c b/drivers/net/dsa/b53/b53_srab.c new file mode 100644 index 000000000000..70fd47284535 --- /dev/null +++ b/drivers/net/dsa/b53/b53_srab.c @@ -0,0 +1,415 @@ +/* + * B53 register access through Switch Register Access Bridge Registers + * + * Copyright (C) 2013 Hauke Mehrtens + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "b53_priv.h" + +/* command and status register of the SRAB */ +#define B53_SRAB_CMDSTAT 0x2c +#define B53_SRAB_CMDSTAT_RST BIT(2) +#define B53_SRAB_CMDSTAT_WRITE BIT(1) +#define B53_SRAB_CMDSTAT_GORDYN BIT(0) +#define B53_SRAB_CMDSTAT_PAGE 24 +#define B53_SRAB_CMDSTAT_REG 16 + +/* high order word of write data to switch registe */ +#define B53_SRAB_WD_H 0x30 + +/* low order word of write data to switch registe */ +#define B53_SRAB_WD_L 0x34 + +/* high order word of read data from switch register */ +#define B53_SRAB_RD_H 0x38 + +/* low order word of read data from switch register */ +#define B53_SRAB_RD_L 0x3c + +/* command and status register of the SRAB */ +#define B53_SRAB_CTRLS 0x40 +#define B53_SRAB_CTRLS_RCAREQ BIT(3) +#define B53_SRAB_CTRLS_RCAGNT BIT(4) +#define B53_SRAB_CTRLS_SW_INIT_DONE BIT(6) + +/* the register captures interrupt pulses from the switch */ +#define B53_SRAB_INTR 0x44 +#define B53_SRAB_INTR_P(x) BIT(x) +#define B53_SRAB_SWITCH_PHY BIT(8) +#define B53_SRAB_1588_SYNC BIT(9) +#define B53_SRAB_IMP1_SLEEP_TIMER BIT(10) +#define B53_SRAB_P7_SLEEP_TIMER BIT(11) +#define B53_SRAB_IMP0_SLEEP_TIMER BIT(12) + +struct b53_srab_priv { + void __iomem *regs; +}; + +static int b53_srab_request_grant(struct b53_device *dev) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + u32 ctrls; + int i; + + ctrls = readl(regs + B53_SRAB_CTRLS); + ctrls |= B53_SRAB_CTRLS_RCAREQ; + writel(ctrls, regs + B53_SRAB_CTRLS); + + for (i = 0; i < 20; i++) { + ctrls = readl(regs + B53_SRAB_CTRLS); + if (ctrls & B53_SRAB_CTRLS_RCAGNT) + break; + usleep_range(10, 100); + } + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static void b53_srab_release_grant(struct b53_device *dev) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + u32 ctrls; + + ctrls = readl(regs + B53_SRAB_CTRLS); + ctrls &= ~B53_SRAB_CTRLS_RCAREQ; + writel(ctrls, regs + B53_SRAB_CTRLS); +} + +static int b53_srab_op(struct b53_device *dev, u8 page, u8 reg, u32 op) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int i; + u32 cmdstat; + + /* set register address */ + cmdstat = (page << B53_SRAB_CMDSTAT_PAGE) | + (reg << B53_SRAB_CMDSTAT_REG) | + B53_SRAB_CMDSTAT_GORDYN | + op; + writel(cmdstat, regs + B53_SRAB_CMDSTAT); + + /* check if operation completed */ + for (i = 0; i < 5; ++i) { + cmdstat = readl(regs + B53_SRAB_CMDSTAT); + if (!(cmdstat & B53_SRAB_CMDSTAT_GORDYN)) + break; + usleep_range(10, 100); + } + + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static int b53_srab_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L) & 0xff; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L) & 0xffff; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + *val += ((u64)readl(regs + B53_SRAB_RD_H) & 0xffff) << 32; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + *val += (u64)readl(regs + B53_SRAB_RD_H) << 32; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel((u32)value, regs + B53_SRAB_WD_L); + writel((u16)(value >> 32), regs + B53_SRAB_WD_H); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + struct b53_srab_priv *priv = dev->priv; + u8 __iomem *regs = priv->regs; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel((u32)value, regs + B53_SRAB_WD_L); + writel((u32)(value >> 32), regs + B53_SRAB_WD_H); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static struct b53_io_ops b53_srab_ops = { + .read8 = b53_srab_read8, + .read16 = b53_srab_read16, + .read32 = b53_srab_read32, + .read48 = b53_srab_read48, + .read64 = b53_srab_read64, + .write8 = b53_srab_write8, + .write16 = b53_srab_write16, + .write32 = b53_srab_write32, + .write48 = b53_srab_write48, + .write64 = b53_srab_write64, +}; + +static int b53_srab_probe(struct platform_device *pdev) +{ + struct b53_srab_priv *priv; + struct b53_device *dev; + struct resource *r; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(priv->regs)) + return -ENOMEM; + + dev = b53_switch_alloc(&pdev->dev, &b53_srab_ops, priv); + if (!dev) + return -ENOMEM; + + platform_set_drvdata(pdev, dev); + + return b53_switch_register(dev); +} + +static int b53_srab_remove(struct platform_device *pdev) +{ + struct b53_device *dev = platform_get_drvdata(pdev); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static const struct of_device_id b53_srab_of_match[] = { + { .compatible = "brcm,bcm53010-srab" }, + { .compatible = "brcm,bcm53011-srab" }, + { .compatible = "brcm,bcm53012-srab" }, + { .compatible = "brcm,bcm53018-srab" }, + { .compatible = "brcm,bcm53019-srab" }, + { .compatible = "brcm,bcm5301x-srab" }, + { /* sentinel */ }, +}; + +static struct platform_driver b53_srab_driver = { + .probe = b53_srab_probe, + .remove = b53_srab_remove, + .driver = { + .name = "b53-srab-switch", + .of_match_table = b53_srab_of_match, + }, +}; + +module_platform_driver(b53_srab_driver); +MODULE_AUTHOR("Hauke Mehrtens "); +MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/include/linux/platform_data/b53.h b/include/linux/platform_data/b53.h new file mode 100644 index 000000000000..69d279c0da96 --- /dev/null +++ b/include/linux/platform_data/b53.h @@ -0,0 +1,33 @@ +/* + * B53 platform data + * + * Copyright (C) 2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_H +#define __B53_H + +#include + +struct b53_platform_data { + u32 chip_id; + u16 enabled_ports; + + /* only used by MMAP'd driver */ + unsigned big_endian:1; + void __iomem *regs; +}; + +#endif -- cgit v1.2.3 From 6deb2087b8683cb64362b7ff0eeba8df0bf5a07d Mon Sep 17 00:00:00 2001 From: Pramod Kumar Date: Fri, 10 Jun 2016 11:03:46 +0530 Subject: binding: Make "mdio-parent-bus" property from mandatory to optional Change "mdio-parent-bus" from mandatory section to optional as it won't be required by integrated MDIO multiplexer which has bus selection and mdio transaction generation logic, integrated inside. Signed-off-by: Pramod Kumar Reviewed-by: Andrew Lunn Acked-by: Rob Herring Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/mdio-mux.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/mdio-mux.txt b/Documentation/devicetree/bindings/net/mdio-mux.txt index 491f5bd55203..f58571f36570 100644 --- a/Documentation/devicetree/bindings/net/mdio-mux.txt +++ b/Documentation/devicetree/bindings/net/mdio-mux.txt @@ -5,11 +5,12 @@ numbered uniquely in a device dependent manner. The nodes for an MDIO bus multiplexer/switch will have one child node for each child bus. Required properties: -- mdio-parent-bus : phandle to the parent MDIO bus. - #address-cells = <1>; - #size-cells = <0>; Optional properties: +- mdio-parent-bus : phandle to the parent MDIO bus. + - Other properties specific to the multiplexer/switch hardware. Required properties for child nodes: -- cgit v1.2.3 From ce8d5dbfd64f6ca84e2fe55a0066ec8b22601567 Mon Sep 17 00:00:00 2001 From: Pramod Kumar Date: Fri, 10 Jun 2016 11:03:47 +0530 Subject: binding: mdio-mux: Add DT binding doc for Broadcom MDIO bus multiplexer Add DT binding doc for Broadcom MDIO bus multiplexer driver. Reviewed-by: Andrew Lunn Signed-off-by: Pramod Kumar Reviewed-by: Florian Fainelli Acked-by: Rob Herring Signed-off-by: David S. Miller --- .../bindings/net/brcm,mdio-mux-iproc.txt | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/brcm,mdio-mux-iproc.txt (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/brcm,mdio-mux-iproc.txt b/Documentation/devicetree/bindings/net/brcm,mdio-mux-iproc.txt new file mode 100644 index 000000000000..dfe287a5d6f2 --- /dev/null +++ b/Documentation/devicetree/bindings/net/brcm,mdio-mux-iproc.txt @@ -0,0 +1,59 @@ +Properties for an MDIO bus multiplexer found in Broadcom iProc based SoCs. + +This MDIO bus multiplexer defines buses that could be internal as well as +external to SoCs and could accept MDIO transaction compatible to C-22 or +C-45 Clause. When child bus is selected, one needs to select these two +properties as well to generate desired MDIO transaction on appropriate bus. + +Required properties in addition to the generic multiplexer properties: + +MDIO multiplexer node: +- compatible: brcm,mdio-mux-iproc. + +Every non-ethernet PHY requires a compatible so that it could be probed based +on this compatible string. + +Additional information regarding generic multiplexer properties can be found +at- Documentation/devicetree/bindings/net/mdio-mux.txt + + +for example: + mdio_mux_iproc: mdio-mux@6602023c { + compatible = "brcm,mdio-mux-iproc"; + reg = <0x6602023c 0x14>; + #address-cells = <1>; + #size-cells = <0>; + + mdio@0 { + reg = <0x0>; + #address-cells = <1>; + #size-cells = <0>; + + pci_phy0: pci-phy@0 { + compatible = "brcm,ns2-pcie-phy"; + reg = <0x0>; + #phy-cells = <0>; + }; + }; + + mdio@7 { + reg = <0x7>; + #address-cells = <1>; + #size-cells = <0>; + + pci_phy1: pci-phy@0 { + compatible = "brcm,ns2-pcie-phy"; + reg = <0x0>; + #phy-cells = <0>; + }; + }; + mdio@10 { + reg = <0x10>; + #address-cells = <1>; + #size-cells = <0>; + + gphy0: eth-phy@10 { + reg = <0x10>; + }; + }; + }; -- cgit v1.2.3 From 464e3b4b118f5c7abfdb68f3628df0ca65d28948 Mon Sep 17 00:00:00 2001 From: Pramod Kumar Date: Fri, 10 Jun 2016 11:03:50 +0530 Subject: binding: PHY: Binding doc for NS2 PCIe PHYs. Binding doc for NS2 PCIe PHYs. Signed-off-by: Pramod Kumar Signed-off-by: Jon Mason Acked-by: Rob Herring Signed-off-by: David S. Miller --- .../bindings/phy/brcm,mdio-mux-bus-pci.txt | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/brcm,mdio-mux-bus-pci.txt (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/phy/brcm,mdio-mux-bus-pci.txt b/Documentation/devicetree/bindings/phy/brcm,mdio-mux-bus-pci.txt new file mode 100644 index 000000000000..5b51007c6f24 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/brcm,mdio-mux-bus-pci.txt @@ -0,0 +1,27 @@ +* Broadcom NS2 PCIe PHY binding document + +Required bus properties: +- reg: MDIO Bus number for the MDIO interface +- #address-cells: must be 1 +- #size-cells: must be 0 + +Required PHY properties: +- compatible: should be "brcm,ns2-pcie-phy" +- reg: MDIO Phy ID for the MDIO interface +- #phy-cells: must be 0 + +This is a child bus node of "brcm,mdio-mux-iproc" node. + +Example: + +mdio@0 { + reg = <0x0>; + #address-cells = <1>; + #size-cells = <0>; + + pci_phy0: pci-phy@0 { + compatible = "brcm,ns2-pcie-phy"; + reg = <0x0>; + #phy-cells = <0>; + }; +}; -- cgit v1.2.3 From 818d49ad164cb680983568e3307a30012ab66a23 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Mon, 13 Jun 2016 18:52:17 +0300 Subject: dt: bindings: Add bindings for Cirrus Logic CS89x0 ethernet chip Add device tree binding documentation details for Cirrus Logic CS8900/CS8920 ethernet chip. Signed-off-by: Alexander Shiyan Acked-by: Rob Herring Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/cirrus,cs89x0.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/cirrus,cs89x0.txt (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/cirrus,cs89x0.txt b/Documentation/devicetree/bindings/net/cirrus,cs89x0.txt new file mode 100644 index 000000000000..c070076bacb9 --- /dev/null +++ b/Documentation/devicetree/bindings/net/cirrus,cs89x0.txt @@ -0,0 +1,13 @@ +* Cirrus Logic CS8900/CS8920 Network Controller + +Required properties: +- compatible : Should be "cirrus,cs8900" or "cirrus,cs8920". +- reg : Address and length of the IO space. +- interrupts : Should contain the controller interrupt line. + +Examples: + eth0: eth@10000000 { + compatible = "cirrus,cs8900"; + reg = <0x10000000 0x400>; + interrupts = <10>; + }; -- cgit v1.2.3 From dd3bd23eb438919aeeb73a7116642731ef8f73a8 Mon Sep 17 00:00:00 2001 From: Ramesh Shanmugasundaram Date: Fri, 17 Jun 2016 09:20:55 +0100 Subject: can: rcar_canfd: Add Renesas R-Car CAN FD driver This patch adds support for the CAN FD controller found in Renesas R-Car SoCs. The controller operates in CAN FD only mode by default. CAN FD mode supports both Classical CAN & CAN FD frame formats. The controller supports ISO 11898-1:2015 CAN FD format only. This controller supports two channels and the driver can enable either or both of the channels. Driver uses Rx FIFOs (one per channel) for reception & Common FIFOs (one per channel) for transmission. Rx filter rules are configured to the minimum (one per channel) and it accepts Standard, Extended, Data & Remote Frame combinations. Note: There are few documentation errors in R-Car Gen3 Hardware User Manual v0.5E with respect to CAN FD controller. They are listed below: 1. CAN FD interrupt numbers 29 & 30 are listed as per channel interrupts. However, they are common to both channels (i.e.) they are global and channel interrupts respectively. 2. CANFD clock is derived from PLL1. This is not documented. 3. CANFD clock is further divided by (1/2) within the CAN FD controller. This is not documented. 4. The minimum value of NTSEG1 in RSCFDnCFDCmNCFG register is 2 Tq. It is specified 4 Tq in the manual. 5. The maximum number of message RAM area the controller can use is 3584 bytes. It is specified 10752 bytes in the manual. Signed-off-by: Ramesh Shanmugasundaram Acked-by: Rob Herring Reviewed-by: Ulrich Hecht Signed-off-by: Marc Kleine-Budde --- .../devicetree/bindings/net/can/rcar_canfd.txt | 89 + drivers/net/can/Kconfig | 1 + drivers/net/can/Makefile | 1 + drivers/net/can/rcar/Kconfig | 11 + drivers/net/can/rcar/Makefile | 5 + drivers/net/can/rcar/rcar_canfd.c | 1695 ++++++++++++++++++++ 6 files changed, 1802 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/can/rcar_canfd.txt create mode 100644 drivers/net/can/rcar/Kconfig create mode 100644 drivers/net/can/rcar/Makefile create mode 100644 drivers/net/can/rcar/rcar_canfd.c (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/can/rcar_canfd.txt b/Documentation/devicetree/bindings/net/can/rcar_canfd.txt new file mode 100644 index 000000000000..d45182bd21dd --- /dev/null +++ b/Documentation/devicetree/bindings/net/can/rcar_canfd.txt @@ -0,0 +1,89 @@ +Renesas R-Car CAN FD controller Device Tree Bindings +---------------------------------------------------- + +Required properties: +- compatible: Must contain one or more of the following: + - "renesas,rcar-gen3-canfd" for R-Car Gen3 compatible controller. + - "renesas,r8a7795-canfd" for R8A7795 (R-Car H3) compatible controller. + + When compatible with the generic version, nodes must list the + SoC-specific version corresponding to the platform first, followed by the + family-specific and/or generic versions. + +- reg: physical base address and size of the R-Car CAN FD register map. +- interrupts: interrupt specifier for the Global & Channel interrupts +- clocks: phandles and clock specifiers for 3 clock inputs. +- clock-names: 3 clock input name strings: "fck", "canfd", "can_clk". +- pinctrl-0: pin control group to be used for this controller. +- pinctrl-names: must be "default". + +Required child nodes: +The controller supports two channels and each is represented as a child node. +The name of the child nodes are "channel0" and "channel1" respectively. Each +child node supports the "status" property only, which is used to +enable/disable the respective channel. + +Required properties for "renesas,r8a7795-canfd" compatible: +In R8A7795 SoC, canfd clock is a div6 clock and can be used by both CAN +and CAN FD controller at the same time. It needs to be scaled to maximum +frequency if any of these controllers use it. This is done using the +below properties. + +- assigned-clocks: phandle of canfd clock. +- assigned-clock-rates: maximum frequency of this clock. + +Example +------- + +SoC common .dtsi file: + + canfd: can@e66c0000 { + compatible = "renesas,r8a7795-canfd", + "renesas,rcar-gen3-canfd"; + reg = <0 0xe66c0000 0 0x8000>; + interrupts = , + ; + clocks = <&cpg CPG_MOD 914>, + <&cpg CPG_CORE R8A7795_CLK_CANFD>, + <&can_clk>; + clock-names = "fck", "canfd", "can_clk"; + assigned-clocks = <&cpg CPG_CORE R8A7795_CLK_CANFD>; + assigned-clock-rates = <40000000>; + power-domains = <&cpg>; + status = "disabled"; + + channel0 { + status = "disabled"; + }; + + channel1 { + status = "disabled"; + }; + }; + +Board specific .dts file: + +E.g. below enables Channel 1 alone in the board. + +&canfd { + pinctrl-0 = <&canfd1_pins>; + pinctrl-names = "default"; + status = "okay"; + + channel1 { + status = "okay"; + }; +}; + +E.g. below enables Channel 0 alone in the board using External clock +as fCAN clock. + +&canfd { + pinctrl-0 = <&canfd0_pins &can_clk_pins>; + pinctrl-names = "default"; + status = "okay"; + + channel0 { + status = "okay"; + }; +}; diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index 0d40aef928e2..13003a9472f2 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -152,6 +152,7 @@ source "drivers/net/can/cc770/Kconfig" source "drivers/net/can/ifi_canfd/Kconfig" source "drivers/net/can/m_can/Kconfig" source "drivers/net/can/mscan/Kconfig" +source "drivers/net/can/rcar/Kconfig" source "drivers/net/can/sja1000/Kconfig" source "drivers/net/can/softing/Kconfig" source "drivers/net/can/spi/Kconfig" diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index e3db0c807f55..226d5b516746 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -10,6 +10,7 @@ can-dev-y := dev.o can-dev-$(CONFIG_CAN_LEDS) += led.o +obj-y += rcar/ obj-y += spi/ obj-y += usb/ obj-y += softing/ diff --git a/drivers/net/can/rcar/Kconfig b/drivers/net/can/rcar/Kconfig new file mode 100644 index 000000000000..6ea64d19ec26 --- /dev/null +++ b/drivers/net/can/rcar/Kconfig @@ -0,0 +1,11 @@ +config CAN_RCAR_CANFD + tristate "Renesas R-Car CAN FD controller" + depends on ARCH_RENESAS || ARM + ---help--- + Say Y here if you want to use CAN FD controller found on + Renesas R-Car SoCs. The driver puts the controller in CAN FD only + mode, which can interoperate with CAN2.0 nodes but does not support + dedicated CAN 2.0 mode. + + To compile this driver as a module, choose M here: the module will + be called rcar_canfd. diff --git a/drivers/net/can/rcar/Makefile b/drivers/net/can/rcar/Makefile new file mode 100644 index 000000000000..cbaf498e1ac7 --- /dev/null +++ b/drivers/net/can/rcar/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the Renesas R-Car CAN FD controller driver +# + +obj-$(CONFIG_CAN_RCAR_CANFD) += rcar_canfd.o diff --git a/drivers/net/can/rcar/rcar_canfd.c b/drivers/net/can/rcar/rcar_canfd.c new file mode 100644 index 000000000000..a39d9224166c --- /dev/null +++ b/drivers/net/can/rcar/rcar_canfd.c @@ -0,0 +1,1695 @@ +/* Renesas R-Car CAN FD device driver + * + * Copyright (C) 2015 Renesas Electronics Corp. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +/* The R-Car CAN FD controller can operate in either one of the below two modes + * - CAN FD only mode + * - Classical CAN (CAN 2.0) only mode + * + * This driver puts the controller in CAN FD only mode by default. In this + * mode, the controller acts as a CAN FD node that can also interoperate with + * CAN 2.0 nodes. + * + * As of now, this driver does not support the Classical CAN (CAN 2.0) mode, + * which is handled by a different register map compared to CAN FD only mode. + * + * Note: The h/w manual register naming convention is clumsy and not acceptable + * to use as it is in the driver. However, those names are added as comments + * wherever it is modified to a readable name. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RCANFD_DRV_NAME "rcar_canfd" + +/* Global register bits */ + +/* RSCFDnCFDGRMCFG */ +#define RCANFD_GRMCFG_RCMC BIT(0) + +/* RSCFDnCFDGCFG */ +#define RCANFD_GCFG_CMPOC BIT(5) +#define RCANFD_GCFG_DCS BIT(4) +#define RCANFD_GCFG_DCE BIT(1) +#define RCANFD_GCFG_TPRI BIT(0) + +/* RSCFDnCFDGCTR */ +#define RCANFD_GCTR_TSRST BIT(16) +#define RCANFD_GCTR_CFMPOFIE BIT(11) +#define RCANFD_GCTR_THLEIE BIT(10) +#define RCANFD_GCTR_MEIE BIT(9) +#define RCANFD_GCTR_DEIE BIT(8) +#define RCANFD_GCTR_GSLPR BIT(2) +#define RCANFD_GCTR_GMDC_MASK (0x3) +#define RCANFD_GCTR_GMDC_GOPM (0x0) +#define RCANFD_GCTR_GMDC_GRESET (0x1) +#define RCANFD_GCTR_GMDC_GTEST (0x2) + +/* RSCFDnCFDGSTS */ +#define RCANFD_GSTS_GRAMINIT BIT(3) +#define RCANFD_GSTS_GSLPSTS BIT(2) +#define RCANFD_GSTS_GHLTSTS BIT(1) +#define RCANFD_GSTS_GRSTSTS BIT(0) +/* Non-operational status */ +#define RCANFD_GSTS_GNOPM (BIT(0) | BIT(1) | BIT(2) | BIT(3)) + +/* RSCFDnCFDGERFL */ +#define RCANFD_GERFL_EEF1 BIT(17) +#define RCANFD_GERFL_EEF0 BIT(16) +#define RCANFD_GERFL_CMPOF BIT(3) +#define RCANFD_GERFL_THLES BIT(2) +#define RCANFD_GERFL_MES BIT(1) +#define RCANFD_GERFL_DEF BIT(0) + +#define RCANFD_GERFL_ERR(x) ((x) & (RCANFD_GERFL_EEF1 |\ + RCANFD_GERFL_EEF0 |\ + RCANFD_GERFL_MES |\ + RCANFD_GERFL_CMPOF)) + +/* AFL Rx rules registers */ + +/* RSCFDnCFDGAFLCFG0 */ +#define RCANFD_GAFLCFG_SETRNC(n, x) (((x) & 0xff) << (24 - n * 8)) +#define RCANFD_GAFLCFG_GETRNC(n, x) (((x) >> (24 - n * 8)) & 0xff) + +/* RSCFDnCFDGAFLECTR */ +#define RCANFD_GAFLECTR_AFLDAE BIT(8) +#define RCANFD_GAFLECTR_AFLPN(x) ((x) & 0x1f) + +/* RSCFDnCFDGAFLIDj */ +#define RCANFD_GAFLID_GAFLLB BIT(29) + +/* RSCFDnCFDGAFLP1_j */ +#define RCANFD_GAFLP1_GAFLFDP(x) (1 << (x)) + +/* Channel register bits */ + +/* RSCFDnCFDCmNCFG */ +#define RCANFD_NCFG_NTSEG2(x) (((x) & 0x1f) << 24) +#define RCANFD_NCFG_NTSEG1(x) (((x) & 0x7f) << 16) +#define RCANFD_NCFG_NSJW(x) (((x) & 0x1f) << 11) +#define RCANFD_NCFG_NBRP(x) (((x) & 0x3ff) << 0) + +/* RSCFDnCFDCmCTR */ +#define RCANFD_CCTR_CTME BIT(24) +#define RCANFD_CCTR_ERRD BIT(23) +#define RCANFD_CCTR_BOM_MASK (0x3 << 21) +#define RCANFD_CCTR_BOM_ISO (0x0 << 21) +#define RCANFD_CCTR_BOM_BENTRY (0x1 << 21) +#define RCANFD_CCTR_BOM_BEND (0x2 << 21) +#define RCANFD_CCTR_TDCVFIE BIT(19) +#define RCANFD_CCTR_SOCOIE BIT(18) +#define RCANFD_CCTR_EOCOIE BIT(17) +#define RCANFD_CCTR_TAIE BIT(16) +#define RCANFD_CCTR_ALIE BIT(15) +#define RCANFD_CCTR_BLIE BIT(14) +#define RCANFD_CCTR_OLIE BIT(13) +#define RCANFD_CCTR_BORIE BIT(12) +#define RCANFD_CCTR_BOEIE BIT(11) +#define RCANFD_CCTR_EPIE BIT(10) +#define RCANFD_CCTR_EWIE BIT(9) +#define RCANFD_CCTR_BEIE BIT(8) +#define RCANFD_CCTR_CSLPR BIT(2) +#define RCANFD_CCTR_CHMDC_MASK (0x3) +#define RCANFD_CCTR_CHDMC_COPM (0x0) +#define RCANFD_CCTR_CHDMC_CRESET (0x1) +#define RCANFD_CCTR_CHDMC_CHLT (0x2) + +/* RSCFDnCFDCmSTS */ +#define RCANFD_CSTS_COMSTS BIT(7) +#define RCANFD_CSTS_RECSTS BIT(6) +#define RCANFD_CSTS_TRMSTS BIT(5) +#define RCANFD_CSTS_BOSTS BIT(4) +#define RCANFD_CSTS_EPSTS BIT(3) +#define RCANFD_CSTS_SLPSTS BIT(2) +#define RCANFD_CSTS_HLTSTS BIT(1) +#define RCANFD_CSTS_CRSTSTS BIT(0) + +#define RCANFD_CSTS_TECCNT(x) (((x) >> 24) & 0xff) +#define RCANFD_CSTS_RECCNT(x) (((x) >> 16) & 0xff) + +/* RSCFDnCFDCmERFL */ +#define RCANFD_CERFL_ADERR BIT(14) +#define RCANFD_CERFL_B0ERR BIT(13) +#define RCANFD_CERFL_B1ERR BIT(12) +#define RCANFD_CERFL_CERR BIT(11) +#define RCANFD_CERFL_AERR BIT(10) +#define RCANFD_CERFL_FERR BIT(9) +#define RCANFD_CERFL_SERR BIT(8) +#define RCANFD_CERFL_ALF BIT(7) +#define RCANFD_CERFL_BLF BIT(6) +#define RCANFD_CERFL_OVLF BIT(5) +#define RCANFD_CERFL_BORF BIT(4) +#define RCANFD_CERFL_BOEF BIT(3) +#define RCANFD_CERFL_EPF BIT(2) +#define RCANFD_CERFL_EWF BIT(1) +#define RCANFD_CERFL_BEF BIT(0) + +#define RCANFD_CERFL_ERR(x) ((x) & (0x7fff)) /* above bits 14:0 */ + +/* RSCFDnCFDCmDCFG */ +#define RCANFD_DCFG_DSJW(x) (((x) & 0x7) << 24) +#define RCANFD_DCFG_DTSEG2(x) (((x) & 0x7) << 20) +#define RCANFD_DCFG_DTSEG1(x) (((x) & 0xf) << 16) +#define RCANFD_DCFG_DBRP(x) (((x) & 0xff) << 0) + +/* RSCFDnCFDCmFDCFG */ +#define RCANFD_FDCFG_TDCE BIT(9) +#define RCANFD_FDCFG_TDCOC BIT(8) +#define RCANFD_FDCFG_TDCO(x) (((x) & 0x7f) >> 16) + +/* RSCFDnCFDRFCCx */ +#define RCANFD_RFCC_RFIM BIT(12) +#define RCANFD_RFCC_RFDC(x) (((x) & 0x7) << 8) +#define RCANFD_RFCC_RFPLS(x) (((x) & 0x7) << 4) +#define RCANFD_RFCC_RFIE BIT(1) +#define RCANFD_RFCC_RFE BIT(0) + +/* RSCFDnCFDRFSTSx */ +#define RCANFD_RFSTS_RFIF BIT(3) +#define RCANFD_RFSTS_RFMLT BIT(2) +#define RCANFD_RFSTS_RFFLL BIT(1) +#define RCANFD_RFSTS_RFEMP BIT(0) + +/* RSCFDnCFDRFIDx */ +#define RCANFD_RFID_RFIDE BIT(31) +#define RCANFD_RFID_RFRTR BIT(30) + +/* RSCFDnCFDRFPTRx */ +#define RCANFD_RFPTR_RFDLC(x) (((x) >> 28) & 0xf) +#define RCANFD_RFPTR_RFPTR(x) (((x) >> 16) & 0xfff) +#define RCANFD_RFPTR_RFTS(x) (((x) >> 0) & 0xffff) + +/* RSCFDnCFDRFFDSTSx */ +#define RCANFD_RFFDSTS_RFFDF BIT(2) +#define RCANFD_RFFDSTS_RFBRS BIT(1) +#define RCANFD_RFFDSTS_RFESI BIT(0) + +/* Common FIFO bits */ + +/* RSCFDnCFDCFCCk */ +#define RCANFD_CFCC_CFTML(x) (((x) & 0xf) << 20) +#define RCANFD_CFCC_CFM(x) (((x) & 0x3) << 16) +#define RCANFD_CFCC_CFIM BIT(12) +#define RCANFD_CFCC_CFDC(x) (((x) & 0x7) << 8) +#define RCANFD_CFCC_CFPLS(x) (((x) & 0x7) << 4) +#define RCANFD_CFCC_CFTXIE BIT(2) +#define RCANFD_CFCC_CFE BIT(0) + +/* RSCFDnCFDCFSTSk */ +#define RCANFD_CFSTS_CFMC(x) (((x) >> 8) & 0xff) +#define RCANFD_CFSTS_CFTXIF BIT(4) +#define RCANFD_CFSTS_CFMLT BIT(2) +#define RCANFD_CFSTS_CFFLL BIT(1) +#define RCANFD_CFSTS_CFEMP BIT(0) + +/* RSCFDnCFDCFIDk */ +#define RCANFD_CFID_CFIDE BIT(31) +#define RCANFD_CFID_CFRTR BIT(30) +#define RCANFD_CFID_CFID_MASK(x) ((x) & 0x1fffffff) + +/* RSCFDnCFDCFPTRk */ +#define RCANFD_CFPTR_CFDLC(x) (((x) & 0xf) << 28) +#define RCANFD_CFPTR_CFPTR(x) (((x) & 0xfff) << 16) +#define RCANFD_CFPTR_CFTS(x) (((x) & 0xff) << 0) + +/* RSCFDnCFDCFFDCSTSk */ +#define RCANFD_CFFDCSTS_CFFDF BIT(2) +#define RCANFD_CFFDCSTS_CFBRS BIT(1) +#define RCANFD_CFFDCSTS_CFESI BIT(0) + +/* This controller supports classical CAN only mode or CAN FD only mode. These + * modes are supported in two separate set of register maps & names. However, + * some of the register offsets are common for both modes. Those offsets are + * listed below as Common registers. + * + * The CAN FD only specific registers are listed separately and their names + * starts with RCANFD_F_xxx names. When classical CAN only specific registers + * are added, those specific registers can be prefixed as RCANFD_C_xxx. + */ + +/* Common registers */ + +/* RSCFDnCFDCmNCFG / RSCFDnCmCFG */ +#define RCANFD_CCFG(m) (0x0000 + (0x10 * (m))) +/* RSCFDnCFDCmCTR / RSCFDnCmCTR */ +#define RCANFD_CCTR(m) (0x0004 + (0x10 * (m))) +/* RSCFDnCFDCmSTS / RSCFDnCmSTS */ +#define RCANFD_CSTS(m) (0x0008 + (0x10 * (m))) +/* RSCFDnCFDCmERFL / RSCFDnCmERFL */ +#define RCANFD_CERFL(m) (0x000C + (0x10 * (m))) + +/* RSCFDnCFDGCFG / RSCFDnGCFG */ +#define RCANFD_GCFG (0x0084) +/* RSCFDnCFDGCTR / RSCFDnGCTR */ +#define RCANFD_GCTR (0x0088) +/* RSCFDnCFDGCTS / RSCFDnGCTS */ +#define RCANFD_GSTS (0x008c) +/* RSCFDnCFDGERFL / RSCFDnGERFL */ +#define RCANFD_GERFL (0x0090) +/* RSCFDnCFDGTSC / RSCFDnGTSC */ +#define RCANFD_GTSC (0x0094) +/* RSCFDnCFDGAFLECTR / RSCFDnGAFLECTR */ +#define RCANFD_GAFLECTR (0x0098) +/* RSCFDnCFDGAFLCFG0 / RSCFDnGAFLCFG0 */ +#define RCANFD_GAFLCFG0 (0x009c) +/* RSCFDnCFDGAFLCFG1 / RSCFDnGAFLCFG1 */ +#define RCANFD_GAFLCFG1 (0x00a0) +/* RSCFDnCFDRMNB / RSCFDnRMNB */ +#define RCANFD_RMNB (0x00a4) +/* RSCFDnCFDRMND / RSCFDnRMND */ +#define RCANFD_RMND(y) (0x00a8 + (0x04 * (y))) + +/* RSCFDnCFDRFCCx / RSCFDnRFCCx */ +#define RCANFD_RFCC(x) (0x00b8 + (0x04 * (x))) +/* RSCFDnCFDRFSTSx / RSCFDnRFSTSx */ +#define RCANFD_RFSTS(x) (0x00d8 + (0x04 * (x))) +/* RSCFDnCFDRFPCTRx / RSCFDnRFPCTRx */ +#define RCANFD_RFPCTR(x) (0x00f8 + (0x04 * (x))) + +/* Common FIFO Control registers */ + +/* RSCFDnCFDCFCCx / RSCFDnCFCCx */ +#define RCANFD_CFCC(ch, idx) (0x0118 + (0x0c * (ch)) + \ + (0x04 * (idx))) +/* RSCFDnCFDCFSTSx / RSCFDnCFSTSx */ +#define RCANFD_CFSTS(ch, idx) (0x0178 + (0x0c * (ch)) + \ + (0x04 * (idx))) +/* RSCFDnCFDCFPCTRx / RSCFDnCFPCTRx */ +#define RCANFD_CFPCTR(ch, idx) (0x01d8 + (0x0c * (ch)) + \ + (0x04 * (idx))) + +/* RSCFDnCFDFESTS / RSCFDnFESTS */ +#define RCANFD_FESTS (0x0238) +/* RSCFDnCFDFFSTS / RSCFDnFFSTS */ +#define RCANFD_FFSTS (0x023c) +/* RSCFDnCFDFMSTS / RSCFDnFMSTS */ +#define RCANFD_FMSTS (0x0240) +/* RSCFDnCFDRFISTS / RSCFDnRFISTS */ +#define RCANFD_RFISTS (0x0244) +/* RSCFDnCFDCFRISTS / RSCFDnCFRISTS */ +#define RCANFD_CFRISTS (0x0248) +/* RSCFDnCFDCFTISTS / RSCFDnCFTISTS */ +#define RCANFD_CFTISTS (0x024c) + +/* RSCFDnCFDTMCp / RSCFDnTMCp */ +#define RCANFD_TMC(p) (0x0250 + (0x01 * (p))) +/* RSCFDnCFDTMSTSp / RSCFDnTMSTSp */ +#define RCANFD_TMSTS(p) (0x02d0 + (0x01 * (p))) + +/* RSCFDnCFDTMTRSTSp / RSCFDnTMTRSTSp */ +#define RCANFD_TMTRSTS(y) (0x0350 + (0x04 * (y))) +/* RSCFDnCFDTMTARSTSp / RSCFDnTMTARSTSp */ +#define RCANFD_TMTARSTS(y) (0x0360 + (0x04 * (y))) +/* RSCFDnCFDTMTCSTSp / RSCFDnTMTCSTSp */ +#define RCANFD_TMTCSTS(y) (0x0370 + (0x04 * (y))) +/* RSCFDnCFDTMTASTSp / RSCFDnTMTASTSp */ +#define RCANFD_TMTASTS(y) (0x0380 + (0x04 * (y))) +/* RSCFDnCFDTMIECy / RSCFDnTMIECy */ +#define RCANFD_TMIEC(y) (0x0390 + (0x04 * (y))) + +/* RSCFDnCFDTXQCCm / RSCFDnTXQCCm */ +#define RCANFD_TXQCC(m) (0x03a0 + (0x04 * (m))) +/* RSCFDnCFDTXQSTSm / RSCFDnTXQSTSm */ +#define RCANFD_TXQSTS(m) (0x03c0 + (0x04 * (m))) +/* RSCFDnCFDTXQPCTRm / RSCFDnTXQPCTRm */ +#define RCANFD_TXQPCTR(m) (0x03e0 + (0x04 * (m))) + +/* RSCFDnCFDTHLCCm / RSCFDnTHLCCm */ +#define RCANFD_THLCC(m) (0x0400 + (0x04 * (m))) +/* RSCFDnCFDTHLSTSm / RSCFDnTHLSTSm */ +#define RCANFD_THLSTS(m) (0x0420 + (0x04 * (m))) +/* RSCFDnCFDTHLPCTRm / RSCFDnTHLPCTRm */ +#define RCANFD_THLPCTR(m) (0x0440 + (0x04 * (m))) + +/* RSCFDnCFDGTINTSTS0 / RSCFDnGTINTSTS0 */ +#define RCANFD_GTINTSTS0 (0x0460) +/* RSCFDnCFDGTINTSTS1 / RSCFDnGTINTSTS1 */ +#define RCANFD_GTINTSTS1 (0x0464) +/* RSCFDnCFDGTSTCFG / RSCFDnGTSTCFG */ +#define RCANFD_GTSTCFG (0x0468) +/* RSCFDnCFDGTSTCTR / RSCFDnGTSTCTR */ +#define RCANFD_GTSTCTR (0x046c) +/* RSCFDnCFDGLOCKK / RSCFDnGLOCKK */ +#define RCANFD_GLOCKK (0x047c) +/* RSCFDnCFDGRMCFG / RSCFDnGRMCFG */ +#define RCANFD_GRMCFG (0x04fc) + +/* RSCFDnCFDGAFLIDj / RSCFDnGAFLIDj */ +#define RCANFD_GAFLID(offset, j) ((offset) + (0x10 * (j))) +/* RSCFDnCFDGAFLMj / RSCFDnGAFLMj */ +#define RCANFD_GAFLM(offset, j) ((offset) + 0x04 + (0x10 * (j))) +/* RSCFDnCFDGAFLP0j / RSCFDnGAFLP0j */ +#define RCANFD_GAFLP0(offset, j) ((offset) + 0x08 + (0x10 * (j))) +/* RSCFDnCFDGAFLP1j / RSCFDnGAFLP1j */ +#define RCANFD_GAFLP1(offset, j) ((offset) + 0x0c + (0x10 * (j))) + +/* CAN FD mode specific regsiter map */ + +/* RSCFDnCFDCmXXX -> RCANFD_F_XXX(m) */ +#define RCANFD_F_DCFG(m) (0x0500 + (0x20 * (m))) +#define RCANFD_F_CFDCFG(m) (0x0504 + (0x20 * (m))) +#define RCANFD_F_CFDCTR(m) (0x0508 + (0x20 * (m))) +#define RCANFD_F_CFDSTS(m) (0x050c + (0x20 * (m))) +#define RCANFD_F_CFDCRC(m) (0x0510 + (0x20 * (m))) + +/* RSCFDnCFDGAFLXXXj offset */ +#define RCANFD_F_GAFL_OFFSET (0x1000) + +/* RSCFDnCFDRMXXXq -> RCANFD_F_RMXXX(q) */ +#define RCANFD_F_RMID(q) (0x2000 + (0x20 * (q))) +#define RCANFD_F_RMPTR(q) (0x2004 + (0x20 * (q))) +#define RCANFD_F_RMFDSTS(q) (0x2008 + (0x20 * (q))) +#define RCANFD_F_RMDF(q, b) (0x200c + (0x04 * (b)) + (0x20 * (q))) + +/* RSCFDnCFDRFXXx -> RCANFD_F_RFXX(x) */ +#define RCANFD_F_RFOFFSET (0x3000) +#define RCANFD_F_RFID(x) (RCANFD_F_RFOFFSET + (0x80 * (x))) +#define RCANFD_F_RFPTR(x) (RCANFD_F_RFOFFSET + 0x04 + \ + (0x80 * (x))) +#define RCANFD_F_RFFDSTS(x) (RCANFD_F_RFOFFSET + 0x08 + \ + (0x80 * (x))) +#define RCANFD_F_RFDF(x, df) (RCANFD_F_RFOFFSET + 0x0c + \ + (0x80 * (x)) + (0x04 * (df))) + +/* RSCFDnCFDCFXXk -> RCANFD_F_CFXX(ch, k) */ +#define RCANFD_F_CFOFFSET (0x3400) +#define RCANFD_F_CFID(ch, idx) (RCANFD_F_CFOFFSET + (0x180 * (ch)) + \ + (0x80 * (idx))) +#define RCANFD_F_CFPTR(ch, idx) (RCANFD_F_CFOFFSET + 0x04 + \ + (0x180 * (ch)) + (0x80 * (idx))) +#define RCANFD_F_CFFDCSTS(ch, idx) (RCANFD_F_CFOFFSET + 0x08 + \ + (0x180 * (ch)) + (0x80 * (idx))) +#define RCANFD_F_CFDF(ch, idx, df) (RCANFD_F_CFOFFSET + 0x0c + \ + (0x180 * (ch)) + (0x80 * (idx)) + \ + (0x04 * (df))) + +/* RSCFDnCFDTMXXp -> RCANFD_F_TMXX(p) */ +#define RCANFD_F_TMID(p) (0x4000 + (0x20 * (p))) +#define RCANFD_F_TMPTR(p) (0x4004 + (0x20 * (p))) +#define RCANFD_F_TMFDCTR(p) (0x4008 + (0x20 * (p))) +#define RCANFD_F_TMDF(p, b) (0x400c + (0x20 * (p)) + (0x04 * (b))) + +/* RSCFDnCFDTHLACCm */ +#define RCANFD_F_THLACC(m) (0x6000 + (0x04 * (m))) +/* RSCFDnCFDRPGACCr */ +#define RCANFD_F_RPGACC(r) (0x6400 + (0x04 * (r))) + +/* Constants */ +#define RCANFD_FIFO_DEPTH 8 /* Tx FIFO depth */ +#define RCANFD_NAPI_WEIGHT 8 /* Rx poll quota */ + +#define RCANFD_NUM_CHANNELS 2 /* Two channels max */ +#define RCANFD_CHANNELS_MASK BIT((RCANFD_NUM_CHANNELS) - 1) + +#define RCANFD_GAFL_PAGENUM(entry) ((entry) / 16) +#define RCANFD_CHANNEL_NUMRULES 1 /* only one rule per channel */ + +/* Rx FIFO is a global resource of the controller. There are 8 such FIFOs + * available. Each channel gets a dedicated Rx FIFO (i.e.) the channel + * number is added to RFFIFO index. + */ +#define RCANFD_RFFIFO_IDX 0 + +/* Tx/Rx or Common FIFO is a per channel resource. Each channel has 3 Common + * FIFOs dedicated to them. Use the first (index 0) FIFO out of the 3 for Tx. + */ +#define RCANFD_CFFIFO_IDX 0 + +/* fCAN clock select register settings */ +enum rcar_canfd_fcanclk { + RCANFD_CANFDCLK = 0, /* CANFD clock */ + RCANFD_EXTCLK, /* Externally input clock */ +}; + +struct rcar_canfd_global; + +/* Channel priv data */ +struct rcar_canfd_channel { + struct can_priv can; /* Must be the first member */ + struct net_device *ndev; + struct rcar_canfd_global *gpriv; /* Controller reference */ + void __iomem *base; /* Register base address */ + struct napi_struct napi; + u8 tx_len[RCANFD_FIFO_DEPTH]; /* For net stats */ + u32 tx_head; /* Incremented on xmit */ + u32 tx_tail; /* Incremented on xmit done */ + u32 channel; /* Channel number */ + spinlock_t tx_lock; /* To protect tx path */ +}; + +/* Global priv data */ +struct rcar_canfd_global { + struct rcar_canfd_channel *ch[RCANFD_NUM_CHANNELS]; + void __iomem *base; /* Register base address */ + struct platform_device *pdev; /* Respective platform device */ + struct clk *clkp; /* Peripheral clock */ + struct clk *can_clk; /* fCAN clock */ + enum rcar_canfd_fcanclk fcan; /* CANFD or Ext clock */ + unsigned long channels_mask; /* Enabled channels mask */ +}; + +/* CAN FD mode nominal rate constants */ +static const struct can_bittiming_const rcar_canfd_nom_bittiming_const = { + .name = RCANFD_DRV_NAME, + .tseg1_min = 2, + .tseg1_max = 128, + .tseg2_min = 2, + .tseg2_max = 32, + .sjw_max = 32, + .brp_min = 1, + .brp_max = 1024, + .brp_inc = 1, +}; + +/* CAN FD mode data rate constants */ +static const struct can_bittiming_const rcar_canfd_data_bittiming_const = { + .name = RCANFD_DRV_NAME, + .tseg1_min = 2, + .tseg1_max = 16, + .tseg2_min = 2, + .tseg2_max = 8, + .sjw_max = 8, + .brp_min = 1, + .brp_max = 256, + .brp_inc = 1, +}; + +/* Helper functions */ +static inline void rcar_canfd_update(u32 mask, u32 val, u32 __iomem *reg) +{ + u32 data = readl(reg); + + data &= ~mask; + data |= (val & mask); + writel(data, reg); +} + +static inline u32 rcar_canfd_read(void __iomem *base, u32 offset) +{ + return readl(base + (offset)); +} + +static inline void rcar_canfd_write(void __iomem *base, u32 offset, u32 val) +{ + writel(val, base + (offset)); +} + +static void rcar_canfd_set_bit(void __iomem *base, u32 reg, u32 val) +{ + rcar_canfd_update(val, val, base + (reg)); +} + +static void rcar_canfd_clear_bit(void __iomem *base, u32 reg, u32 val) +{ + rcar_canfd_update(val, 0, base + (reg)); +} + +static void rcar_canfd_update_bit(void __iomem *base, u32 reg, + u32 mask, u32 val) +{ + rcar_canfd_update(mask, val, base + (reg)); +} + +static void rcar_canfd_get_data(struct rcar_canfd_channel *priv, + struct canfd_frame *cf, u32 off) +{ + u32 i, lwords; + + lwords = DIV_ROUND_UP(cf->len, sizeof(u32)); + for (i = 0; i < lwords; i++) + *((u32 *)cf->data + i) = + rcar_canfd_read(priv->base, off + (i * sizeof(u32))); +} + +static void rcar_canfd_put_data(struct rcar_canfd_channel *priv, + struct canfd_frame *cf, u32 off) +{ + u32 i, lwords; + + lwords = DIV_ROUND_UP(cf->len, sizeof(u32)); + for (i = 0; i < lwords; i++) + rcar_canfd_write(priv->base, off + (i * sizeof(u32)), + *((u32 *)cf->data + i)); +} + +static void rcar_canfd_tx_failure_cleanup(struct net_device *ndev) +{ + u32 i; + + for (i = 0; i < RCANFD_FIFO_DEPTH; i++) + can_free_echo_skb(ndev, i); +} + +static int rcar_canfd_reset_controller(struct rcar_canfd_global *gpriv) +{ + u32 sts, ch; + int err; + + /* Check RAMINIT flag as CAN RAM initialization takes place + * after the MCU reset + */ + err = readl_poll_timeout((gpriv->base + RCANFD_GSTS), sts, + !(sts & RCANFD_GSTS_GRAMINIT), 2, 500000); + if (err) { + dev_dbg(&gpriv->pdev->dev, "global raminit failed\n"); + return err; + } + + /* Transition to Global Reset mode */ + rcar_canfd_clear_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GSLPR); + rcar_canfd_update_bit(gpriv->base, RCANFD_GCTR, + RCANFD_GCTR_GMDC_MASK, RCANFD_GCTR_GMDC_GRESET); + + /* Ensure Global reset mode */ + err = readl_poll_timeout((gpriv->base + RCANFD_GSTS), sts, + (sts & RCANFD_GSTS_GRSTSTS), 2, 500000); + if (err) { + dev_dbg(&gpriv->pdev->dev, "global reset failed\n"); + return err; + } + + /* Reset Global error flags */ + rcar_canfd_write(gpriv->base, RCANFD_GERFL, 0x0); + + /* Set the controller into FD mode */ + rcar_canfd_set_bit(gpriv->base, RCANFD_GRMCFG, RCANFD_GRMCFG_RCMC); + + /* Transition all Channels to reset mode */ + for_each_set_bit(ch, &gpriv->channels_mask, RCANFD_NUM_CHANNELS) { + rcar_canfd_clear_bit(gpriv->base, + RCANFD_CCTR(ch), RCANFD_CCTR_CSLPR); + + rcar_canfd_update_bit(gpriv->base, RCANFD_CCTR(ch), + RCANFD_CCTR_CHMDC_MASK, + RCANFD_CCTR_CHDMC_CRESET); + + /* Ensure Channel reset mode */ + err = readl_poll_timeout((gpriv->base + RCANFD_CSTS(ch)), sts, + (sts & RCANFD_CSTS_CRSTSTS), + 2, 500000); + if (err) { + dev_dbg(&gpriv->pdev->dev, + "channel %u reset failed\n", ch); + return err; + } + } + return 0; +} + +static void rcar_canfd_configure_controller(struct rcar_canfd_global *gpriv) +{ + u32 cfg, ch; + + /* Global configuration settings */ + + /* Truncate payload to configured message size RFPLS */ + cfg = RCANFD_GCFG_CMPOC; + + /* Set External Clock if selected */ + if (gpriv->fcan != RCANFD_CANFDCLK) + cfg |= RCANFD_GCFG_DCS; + + rcar_canfd_set_bit(gpriv->base, RCANFD_GCFG, cfg); + + /* Channel configuration settings */ + for_each_set_bit(ch, &gpriv->channels_mask, RCANFD_NUM_CHANNELS) { + rcar_canfd_set_bit(gpriv->base, RCANFD_CCTR(ch), + RCANFD_CCTR_ERRD); + rcar_canfd_update_bit(gpriv->base, RCANFD_CCTR(ch), + RCANFD_CCTR_BOM_MASK, + RCANFD_CCTR_BOM_BENTRY); + } +} + +static void rcar_canfd_configure_afl_rules(struct rcar_canfd_global *gpriv, + u32 ch) +{ + u32 cfg; + int start, page, num_rules = RCANFD_CHANNEL_NUMRULES; + u32 ridx = ch + RCANFD_RFFIFO_IDX; + + if (ch == 0) { + start = 0; /* Channel 0 always starts from 0th rule */ + } else { + /* Get number of Channel 0 rules and adjust */ + cfg = rcar_canfd_read(gpriv->base, RCANFD_GAFLCFG0); + start = RCANFD_GAFLCFG_GETRNC(0, cfg); + } + + /* Enable write access to entry */ + page = RCANFD_GAFL_PAGENUM(start); + rcar_canfd_set_bit(gpriv->base, RCANFD_GAFLECTR, + (RCANFD_GAFLECTR_AFLPN(page) | + RCANFD_GAFLECTR_AFLDAE)); + + /* Write number of rules for channel */ + rcar_canfd_set_bit(gpriv->base, RCANFD_GAFLCFG0, + RCANFD_GAFLCFG_SETRNC(ch, num_rules)); + + /* Accept all IDs */ + rcar_canfd_write(gpriv->base, + RCANFD_GAFLID(RCANFD_F_GAFL_OFFSET, start), 0); + /* IDE or RTR is not considered for matching */ + rcar_canfd_write(gpriv->base, + RCANFD_GAFLM(RCANFD_F_GAFL_OFFSET, start), 0); + /* Any data length accepted */ + rcar_canfd_write(gpriv->base, + RCANFD_GAFLP0(RCANFD_F_GAFL_OFFSET, start), 0); + /* Place the msg in corresponding Rx FIFO entry */ + rcar_canfd_write(gpriv->base, + RCANFD_GAFLP1(RCANFD_F_GAFL_OFFSET, start), + RCANFD_GAFLP1_GAFLFDP(ridx)); + + /* Disable write access to page */ + rcar_canfd_clear_bit(gpriv->base, + RCANFD_GAFLECTR, RCANFD_GAFLECTR_AFLDAE); +} + +static void rcar_canfd_configure_rx(struct rcar_canfd_global *gpriv, u32 ch) +{ + /* Rx FIFO is used for reception */ + u32 cfg; + u16 rfdc, rfpls; + + /* Select Rx FIFO based on channel */ + u32 ridx = ch + RCANFD_RFFIFO_IDX; + + rfdc = 2; /* b010 - 8 messages Rx FIFO depth */ + rfpls = 7; /* b111 - Max 64 bytes payload */ + + cfg = (RCANFD_RFCC_RFIM | RCANFD_RFCC_RFDC(rfdc) | + RCANFD_RFCC_RFPLS(rfpls) | RCANFD_RFCC_RFIE); + rcar_canfd_write(gpriv->base, RCANFD_RFCC(ridx), cfg); +} + +static void rcar_canfd_configure_tx(struct rcar_canfd_global *gpriv, u32 ch) +{ + /* Tx/Rx(Common) FIFO configured in Tx mode is + * used for transmission + * + * Each channel has 3 Common FIFO dedicated to them. + * Use the 1st (index 0) out of 3 + */ + u32 cfg; + u16 cftml, cfm, cfdc, cfpls; + + cftml = 0; /* 0th buffer */ + cfm = 1; /* b01 - Transmit mode */ + cfdc = 2; /* b010 - 8 messages Tx FIFO depth */ + cfpls = 7; /* b111 - Max 64 bytes payload */ + + cfg = (RCANFD_CFCC_CFTML(cftml) | RCANFD_CFCC_CFM(cfm) | + RCANFD_CFCC_CFIM | RCANFD_CFCC_CFDC(cfdc) | + RCANFD_CFCC_CFPLS(cfpls) | RCANFD_CFCC_CFTXIE); + rcar_canfd_write(gpriv->base, RCANFD_CFCC(ch, RCANFD_CFFIFO_IDX), cfg); + + /* Clear FD mode specific control/status register */ + rcar_canfd_write(gpriv->base, + RCANFD_F_CFFDCSTS(ch, RCANFD_CFFIFO_IDX), 0); +} + +static void rcar_canfd_enable_global_interrupts(struct rcar_canfd_global *gpriv) +{ + u32 ctr; + + /* Clear any stray error interrupt flags */ + rcar_canfd_write(gpriv->base, RCANFD_GERFL, 0); + + /* Global interrupts setup */ + ctr = RCANFD_GCTR_MEIE; + ctr |= RCANFD_GCTR_CFMPOFIE; + + rcar_canfd_set_bit(gpriv->base, RCANFD_GCTR, ctr); +} + +static void rcar_canfd_disable_global_interrupts(struct rcar_canfd_global + *gpriv) +{ + /* Disable all interrupts */ + rcar_canfd_write(gpriv->base, RCANFD_GCTR, 0); + + /* Clear any stray error interrupt flags */ + rcar_canfd_write(gpriv->base, RCANFD_GERFL, 0); +} + +static void rcar_canfd_enable_channel_interrupts(struct rcar_canfd_channel + *priv) +{ + u32 ctr, ch = priv->channel; + + /* Clear any stray error flags */ + rcar_canfd_write(priv->base, RCANFD_CERFL(ch), 0); + + /* Channel interrupts setup */ + ctr = (RCANFD_CCTR_TAIE | + RCANFD_CCTR_ALIE | RCANFD_CCTR_BLIE | + RCANFD_CCTR_OLIE | RCANFD_CCTR_BORIE | + RCANFD_CCTR_BOEIE | RCANFD_CCTR_EPIE | + RCANFD_CCTR_EWIE | RCANFD_CCTR_BEIE); + rcar_canfd_set_bit(priv->base, RCANFD_CCTR(ch), ctr); +} + +static void rcar_canfd_disable_channel_interrupts(struct rcar_canfd_channel + *priv) +{ + u32 ctr, ch = priv->channel; + + ctr = (RCANFD_CCTR_TAIE | + RCANFD_CCTR_ALIE | RCANFD_CCTR_BLIE | + RCANFD_CCTR_OLIE | RCANFD_CCTR_BORIE | + RCANFD_CCTR_BOEIE | RCANFD_CCTR_EPIE | + RCANFD_CCTR_EWIE | RCANFD_CCTR_BEIE); + rcar_canfd_clear_bit(priv->base, RCANFD_CCTR(ch), ctr); + + /* Clear any stray error flags */ + rcar_canfd_write(priv->base, RCANFD_CERFL(ch), 0); +} + +static void rcar_canfd_global_error(struct net_device *ndev) +{ + struct rcar_canfd_channel *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + u32 ch = priv->channel; + u32 gerfl, sts; + u32 ridx = ch + RCANFD_RFFIFO_IDX; + + gerfl = rcar_canfd_read(priv->base, RCANFD_GERFL); + if ((gerfl & RCANFD_GERFL_EEF0) && (ch == 0)) { + netdev_dbg(ndev, "Ch0: ECC Error flag\n"); + stats->tx_dropped++; + } + if ((gerfl & RCANFD_GERFL_EEF1) && (ch == 1)) { + netdev_dbg(ndev, "Ch1: ECC Error flag\n"); + stats->tx_dropped++; + } + if (gerfl & RCANFD_GERFL_MES) { + sts = rcar_canfd_read(priv->base, + RCANFD_CFSTS(ch, RCANFD_CFFIFO_IDX)); + if (sts & RCANFD_CFSTS_CFMLT) { + netdev_dbg(ndev, "Tx Message Lost flag\n"); + stats->tx_dropped++; + rcar_canfd_write(priv->base, + RCANFD_CFSTS(ch, RCANFD_CFFIFO_IDX), + sts & ~RCANFD_CFSTS_CFMLT); + } + + sts = rcar_canfd_read(priv->base, RCANFD_RFSTS(ridx)); + if (sts & RCANFD_RFSTS_RFMLT) { + netdev_dbg(ndev, "Rx Message Lost flag\n"); + stats->rx_dropped++; + rcar_canfd_write(priv->base, RCANFD_RFSTS(ridx), + sts & ~RCANFD_RFSTS_RFMLT); + } + } + if (gerfl & RCANFD_GERFL_CMPOF) { + /* Message Lost flag will be set for respective channel + * when this condition happens with counters and flags + * already updated. + */ + netdev_dbg(ndev, "global payload overflow interrupt\n"); + } + + /* Clear all global error interrupts. Only affected channels bits + * get cleared + */ + rcar_canfd_write(priv->base, RCANFD_GERFL, 0); +} + +static void rcar_canfd_error(struct net_device *ndev) +{ + struct rcar_canfd_channel *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + struct can_frame *cf; + struct sk_buff *skb; + u32 cerfl, csts; + u32 txerr = 0, rxerr = 0; + u32 ch = priv->channel; + + /* Propagate the error condition to the CAN stack */ + skb = alloc_can_err_skb(ndev, &cf); + if (!skb) { + stats->rx_dropped++; + return; + } + + /* Channel error interrupt */ + cerfl = rcar_canfd_read(priv->base, RCANFD_CERFL(ch)); + csts = rcar_canfd_read(priv->base, RCANFD_CSTS(ch)); + txerr = RCANFD_CSTS_TECCNT(csts); + rxerr = RCANFD_CSTS_RECCNT(csts); + + netdev_dbg(ndev, "ch erfl %x sts %x txerr %u rxerr %u\n", + cerfl, csts, txerr, rxerr); + + if (cerfl & RCANFD_CERFL_BEF) { + netdev_dbg(ndev, "Bus error\n"); + cf->can_id |= CAN_ERR_BUSERROR | CAN_ERR_PROT; + cf->data[2] = CAN_ERR_PROT_UNSPEC; + priv->can.can_stats.bus_error++; + } + if (cerfl & RCANFD_CERFL_ADERR) { + netdev_dbg(ndev, "ACK Delimiter Error\n"); + stats->tx_errors++; + cf->data[3] |= CAN_ERR_PROT_LOC_ACK_DEL; + } + if (cerfl & RCANFD_CERFL_B0ERR) { + netdev_dbg(ndev, "Bit Error (dominant)\n"); + stats->tx_errors++; + cf->data[2] |= CAN_ERR_PROT_BIT0; + } + if (cerfl & RCANFD_CERFL_B1ERR) { + netdev_dbg(ndev, "Bit Error (recessive)\n"); + stats->tx_errors++; + cf->data[2] |= CAN_ERR_PROT_BIT1; + } + if (cerfl & RCANFD_CERFL_CERR) { + netdev_dbg(ndev, "CRC Error\n"); + stats->rx_errors++; + cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ; + } + if (cerfl & RCANFD_CERFL_AERR) { + netdev_dbg(ndev, "ACK Error\n"); + stats->tx_errors++; + cf->can_id |= CAN_ERR_ACK; + cf->data[3] |= CAN_ERR_PROT_LOC_ACK; + } + if (cerfl & RCANFD_CERFL_FERR) { + netdev_dbg(ndev, "Form Error\n"); + stats->rx_errors++; + cf->data[2] |= CAN_ERR_PROT_FORM; + } + if (cerfl & RCANFD_CERFL_SERR) { + netdev_dbg(ndev, "Stuff Error\n"); + stats->rx_errors++; + cf->data[2] |= CAN_ERR_PROT_STUFF; + } + if (cerfl & RCANFD_CERFL_ALF) { + netdev_dbg(ndev, "Arbitration lost Error\n"); + priv->can.can_stats.arbitration_lost++; + cf->can_id |= CAN_ERR_LOSTARB; + cf->data[0] |= CAN_ERR_LOSTARB_UNSPEC; + } + if (cerfl & RCANFD_CERFL_BLF) { + netdev_dbg(ndev, "Bus Lock Error\n"); + stats->rx_errors++; + cf->can_id |= CAN_ERR_BUSERROR; + } + if (cerfl & RCANFD_CERFL_EWF) { + netdev_dbg(ndev, "Error warning interrupt\n"); + priv->can.state = CAN_STATE_ERROR_WARNING; + priv->can.can_stats.error_warning++; + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] = txerr > rxerr ? CAN_ERR_CRTL_TX_WARNING : + CAN_ERR_CRTL_RX_WARNING; + cf->data[6] = txerr; + cf->data[7] = rxerr; + } + if (cerfl & RCANFD_CERFL_EPF) { + netdev_dbg(ndev, "Error passive interrupt\n"); + priv->can.state = CAN_STATE_ERROR_PASSIVE; + priv->can.can_stats.error_passive++; + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] = txerr > rxerr ? CAN_ERR_CRTL_TX_PASSIVE : + CAN_ERR_CRTL_RX_PASSIVE; + cf->data[6] = txerr; + cf->data[7] = rxerr; + } + if (cerfl & RCANFD_CERFL_BOEF) { + netdev_dbg(ndev, "Bus-off entry interrupt\n"); + rcar_canfd_tx_failure_cleanup(ndev); + priv->can.state = CAN_STATE_BUS_OFF; + priv->can.can_stats.bus_off++; + can_bus_off(ndev); + cf->can_id |= CAN_ERR_BUSOFF; + } + if (cerfl & RCANFD_CERFL_OVLF) { + netdev_dbg(ndev, + "Overload Frame Transmission error interrupt\n"); + stats->tx_errors++; + cf->can_id |= CAN_ERR_PROT; + cf->data[2] |= CAN_ERR_PROT_OVERLOAD; + } + + /* Clear all channel error interrupts */ + rcar_canfd_write(priv->base, RCANFD_CERFL(ch), 0); + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; + netif_rx(skb); +} + +static void rcar_canfd_tx_done(struct net_device *ndev) +{ + struct rcar_canfd_channel *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + u32 sts; + unsigned long flags; + u32 ch = priv->channel; + + do { + u8 unsent, sent; + + sent = priv->tx_tail % RCANFD_FIFO_DEPTH; + stats->tx_packets++; + stats->tx_bytes += priv->tx_len[sent]; + priv->tx_len[sent] = 0; + can_get_echo_skb(ndev, sent); + + spin_lock_irqsave(&priv->tx_lock, flags); + priv->tx_tail++; + sts = rcar_canfd_read(priv->base, + RCANFD_CFSTS(ch, RCANFD_CFFIFO_IDX)); + unsent = RCANFD_CFSTS_CFMC(sts); + + /* Wake producer only when there is room */ + if (unsent != RCANFD_FIFO_DEPTH) + netif_wake_queue(ndev); + + if (priv->tx_head - priv->tx_tail <= unsent) { + spin_unlock_irqrestore(&priv->tx_lock, flags); + break; + } + spin_unlock_irqrestore(&priv->tx_lock, flags); + + } while (1); + + /* Clear interrupt */ + rcar_canfd_write(priv->base, RCANFD_CFSTS(ch, RCANFD_CFFIFO_IDX), + sts & ~RCANFD_CFSTS_CFTXIF); + can_led_event(ndev, CAN_LED_EVENT_TX); +} + +static irqreturn_t rcar_canfd_global_interrupt(int irq, void *dev_id) +{ + struct rcar_canfd_global *gpriv = dev_id; + struct net_device *ndev; + struct rcar_canfd_channel *priv; + u32 sts, gerfl; + u32 ch, ridx; + + /* Global error interrupts still indicate a condition specific + * to a channel. RxFIFO interrupt is a global interrupt. + */ + for_each_set_bit(ch, &gpriv->channels_mask, RCANFD_NUM_CHANNELS) { + priv = gpriv->ch[ch]; + ndev = priv->ndev; + ridx = ch + RCANFD_RFFIFO_IDX; + + /* Global error interrupts */ + gerfl = rcar_canfd_read(priv->base, RCANFD_GERFL); + if (RCANFD_GERFL_ERR(gerfl)) + rcar_canfd_global_error(ndev); + + /* Handle Rx interrupts */ + sts = rcar_canfd_read(priv->base, RCANFD_RFSTS(ridx)); + if (sts & RCANFD_RFSTS_RFIF) { + if (napi_schedule_prep(&priv->napi)) { + /* Disable Rx FIFO interrupts */ + rcar_canfd_clear_bit(priv->base, + RCANFD_RFCC(ridx), + RCANFD_RFCC_RFIE); + __napi_schedule(&priv->napi); + } + } + } + return IRQ_HANDLED; +} + +static irqreturn_t rcar_canfd_channel_interrupt(int irq, void *dev_id) +{ + struct rcar_canfd_global *gpriv = dev_id; + struct net_device *ndev; + struct rcar_canfd_channel *priv; + u32 sts, cerfl, ch; + + /* Common FIFO is a per channel resource */ + for_each_set_bit(ch, &gpriv->channels_mask, RCANFD_NUM_CHANNELS) { + priv = gpriv->ch[ch]; + ndev = priv->ndev; + + /* Channel error interrupts */ + cerfl = rcar_canfd_read(priv->base, RCANFD_CERFL(ch)); + if (RCANFD_CERFL_ERR(cerfl)) + rcar_canfd_error(ndev); + + /* Handle Tx interrupts */ + sts = rcar_canfd_read(priv->base, + RCANFD_CFSTS(ch, RCANFD_CFFIFO_IDX)); + if (sts & RCANFD_CFSTS_CFTXIF) + rcar_canfd_tx_done(ndev); + } + return IRQ_HANDLED; +} + +static void rcar_canfd_set_bittiming(struct net_device *dev) +{ + struct rcar_canfd_channel *priv = netdev_priv(dev); + const struct can_bittiming *bt = &priv->can.bittiming; + const struct can_bittiming *dbt = &priv->can.data_bittiming; + u16 brp, sjw, tseg1, tseg2; + u32 cfg; + u32 ch = priv->channel; + + /* Nominal bit timing settings */ + brp = bt->brp - 1; + sjw = bt->sjw - 1; + tseg1 = bt->prop_seg + bt->phase_seg1 - 1; + tseg2 = bt->phase_seg2 - 1; + + cfg = (RCANFD_NCFG_NTSEG1(tseg1) | RCANFD_NCFG_NBRP(brp) | + RCANFD_NCFG_NSJW(sjw) | RCANFD_NCFG_NTSEG2(tseg2)); + + rcar_canfd_write(priv->base, RCANFD_CCFG(ch), cfg); + netdev_dbg(priv->ndev, "nrate: brp %u, sjw %u, tseg1 %u, tseg2 %u\n", + brp, sjw, tseg1, tseg2); + + /* Data bit timing settings */ + brp = dbt->brp - 1; + sjw = dbt->sjw - 1; + tseg1 = dbt->prop_seg + dbt->phase_seg1 - 1; + tseg2 = dbt->phase_seg2 - 1; + + cfg = (RCANFD_DCFG_DTSEG1(tseg1) | RCANFD_DCFG_DBRP(brp) | + RCANFD_DCFG_DSJW(sjw) | RCANFD_DCFG_DTSEG2(tseg2)); + + rcar_canfd_write(priv->base, RCANFD_F_DCFG(ch), cfg); + netdev_dbg(priv->ndev, "drate: brp %u, sjw %u, tseg1 %u, tseg2 %u\n", + brp, sjw, tseg1, tseg2); +} + +static int rcar_canfd_start(struct net_device *ndev) +{ + struct rcar_canfd_channel *priv = netdev_priv(ndev); + int err = -EOPNOTSUPP; + u32 sts, ch = priv->channel; + u32 ridx = ch + RCANFD_RFFIFO_IDX; + + rcar_canfd_set_bittiming(ndev); + + rcar_canfd_enable_channel_interrupts(priv); + + /* Set channel to Operational mode */ + rcar_canfd_update_bit(priv->base, RCANFD_CCTR(ch), + RCANFD_CCTR_CHMDC_MASK, RCANFD_CCTR_CHDMC_COPM); + + /* Verify channel mode change */ + err = readl_poll_timeout((priv->base + RCANFD_CSTS(ch)), sts, + (sts & RCANFD_CSTS_COMSTS), 2, 500000); + if (err) { + netdev_err(ndev, "channel %u communication state failed\n", ch); + goto fail_mode_change; + } + + /* Enable Common & Rx FIFO */ + rcar_canfd_set_bit(priv->base, RCANFD_CFCC(ch, RCANFD_CFFIFO_IDX), + RCANFD_CFCC_CFE); + rcar_canfd_set_bit(priv->base, RCANFD_RFCC(ridx), RCANFD_RFCC_RFE); + + priv->can.state = CAN_STATE_ERROR_ACTIVE; + return 0; + +fail_mode_change: + rcar_canfd_disable_channel_interrupts(priv); + return err; +} + +static int rcar_canfd_open(struct net_device *ndev) +{ + struct rcar_canfd_channel *priv = netdev_priv(ndev); + struct rcar_canfd_global *gpriv = priv->gpriv; + int err; + + /* Peripheral clock is already enabled in probe */ + err = clk_prepare_enable(gpriv->can_clk); + if (err) { + netdev_err(ndev, "failed to enable CAN clock, error %d\n", err); + goto out_clock; + } + + err = open_candev(ndev); + if (err) { + netdev_err(ndev, "open_candev() failed, error %d\n", err); + goto out_can_clock; + } + + napi_enable(&priv->napi); + err = rcar_canfd_start(ndev); + if (err) + goto out_close; + netif_start_queue(ndev); + can_led_event(ndev, CAN_LED_EVENT_OPEN); + return 0; +out_close: + napi_disable(&priv->napi); + close_candev(ndev); +out_can_clock: + clk_disable_unprepare(gpriv->can_clk); +out_clock: + return err; +} + +static void rcar_canfd_stop(struct net_device *ndev) +{ + struct rcar_canfd_channel *priv = netdev_priv(ndev); + int err; + u32 sts, ch = priv->channel; + u32 ridx = ch + RCANFD_RFFIFO_IDX; + + /* Transition to channel reset mode */ + rcar_canfd_update_bit(priv->base, RCANFD_CCTR(ch), + RCANFD_CCTR_CHMDC_MASK, RCANFD_CCTR_CHDMC_CRESET); + + /* Check Channel reset mode */ + err = readl_poll_timeout((priv->base + RCANFD_CSTS(ch)), sts, + (sts & RCANFD_CSTS_CRSTSTS), 2, 500000); + if (err) + netdev_err(ndev, "channel %u reset failed\n", ch); + + rcar_canfd_disable_channel_interrupts(priv); + + /* Disable Common & Rx FIFO */ + rcar_canfd_clear_bit(priv->base, RCANFD_CFCC(ch, RCANFD_CFFIFO_IDX), + RCANFD_CFCC_CFE); + rcar_canfd_clear_bit(priv->base, RCANFD_RFCC(ridx), RCANFD_RFCC_RFE); + + /* Set the state as STOPPED */ + priv->can.state = CAN_STATE_STOPPED; +} + +static int rcar_canfd_close(struct net_device *ndev) +{ + struct rcar_canfd_channel *priv = netdev_priv(ndev); + struct rcar_canfd_global *gpriv = priv->gpriv; + + netif_stop_queue(ndev); + rcar_canfd_stop(ndev); + napi_disable(&priv->napi); + clk_disable_unprepare(gpriv->can_clk); + close_candev(ndev); + can_led_event(ndev, CAN_LED_EVENT_STOP); + return 0; +} + +static netdev_tx_t rcar_canfd_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + struct rcar_canfd_channel *priv = netdev_priv(ndev); + struct canfd_frame *cf = (struct canfd_frame *)skb->data; + u32 sts = 0, id, dlc; + unsigned long flags; + u32 ch = priv->channel; + + if (can_dropped_invalid_skb(ndev, skb)) + return NETDEV_TX_OK; + + if (cf->can_id & CAN_EFF_FLAG) { + id = cf->can_id & CAN_EFF_MASK; + id |= RCANFD_CFID_CFIDE; + } else { + id = cf->can_id & CAN_SFF_MASK; + } + + if (cf->can_id & CAN_RTR_FLAG) + id |= RCANFD_CFID_CFRTR; + + rcar_canfd_write(priv->base, + RCANFD_F_CFID(ch, RCANFD_CFFIFO_IDX), id); + dlc = RCANFD_CFPTR_CFDLC(can_len2dlc(cf->len)); + rcar_canfd_write(priv->base, + RCANFD_F_CFPTR(ch, RCANFD_CFFIFO_IDX), dlc); + + if (can_is_canfd_skb(skb)) { + /* CAN FD frame format */ + sts |= RCANFD_CFFDCSTS_CFFDF; + if (cf->flags & CANFD_BRS) + sts |= RCANFD_CFFDCSTS_CFBRS; + + if (priv->can.state == CAN_STATE_ERROR_PASSIVE) + sts |= RCANFD_CFFDCSTS_CFESI; + } + + rcar_canfd_write(priv->base, RCANFD_F_CFFDCSTS(ch, RCANFD_CFFIFO_IDX), + sts); + + rcar_canfd_put_data(priv, cf, + RCANFD_F_CFDF(ch, RCANFD_CFFIFO_IDX, 0)); + + priv->tx_len[priv->tx_head % RCANFD_FIFO_DEPTH] = cf->len; + can_put_echo_skb(skb, ndev, priv->tx_head % RCANFD_FIFO_DEPTH); + + spin_lock_irqsave(&priv->tx_lock, flags); + priv->tx_head++; + + /* Stop the queue if we've filled all FIFO entries */ + if (priv->tx_head - priv->tx_tail >= RCANFD_FIFO_DEPTH) + netif_stop_queue(ndev); + + /* Start Tx: Write 0xff to CFPC to increment the CPU-side + * pointer for the Common FIFO + */ + rcar_canfd_write(priv->base, + RCANFD_CFPCTR(ch, RCANFD_CFFIFO_IDX), 0xff); + + spin_unlock_irqrestore(&priv->tx_lock, flags); + return NETDEV_TX_OK; +} + +static void rcar_canfd_rx_pkt(struct rcar_canfd_channel *priv) +{ + struct net_device_stats *stats = &priv->ndev->stats; + struct canfd_frame *cf; + struct sk_buff *skb; + u32 sts = 0, id, ptr; + u32 ch = priv->channel; + u32 ridx = ch + RCANFD_RFFIFO_IDX; + + id = rcar_canfd_read(priv->base, RCANFD_F_RFID(ridx)); + ptr = rcar_canfd_read(priv->base, RCANFD_F_RFPTR(ridx)); + + sts = rcar_canfd_read(priv->base, RCANFD_F_RFFDSTS(ridx)); + if (sts & RCANFD_RFFDSTS_RFFDF) + skb = alloc_canfd_skb(priv->ndev, &cf); + else + skb = alloc_can_skb(priv->ndev, + (struct can_frame **)&cf); + + if (!skb) { + stats->rx_dropped++; + return; + } + + if (sts & RCANFD_RFFDSTS_RFFDF) + cf->len = can_dlc2len(RCANFD_RFPTR_RFDLC(ptr)); + else + cf->len = get_can_dlc(RCANFD_RFPTR_RFDLC(ptr)); + + if (sts & RCANFD_RFFDSTS_RFESI) { + cf->flags |= CANFD_ESI; + netdev_dbg(priv->ndev, "ESI Error\n"); + } + + if (id & RCANFD_RFID_RFIDE) + cf->can_id = (id & CAN_EFF_MASK) | CAN_EFF_FLAG; + else + cf->can_id = id & CAN_SFF_MASK; + + if (!(sts & RCANFD_RFFDSTS_RFFDF) && (id & RCANFD_RFID_RFRTR)) { + cf->can_id |= CAN_RTR_FLAG; + } else { + if (sts & RCANFD_RFFDSTS_RFBRS) + cf->flags |= CANFD_BRS; + + rcar_canfd_get_data(priv, cf, RCANFD_F_RFDF(ridx, 0)); + } + + /* Write 0xff to RFPC to increment the CPU-side + * pointer of the Rx FIFO + */ + rcar_canfd_write(priv->base, RCANFD_RFPCTR(ridx), 0xff); + + can_led_event(priv->ndev, CAN_LED_EVENT_RX); + + stats->rx_bytes += cf->len; + stats->rx_packets++; + netif_receive_skb(skb); +} + +static int rcar_canfd_rx_poll(struct napi_struct *napi, int quota) +{ + struct rcar_canfd_channel *priv = + container_of(napi, struct rcar_canfd_channel, napi); + int num_pkts; + u32 sts; + u32 ch = priv->channel; + u32 ridx = ch + RCANFD_RFFIFO_IDX; + + for (num_pkts = 0; num_pkts < quota; num_pkts++) { + sts = rcar_canfd_read(priv->base, RCANFD_RFSTS(ridx)); + /* Check FIFO empty condition */ + if (sts & RCANFD_RFSTS_RFEMP) + break; + + rcar_canfd_rx_pkt(priv); + + /* Clear interrupt bit */ + if (sts & RCANFD_RFSTS_RFIF) + rcar_canfd_write(priv->base, RCANFD_RFSTS(ridx), + sts & ~RCANFD_RFSTS_RFIF); + } + + /* All packets processed */ + if (num_pkts < quota) { + napi_complete(napi); + /* Enable Rx FIFO interrupts */ + rcar_canfd_set_bit(priv->base, RCANFD_RFCC(ridx), + RCANFD_RFCC_RFIE); + } + return num_pkts; +} + +static int rcar_canfd_do_set_mode(struct net_device *ndev, enum can_mode mode) +{ + int err; + + switch (mode) { + case CAN_MODE_START: + err = rcar_canfd_start(ndev); + if (err) + return err; + netif_wake_queue(ndev); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int rcar_canfd_get_berr_counter(const struct net_device *dev, + struct can_berr_counter *bec) +{ + struct rcar_canfd_channel *priv = netdev_priv(dev); + u32 val, ch = priv->channel; + + /* Peripheral clock is already enabled in probe */ + val = rcar_canfd_read(priv->base, RCANFD_CSTS(ch)); + bec->txerr = RCANFD_CSTS_TECCNT(val); + bec->rxerr = RCANFD_CSTS_RECCNT(val); + return 0; +} + +static const struct net_device_ops rcar_canfd_netdev_ops = { + .ndo_open = rcar_canfd_open, + .ndo_stop = rcar_canfd_close, + .ndo_start_xmit = rcar_canfd_start_xmit, + .ndo_change_mtu = can_change_mtu, +}; + +static int rcar_canfd_channel_probe(struct rcar_canfd_global *gpriv, u32 ch, + u32 fcan_freq) +{ + struct platform_device *pdev = gpriv->pdev; + struct rcar_canfd_channel *priv; + struct net_device *ndev; + int err = -ENODEV; + + ndev = alloc_candev(sizeof(*priv), RCANFD_FIFO_DEPTH); + if (!ndev) { + dev_err(&pdev->dev, "alloc_candev() failed\n"); + err = -ENOMEM; + goto fail; + } + priv = netdev_priv(ndev); + + ndev->netdev_ops = &rcar_canfd_netdev_ops; + ndev->flags |= IFF_ECHO; + priv->ndev = ndev; + priv->base = gpriv->base; + priv->channel = ch; + priv->can.clock.freq = fcan_freq; + dev_info(&pdev->dev, "can_clk rate is %u\n", priv->can.clock.freq); + + priv->can.bittiming_const = &rcar_canfd_nom_bittiming_const; + priv->can.data_bittiming_const = + &rcar_canfd_data_bittiming_const; + + /* Controller starts in CAN FD only mode */ + can_set_static_ctrlmode(ndev, CAN_CTRLMODE_FD); + priv->can.ctrlmode_supported = CAN_CTRLMODE_BERR_REPORTING; + + priv->can.do_set_mode = rcar_canfd_do_set_mode; + priv->can.do_get_berr_counter = rcar_canfd_get_berr_counter; + priv->gpriv = gpriv; + SET_NETDEV_DEV(ndev, &pdev->dev); + + netif_napi_add(ndev, &priv->napi, rcar_canfd_rx_poll, + RCANFD_NAPI_WEIGHT); + err = register_candev(ndev); + if (err) { + dev_err(&pdev->dev, + "register_candev() failed, error %d\n", err); + goto fail_candev; + } + spin_lock_init(&priv->tx_lock); + devm_can_led_init(ndev); + gpriv->ch[priv->channel] = priv; + dev_info(&pdev->dev, "device registered (channel %u)\n", priv->channel); + return 0; + +fail_candev: + netif_napi_del(&priv->napi); + free_candev(ndev); +fail: + return err; +} + +static void rcar_canfd_channel_remove(struct rcar_canfd_global *gpriv, u32 ch) +{ + struct rcar_canfd_channel *priv = gpriv->ch[ch]; + + if (priv) { + unregister_candev(priv->ndev); + netif_napi_del(&priv->napi); + free_candev(priv->ndev); + } +} + +static int rcar_canfd_probe(struct platform_device *pdev) +{ + struct resource *mem; + void __iomem *addr; + u32 sts, ch, fcan_freq; + struct rcar_canfd_global *gpriv; + struct device_node *of_child; + unsigned long channels_mask = 0; + int err, ch_irq, g_irq; + + of_child = of_get_child_by_name(pdev->dev.of_node, "channel0"); + if (of_child && of_device_is_available(of_child)) + channels_mask |= BIT(0); /* Channel 0 */ + + of_child = of_get_child_by_name(pdev->dev.of_node, "channel1"); + if (of_child && of_device_is_available(of_child)) + channels_mask |= BIT(1); /* Channel 1 */ + + ch_irq = platform_get_irq(pdev, 0); + if (ch_irq < 0) { + dev_err(&pdev->dev, "no Channel IRQ resource\n"); + err = ch_irq; + goto fail_dev; + } + + g_irq = platform_get_irq(pdev, 1); + if (g_irq < 0) { + dev_err(&pdev->dev, "no Global IRQ resource\n"); + err = g_irq; + goto fail_dev; + } + + /* Global controller context */ + gpriv = devm_kzalloc(&pdev->dev, sizeof(*gpriv), GFP_KERNEL); + if (!gpriv) { + err = -ENOMEM; + goto fail_dev; + } + gpriv->pdev = pdev; + gpriv->channels_mask = channels_mask; + + /* Peripheral clock */ + gpriv->clkp = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(gpriv->clkp)) { + err = PTR_ERR(gpriv->clkp); + dev_err(&pdev->dev, "cannot get peripheral clock, error %d\n", + err); + goto fail_dev; + } + + /* fCAN clock: Pick External clock. If not available fallback to + * CANFD clock + */ + gpriv->can_clk = devm_clk_get(&pdev->dev, "can_clk"); + if (IS_ERR(gpriv->can_clk) || (clk_get_rate(gpriv->can_clk) == 0)) { + gpriv->can_clk = devm_clk_get(&pdev->dev, "canfd"); + if (IS_ERR(gpriv->can_clk)) { + err = PTR_ERR(gpriv->can_clk); + dev_err(&pdev->dev, + "cannot get canfd clock, error %d\n", err); + goto fail_dev; + } + gpriv->fcan = RCANFD_CANFDCLK; + + } else { + gpriv->fcan = RCANFD_EXTCLK; + } + fcan_freq = clk_get_rate(gpriv->can_clk); + + if (gpriv->fcan == RCANFD_CANFDCLK) + /* CANFD clock is further divided by (1/2) within the IP */ + fcan_freq /= 2; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + addr = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(addr)) { + err = PTR_ERR(addr); + goto fail_dev; + } + gpriv->base = addr; + + /* Request IRQ that's common for both channels */ + err = devm_request_irq(&pdev->dev, ch_irq, + rcar_canfd_channel_interrupt, 0, + "canfd.chn", gpriv); + if (err) { + dev_err(&pdev->dev, "devm_request_irq(%d) failed, error %d\n", + ch_irq, err); + goto fail_dev; + } + err = devm_request_irq(&pdev->dev, g_irq, + rcar_canfd_global_interrupt, 0, + "canfd.gbl", gpriv); + if (err) { + dev_err(&pdev->dev, "devm_request_irq(%d) failed, error %d\n", + g_irq, err); + goto fail_dev; + } + + /* Enable peripheral clock for register access */ + err = clk_prepare_enable(gpriv->clkp); + if (err) { + dev_err(&pdev->dev, + "failed to enable peripheral clock, error %d\n", err); + goto fail_dev; + } + + err = rcar_canfd_reset_controller(gpriv); + if (err) { + dev_err(&pdev->dev, "reset controller failed\n"); + goto fail_clk; + } + + /* Controller in Global reset & Channel reset mode */ + rcar_canfd_configure_controller(gpriv); + + /* Configure per channel attributes */ + for_each_set_bit(ch, &gpriv->channels_mask, RCANFD_NUM_CHANNELS) { + /* Configure Channel's Rx fifo */ + rcar_canfd_configure_rx(gpriv, ch); + + /* Configure Channel's Tx (Common) fifo */ + rcar_canfd_configure_tx(gpriv, ch); + + /* Configure receive rules */ + rcar_canfd_configure_afl_rules(gpriv, ch); + } + + /* Configure common interrupts */ + rcar_canfd_enable_global_interrupts(gpriv); + + /* Start Global operation mode */ + rcar_canfd_update_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GMDC_MASK, + RCANFD_GCTR_GMDC_GOPM); + + /* Verify mode change */ + err = readl_poll_timeout((gpriv->base + RCANFD_GSTS), sts, + !(sts & RCANFD_GSTS_GNOPM), 2, 500000); + if (err) { + dev_err(&pdev->dev, "global operational mode failed\n"); + goto fail_mode; + } + + for_each_set_bit(ch, &gpriv->channels_mask, RCANFD_NUM_CHANNELS) { + err = rcar_canfd_channel_probe(gpriv, ch, fcan_freq); + if (err) + goto fail_channel; + } + + platform_set_drvdata(pdev, gpriv); + dev_info(&pdev->dev, "global operational state (clk %d)\n", + gpriv->fcan); + return 0; + +fail_channel: + for_each_set_bit(ch, &gpriv->channels_mask, RCANFD_NUM_CHANNELS) + rcar_canfd_channel_remove(gpriv, ch); +fail_mode: + rcar_canfd_disable_global_interrupts(gpriv); +fail_clk: + clk_disable_unprepare(gpriv->clkp); +fail_dev: + return err; +} + +static int rcar_canfd_remove(struct platform_device *pdev) +{ + struct rcar_canfd_global *gpriv = platform_get_drvdata(pdev); + u32 ch; + + rcar_canfd_reset_controller(gpriv); + rcar_canfd_disable_global_interrupts(gpriv); + + for_each_set_bit(ch, &gpriv->channels_mask, RCANFD_NUM_CHANNELS) { + rcar_canfd_disable_channel_interrupts(gpriv->ch[ch]); + rcar_canfd_channel_remove(gpriv, ch); + } + + /* Enter global sleep mode */ + rcar_canfd_set_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GSLPR); + clk_disable_unprepare(gpriv->clkp); + return 0; +} + +static int __maybe_unused rcar_canfd_suspend(struct device *dev) +{ + return 0; +} + +static int __maybe_unused rcar_canfd_resume(struct device *dev) +{ + return 0; +} + +static SIMPLE_DEV_PM_OPS(rcar_canfd_pm_ops, rcar_canfd_suspend, + rcar_canfd_resume); + +static const struct of_device_id rcar_canfd_of_table[] = { + { .compatible = "renesas,rcar-gen3-canfd" }, + { } +}; + +MODULE_DEVICE_TABLE(of, rcar_canfd_of_table); + +static struct platform_driver rcar_canfd_driver = { + .driver = { + .name = RCANFD_DRV_NAME, + .of_match_table = of_match_ptr(rcar_canfd_of_table), + .pm = &rcar_canfd_pm_ops, + }, + .probe = rcar_canfd_probe, + .remove = rcar_canfd_remove, +}; + +module_platform_driver(rcar_canfd_driver); + +MODULE_AUTHOR("Ramesh Shanmugasundaram "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CAN FD driver for Renesas R-Car SoC"); +MODULE_ALIAS("platform:" RCANFD_DRV_NAME); -- cgit v1.2.3 From 6774b68b2435a129850542f9b7a3b9ce375291c6 Mon Sep 17 00:00:00 2001 From: Ivan Khoronzhuk Date: Fri, 17 Jun 2016 13:25:40 +0300 Subject: Documentation: DT: cpsw: remove rx_descs property There is no reason to hold s/w dependent parameter in device tree. Even more, there is no reason in this parameter because davinici_cpdma driver splits pool of descriptors equally between tx and rx channels anyway. Acked-by: Rob Herring Signed-off-by: Ivan Khoronzhuk Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/cpsw.txt | 1 - arch/arm/boot/dts/am33xx.dtsi | 1 - arch/arm/boot/dts/am4372.dtsi | 1 - arch/arm/boot/dts/dm814x.dtsi | 1 - arch/arm/boot/dts/dra7.dtsi | 1 - 5 files changed, 5 deletions(-) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/cpsw.txt b/Documentation/devicetree/bindings/net/cpsw.txt index 0ae06491b430..5ad439f30135 100644 --- a/Documentation/devicetree/bindings/net/cpsw.txt +++ b/Documentation/devicetree/bindings/net/cpsw.txt @@ -15,7 +15,6 @@ Required properties: - cpdma_channels : Specifies number of channels in CPDMA - ale_entries : Specifies No of entries ALE can hold - bd_ram_size : Specifies internal descriptor RAM size -- rx_descs : Specifies number of Rx descriptors - mac_control : Specifies Default MAC control register content for the specific platform - slaves : Specifies number for slaves diff --git a/arch/arm/boot/dts/am33xx.dtsi b/arch/arm/boot/dts/am33xx.dtsi index 52be48bbd2dd..702126ff4a56 100644 --- a/arch/arm/boot/dts/am33xx.dtsi +++ b/arch/arm/boot/dts/am33xx.dtsi @@ -766,7 +766,6 @@ ale_entries = <1024>; bd_ram_size = <0x2000>; no_bd_ram = <0>; - rx_descs = <64>; mac_control = <0x20>; slaves = <2>; active_slave = <0>; diff --git a/arch/arm/boot/dts/am4372.dtsi b/arch/arm/boot/dts/am4372.dtsi index 12fcde4d4d2e..a10fa7f95442 100644 --- a/arch/arm/boot/dts/am4372.dtsi +++ b/arch/arm/boot/dts/am4372.dtsi @@ -626,7 +626,6 @@ ale_entries = <1024>; bd_ram_size = <0x2000>; no_bd_ram = <0>; - rx_descs = <64>; mac_control = <0x20>; slaves = <2>; active_slave = <0>; diff --git a/arch/arm/boot/dts/dm814x.dtsi b/arch/arm/boot/dts/dm814x.dtsi index d4537dc61497..f23cae0c2179 100644 --- a/arch/arm/boot/dts/dm814x.dtsi +++ b/arch/arm/boot/dts/dm814x.dtsi @@ -509,7 +509,6 @@ ale_entries = <1024>; bd_ram_size = <0x2000>; no_bd_ram = <0>; - rx_descs = <64>; mac_control = <0x20>; slaves = <2>; active_slave = <0>; diff --git a/arch/arm/boot/dts/dra7.dtsi b/arch/arm/boot/dts/dra7.dtsi index e0074014385a..b7ddc645dd52 100644 --- a/arch/arm/boot/dts/dra7.dtsi +++ b/arch/arm/boot/dts/dra7.dtsi @@ -1626,7 +1626,6 @@ ale_entries = <1024>; bd_ram_size = <0x2000>; no_bd_ram = <0>; - rx_descs = <64>; mac_control = <0x20>; slaves = <2>; active_slave = <0>; -- cgit v1.2.3 From 6f4c2eea353809fb85386d5ce17a30e37042847d Mon Sep 17 00:00:00 2001 From: Ramesh Shanmugasundaram Date: Wed, 22 Jun 2016 13:31:46 +0100 Subject: can: rcar_canfd: Add Classical CAN only mode support The controller can operate in one of the two global modes - CAN FD only mode (default) - Classical CAN (CAN2.0) only mode This patch adds support for Classical CAN only mode. It can be enabled by defining the optional device tree property "renesas,no-can-fd" of this node. Note: R-Car Gen3 h/w manual v0.51E shows bit6 of RSCFDnCFDGCFG as reserved, which is incorrect. This bit is same as RSCFDnGCFG. Signed-off-by: Ramesh Shanmugasundaram Signed-off-by: Marc Kleine-Budde --- .../devicetree/bindings/net/can/rcar_canfd.txt | 21 +- drivers/net/can/rcar/rcar_canfd.c | 355 ++++++++++++++------- 2 files changed, 255 insertions(+), 121 deletions(-) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/can/rcar_canfd.txt b/Documentation/devicetree/bindings/net/can/rcar_canfd.txt index d45182bd21dd..22a6f10bab05 100644 --- a/Documentation/devicetree/bindings/net/can/rcar_canfd.txt +++ b/Documentation/devicetree/bindings/net/can/rcar_canfd.txt @@ -32,6 +32,12 @@ below properties. - assigned-clocks: phandle of canfd clock. - assigned-clock-rates: maximum frequency of this clock. +Optional property: +The controller can operate in either CAN FD only mode (default) or +Classical CAN only mode. The mode is global to both the channels. In order to +enable the later, define the following optional property. + - renesas,no-can-fd: puts the controller in Classical CAN only mode. + Example ------- @@ -63,12 +69,13 @@ SoC common .dtsi file: Board specific .dts file: -E.g. below enables Channel 1 alone in the board. +E.g. below enables Channel 1 alone in the board in Classical CAN only mode. &canfd { - pinctrl-0 = <&canfd1_pins>; - pinctrl-names = "default"; - status = "okay"; + pinctrl-0 = <&canfd1_pins>; + pinctrl-names = "default"; + renesas,no-can-fd; + status = "okay"; channel1 { status = "okay"; @@ -79,9 +86,9 @@ E.g. below enables Channel 0 alone in the board using External clock as fCAN clock. &canfd { - pinctrl-0 = <&canfd0_pins &can_clk_pins>; - pinctrl-names = "default"; - status = "okay"; + pinctrl-0 = <&canfd0_pins &can_clk_pins>; + pinctrl-names = "default"; + status = "okay"; channel0 { status = "okay"; diff --git a/drivers/net/can/rcar/rcar_canfd.c b/drivers/net/can/rcar/rcar_canfd.c index a39d9224166c..6bcc47408423 100644 --- a/drivers/net/can/rcar/rcar_canfd.c +++ b/drivers/net/can/rcar/rcar_canfd.c @@ -16,8 +16,9 @@ * mode, the controller acts as a CAN FD node that can also interoperate with * CAN 2.0 nodes. * - * As of now, this driver does not support the Classical CAN (CAN 2.0) mode, - * which is handled by a different register map compared to CAN FD only mode. + * To switch the controller to Classical CAN (CAN 2.0) only mode, add + * "renesas,no-can-fd" optional property to the device tree node. A h/w reset is + * also required to switch modes. * * Note: The h/w manual register naming convention is clumsy and not acceptable * to use as it is in the driver. However, those names are added as comments @@ -48,15 +49,16 @@ /* RSCFDnCFDGRMCFG */ #define RCANFD_GRMCFG_RCMC BIT(0) -/* RSCFDnCFDGCFG */ -#define RCANFD_GCFG_CMPOC BIT(5) +/* RSCFDnCFDGCFG / RSCFDnGCFG */ +#define RCANFD_GCFG_EEFE BIT(6) +#define RCANFD_GCFG_CMPOC BIT(5) /* CAN FD only */ #define RCANFD_GCFG_DCS BIT(4) #define RCANFD_GCFG_DCE BIT(1) #define RCANFD_GCFG_TPRI BIT(0) -/* RSCFDnCFDGCTR */ +/* RSCFDnCFDGCTR / RSCFDnGCTR */ #define RCANFD_GCTR_TSRST BIT(16) -#define RCANFD_GCTR_CFMPOFIE BIT(11) +#define RCANFD_GCTR_CFMPOFIE BIT(11) /* CAN FD only */ #define RCANFD_GCTR_THLEIE BIT(10) #define RCANFD_GCTR_MEIE BIT(9) #define RCANFD_GCTR_DEIE BIT(8) @@ -66,7 +68,7 @@ #define RCANFD_GCTR_GMDC_GRESET (0x1) #define RCANFD_GCTR_GMDC_GTEST (0x2) -/* RSCFDnCFDGSTS */ +/* RSCFDnCFDGSTS / RSCFDnGSTS */ #define RCANFD_GSTS_GRAMINIT BIT(3) #define RCANFD_GSTS_GSLPSTS BIT(2) #define RCANFD_GSTS_GHLTSTS BIT(1) @@ -74,44 +76,50 @@ /* Non-operational status */ #define RCANFD_GSTS_GNOPM (BIT(0) | BIT(1) | BIT(2) | BIT(3)) -/* RSCFDnCFDGERFL */ +/* RSCFDnCFDGERFL / RSCFDnGERFL */ #define RCANFD_GERFL_EEF1 BIT(17) #define RCANFD_GERFL_EEF0 BIT(16) -#define RCANFD_GERFL_CMPOF BIT(3) +#define RCANFD_GERFL_CMPOF BIT(3) /* CAN FD only */ #define RCANFD_GERFL_THLES BIT(2) #define RCANFD_GERFL_MES BIT(1) #define RCANFD_GERFL_DEF BIT(0) -#define RCANFD_GERFL_ERR(x) ((x) & (RCANFD_GERFL_EEF1 |\ - RCANFD_GERFL_EEF0 |\ - RCANFD_GERFL_MES |\ - RCANFD_GERFL_CMPOF)) +#define RCANFD_GERFL_ERR(gpriv, x) ((x) & (RCANFD_GERFL_EEF1 |\ + RCANFD_GERFL_EEF0 | RCANFD_GERFL_MES |\ + (gpriv->fdmode ?\ + RCANFD_GERFL_CMPOF : 0))) /* AFL Rx rules registers */ -/* RSCFDnCFDGAFLCFG0 */ +/* RSCFDnCFDGAFLCFG0 / RSCFDnGAFLCFG0 */ #define RCANFD_GAFLCFG_SETRNC(n, x) (((x) & 0xff) << (24 - n * 8)) #define RCANFD_GAFLCFG_GETRNC(n, x) (((x) >> (24 - n * 8)) & 0xff) -/* RSCFDnCFDGAFLECTR */ +/* RSCFDnCFDGAFLECTR / RSCFDnGAFLECTR */ #define RCANFD_GAFLECTR_AFLDAE BIT(8) #define RCANFD_GAFLECTR_AFLPN(x) ((x) & 0x1f) -/* RSCFDnCFDGAFLIDj */ +/* RSCFDnCFDGAFLIDj / RSCFDnGAFLIDj */ #define RCANFD_GAFLID_GAFLLB BIT(29) -/* RSCFDnCFDGAFLP1_j */ +/* RSCFDnCFDGAFLP1_j / RSCFDnGAFLP1_j */ #define RCANFD_GAFLP1_GAFLFDP(x) (1 << (x)) /* Channel register bits */ -/* RSCFDnCFDCmNCFG */ +/* RSCFDnCmCFG - Classical CAN only */ +#define RCANFD_CFG_SJW(x) (((x) & 0x3) << 24) +#define RCANFD_CFG_TSEG2(x) (((x) & 0x7) << 20) +#define RCANFD_CFG_TSEG1(x) (((x) & 0xf) << 16) +#define RCANFD_CFG_BRP(x) (((x) & 0x3ff) << 0) + +/* RSCFDnCFDCmNCFG - CAN FD only */ #define RCANFD_NCFG_NTSEG2(x) (((x) & 0x1f) << 24) #define RCANFD_NCFG_NTSEG1(x) (((x) & 0x7f) << 16) #define RCANFD_NCFG_NSJW(x) (((x) & 0x1f) << 11) #define RCANFD_NCFG_NBRP(x) (((x) & 0x3ff) << 0) -/* RSCFDnCFDCmCTR */ +/* RSCFDnCFDCmCTR / RSCFDnCmCTR */ #define RCANFD_CCTR_CTME BIT(24) #define RCANFD_CCTR_ERRD BIT(23) #define RCANFD_CCTR_BOM_MASK (0x3 << 21) @@ -136,7 +144,7 @@ #define RCANFD_CCTR_CHDMC_CRESET (0x1) #define RCANFD_CCTR_CHDMC_CHLT (0x2) -/* RSCFDnCFDCmSTS */ +/* RSCFDnCFDCmSTS / RSCFDnCmSTS */ #define RCANFD_CSTS_COMSTS BIT(7) #define RCANFD_CSTS_RECSTS BIT(6) #define RCANFD_CSTS_TRMSTS BIT(5) @@ -149,7 +157,7 @@ #define RCANFD_CSTS_TECCNT(x) (((x) >> 24) & 0xff) #define RCANFD_CSTS_RECCNT(x) (((x) >> 16) & 0xff) -/* RSCFDnCFDCmERFL */ +/* RSCFDnCFDCmERFL / RSCFDnCmERFL */ #define RCANFD_CERFL_ADERR BIT(14) #define RCANFD_CERFL_B0ERR BIT(13) #define RCANFD_CERFL_B1ERR BIT(12) @@ -239,14 +247,14 @@ #define RCANFD_CFFDCSTS_CFBRS BIT(1) #define RCANFD_CFFDCSTS_CFESI BIT(0) -/* This controller supports classical CAN only mode or CAN FD only mode. These - * modes are supported in two separate set of register maps & names. However, - * some of the register offsets are common for both modes. Those offsets are - * listed below as Common registers. +/* This controller supports either Classical CAN only mode or CAN FD only mode. + * These modes are supported in two separate set of register maps & names. + * However, some of the register offsets are common for both modes. Those + * offsets are listed below as Common registers. * - * The CAN FD only specific registers are listed separately and their names - * starts with RCANFD_F_xxx names. When classical CAN only specific registers - * are added, those specific registers can be prefixed as RCANFD_C_xxx. + * The CAN FD only mode specific registers & Classical CAN only mode specific + * registers are listed separately. Their register names starts with + * RCANFD_F_xxx & RCANFD_C_xxx respectively. */ /* Common registers */ @@ -353,7 +361,7 @@ #define RCANFD_GTSTCTR (0x046c) /* RSCFDnCFDGLOCKK / RSCFDnGLOCKK */ #define RCANFD_GLOCKK (0x047c) -/* RSCFDnCFDGRMCFG / RSCFDnGRMCFG */ +/* RSCFDnCFDGRMCFG */ #define RCANFD_GRMCFG (0x04fc) /* RSCFDnCFDGAFLIDj / RSCFDnGAFLIDj */ @@ -365,6 +373,46 @@ /* RSCFDnCFDGAFLP1j / RSCFDnGAFLP1j */ #define RCANFD_GAFLP1(offset, j) ((offset) + 0x0c + (0x10 * (j))) +/* Classical CAN only mode register map */ + +/* RSCFDnGAFLXXXj offset */ +#define RCANFD_C_GAFL_OFFSET (0x0500) + +/* RSCFDnRMXXXq -> RCANFD_C_RMXXX(q) */ +#define RCANFD_C_RMID(q) (0x0600 + (0x10 * (q))) +#define RCANFD_C_RMPTR(q) (0x0604 + (0x10 * (q))) +#define RCANFD_C_RMDF0(q) (0x0608 + (0x10 * (q))) +#define RCANFD_C_RMDF1(q) (0x060c + (0x10 * (q))) + +/* RSCFDnRFXXx -> RCANFD_C_RFXX(x) */ +#define RCANFD_C_RFOFFSET (0x0e00) +#define RCANFD_C_RFID(x) (RCANFD_C_RFOFFSET + (0x10 * (x))) +#define RCANFD_C_RFPTR(x) (RCANFD_C_RFOFFSET + 0x04 + \ + (0x10 * (x))) +#define RCANFD_C_RFDF(x, df) (RCANFD_C_RFOFFSET + 0x08 + \ + (0x10 * (x)) + (0x04 * (df))) + +/* RSCFDnCFXXk -> RCANFD_C_CFXX(ch, k) */ +#define RCANFD_C_CFOFFSET (0x0e80) +#define RCANFD_C_CFID(ch, idx) (RCANFD_C_CFOFFSET + (0x30 * (ch)) + \ + (0x10 * (idx))) +#define RCANFD_C_CFPTR(ch, idx) (RCANFD_C_CFOFFSET + 0x04 + \ + (0x30 * (ch)) + (0x10 * (idx))) +#define RCANFD_C_CFDF(ch, idx, df) (RCANFD_C_CFOFFSET + 0x08 + \ + (0x30 * (ch)) + (0x10 * (idx)) + \ + (0x04 * (df))) + +/* RSCFDnTMXXp -> RCANFD_C_TMXX(p) */ +#define RCANFD_C_TMID(p) (0x1000 + (0x10 * (p))) +#define RCANFD_C_TMPTR(p) (0x1004 + (0x10 * (p))) +#define RCANFD_C_TMDF0(p) (0x1008 + (0x10 * (p))) +#define RCANFD_C_TMDF1(p) (0x100c + (0x10 * (p))) + +/* RSCFDnTHLACCm */ +#define RCANFD_C_THLACC(m) (0x1800 + (0x04 * (m))) +/* RSCFDnRPGACCr */ +#define RCANFD_C_RPGACC(r) (0x1900 + (0x04 * (r))) + /* CAN FD mode specific regsiter map */ /* RSCFDnCFDCmXXX -> RCANFD_F_XXX(m) */ @@ -468,6 +516,7 @@ struct rcar_canfd_global { struct clk *can_clk; /* fCAN clock */ enum rcar_canfd_fcanclk fcan; /* CANFD or Ext clock */ unsigned long channels_mask; /* Enabled channels mask */ + bool fdmode; /* CAN FD or Classical CAN only mode */ }; /* CAN FD mode nominal rate constants */ @@ -496,6 +545,19 @@ static const struct can_bittiming_const rcar_canfd_data_bittiming_const = { .brp_inc = 1, }; +/* Classical CAN mode bitrate constants */ +static const struct can_bittiming_const rcar_canfd_bittiming_const = { + .name = RCANFD_DRV_NAME, + .tseg1_min = 4, + .tseg1_max = 16, + .tseg2_min = 2, + .tseg2_max = 8, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 1024, + .brp_inc = 1, +}; + /* Helper functions */ static inline void rcar_canfd_update(u32 mask, u32 val, u32 __iomem *reg) { @@ -593,8 +655,13 @@ static int rcar_canfd_reset_controller(struct rcar_canfd_global *gpriv) /* Reset Global error flags */ rcar_canfd_write(gpriv->base, RCANFD_GERFL, 0x0); - /* Set the controller into FD mode */ - rcar_canfd_set_bit(gpriv->base, RCANFD_GRMCFG, RCANFD_GRMCFG_RCMC); + /* Set the controller into appropriate mode */ + if (gpriv->fdmode) + rcar_canfd_set_bit(gpriv->base, RCANFD_GRMCFG, + RCANFD_GRMCFG_RCMC); + else + rcar_canfd_clear_bit(gpriv->base, RCANFD_GRMCFG, + RCANFD_GRMCFG_RCMC); /* Transition all Channels to reset mode */ for_each_set_bit(ch, &gpriv->channels_mask, RCANFD_NUM_CHANNELS) { @@ -624,8 +691,12 @@ static void rcar_canfd_configure_controller(struct rcar_canfd_global *gpriv) /* Global configuration settings */ - /* Truncate payload to configured message size RFPLS */ - cfg = RCANFD_GCFG_CMPOC; + /* ECC Error flag Enable */ + cfg = RCANFD_GCFG_EEFE; + + if (gpriv->fdmode) + /* Truncate payload to configured message size RFPLS */ + cfg |= RCANFD_GCFG_CMPOC; /* Set External Clock if selected */ if (gpriv->fcan != RCANFD_CANFDCLK) @@ -647,7 +718,7 @@ static void rcar_canfd_configure_afl_rules(struct rcar_canfd_global *gpriv, u32 ch) { u32 cfg; - int start, page, num_rules = RCANFD_CHANNEL_NUMRULES; + int offset, start, page, num_rules = RCANFD_CHANNEL_NUMRULES; u32 ridx = ch + RCANFD_RFFIFO_IDX; if (ch == 0) { @@ -667,19 +738,19 @@ static void rcar_canfd_configure_afl_rules(struct rcar_canfd_global *gpriv, /* Write number of rules for channel */ rcar_canfd_set_bit(gpriv->base, RCANFD_GAFLCFG0, RCANFD_GAFLCFG_SETRNC(ch, num_rules)); + if (gpriv->fdmode) + offset = RCANFD_F_GAFL_OFFSET; + else + offset = RCANFD_C_GAFL_OFFSET; /* Accept all IDs */ - rcar_canfd_write(gpriv->base, - RCANFD_GAFLID(RCANFD_F_GAFL_OFFSET, start), 0); + rcar_canfd_write(gpriv->base, RCANFD_GAFLID(offset, start), 0); /* IDE or RTR is not considered for matching */ - rcar_canfd_write(gpriv->base, - RCANFD_GAFLM(RCANFD_F_GAFL_OFFSET, start), 0); + rcar_canfd_write(gpriv->base, RCANFD_GAFLM(offset, start), 0); /* Any data length accepted */ - rcar_canfd_write(gpriv->base, - RCANFD_GAFLP0(RCANFD_F_GAFL_OFFSET, start), 0); + rcar_canfd_write(gpriv->base, RCANFD_GAFLP0(offset, start), 0); /* Place the msg in corresponding Rx FIFO entry */ - rcar_canfd_write(gpriv->base, - RCANFD_GAFLP1(RCANFD_F_GAFL_OFFSET, start), + rcar_canfd_write(gpriv->base, RCANFD_GAFLP1(offset, start), RCANFD_GAFLP1_GAFLFDP(ridx)); /* Disable write access to page */ @@ -697,7 +768,10 @@ static void rcar_canfd_configure_rx(struct rcar_canfd_global *gpriv, u32 ch) u32 ridx = ch + RCANFD_RFFIFO_IDX; rfdc = 2; /* b010 - 8 messages Rx FIFO depth */ - rfpls = 7; /* b111 - Max 64 bytes payload */ + if (gpriv->fdmode) + rfpls = 7; /* b111 - Max 64 bytes payload */ + else + rfpls = 0; /* b000 - Max 8 bytes payload */ cfg = (RCANFD_RFCC_RFIM | RCANFD_RFCC_RFDC(rfdc) | RCANFD_RFCC_RFPLS(rfpls) | RCANFD_RFCC_RFIE); @@ -718,16 +792,20 @@ static void rcar_canfd_configure_tx(struct rcar_canfd_global *gpriv, u32 ch) cftml = 0; /* 0th buffer */ cfm = 1; /* b01 - Transmit mode */ cfdc = 2; /* b010 - 8 messages Tx FIFO depth */ - cfpls = 7; /* b111 - Max 64 bytes payload */ + if (gpriv->fdmode) + cfpls = 7; /* b111 - Max 64 bytes payload */ + else + cfpls = 0; /* b000 - Max 8 bytes payload */ cfg = (RCANFD_CFCC_CFTML(cftml) | RCANFD_CFCC_CFM(cfm) | RCANFD_CFCC_CFIM | RCANFD_CFCC_CFDC(cfdc) | RCANFD_CFCC_CFPLS(cfpls) | RCANFD_CFCC_CFTXIE); rcar_canfd_write(gpriv->base, RCANFD_CFCC(ch, RCANFD_CFFIFO_IDX), cfg); - /* Clear FD mode specific control/status register */ - rcar_canfd_write(gpriv->base, - RCANFD_F_CFFDCSTS(ch, RCANFD_CFFIFO_IDX), 0); + if (gpriv->fdmode) + /* Clear FD mode specific control/status register */ + rcar_canfd_write(gpriv->base, + RCANFD_F_CFFDCSTS(ch, RCANFD_CFFIFO_IDX), 0); } static void rcar_canfd_enable_global_interrupts(struct rcar_canfd_global *gpriv) @@ -739,7 +817,8 @@ static void rcar_canfd_enable_global_interrupts(struct rcar_canfd_global *gpriv) /* Global interrupts setup */ ctr = RCANFD_GCTR_MEIE; - ctr |= RCANFD_GCTR_CFMPOFIE; + if (gpriv->fdmode) + ctr |= RCANFD_GCTR_CFMPOFIE; rcar_canfd_set_bit(gpriv->base, RCANFD_GCTR, ctr); } @@ -790,6 +869,7 @@ static void rcar_canfd_disable_channel_interrupts(struct rcar_canfd_channel static void rcar_canfd_global_error(struct net_device *ndev) { struct rcar_canfd_channel *priv = netdev_priv(ndev); + struct rcar_canfd_global *gpriv = priv->gpriv; struct net_device_stats *stats = &ndev->stats; u32 ch = priv->channel; u32 gerfl, sts; @@ -823,7 +903,7 @@ static void rcar_canfd_global_error(struct net_device *ndev) sts & ~RCANFD_RFSTS_RFMLT); } } - if (gerfl & RCANFD_GERFL_CMPOF) { + if (gpriv->fdmode && gerfl & RCANFD_GERFL_CMPOF) { /* Message Lost flag will be set for respective channel * when this condition happens with counters and flags * already updated. @@ -1018,7 +1098,7 @@ static irqreturn_t rcar_canfd_global_interrupt(int irq, void *dev_id) /* Global error interrupts */ gerfl = rcar_canfd_read(priv->base, RCANFD_GERFL); - if (RCANFD_GERFL_ERR(gerfl)) + if (RCANFD_GERFL_ERR(gpriv, gerfl)) rcar_canfd_global_error(ndev); /* Handle Rx interrupts */ @@ -1077,25 +1157,37 @@ static void rcar_canfd_set_bittiming(struct net_device *dev) tseg1 = bt->prop_seg + bt->phase_seg1 - 1; tseg2 = bt->phase_seg2 - 1; - cfg = (RCANFD_NCFG_NTSEG1(tseg1) | RCANFD_NCFG_NBRP(brp) | - RCANFD_NCFG_NSJW(sjw) | RCANFD_NCFG_NTSEG2(tseg2)); + if (priv->can.ctrlmode & CAN_CTRLMODE_FD) { + /* CAN FD only mode */ + cfg = (RCANFD_NCFG_NTSEG1(tseg1) | RCANFD_NCFG_NBRP(brp) | + RCANFD_NCFG_NSJW(sjw) | RCANFD_NCFG_NTSEG2(tseg2)); - rcar_canfd_write(priv->base, RCANFD_CCFG(ch), cfg); - netdev_dbg(priv->ndev, "nrate: brp %u, sjw %u, tseg1 %u, tseg2 %u\n", - brp, sjw, tseg1, tseg2); + rcar_canfd_write(priv->base, RCANFD_CCFG(ch), cfg); + netdev_dbg(priv->ndev, "nrate: brp %u, sjw %u, tseg1 %u, tseg2 %u\n", + brp, sjw, tseg1, tseg2); - /* Data bit timing settings */ - brp = dbt->brp - 1; - sjw = dbt->sjw - 1; - tseg1 = dbt->prop_seg + dbt->phase_seg1 - 1; - tseg2 = dbt->phase_seg2 - 1; + /* Data bit timing settings */ + brp = dbt->brp - 1; + sjw = dbt->sjw - 1; + tseg1 = dbt->prop_seg + dbt->phase_seg1 - 1; + tseg2 = dbt->phase_seg2 - 1; - cfg = (RCANFD_DCFG_DTSEG1(tseg1) | RCANFD_DCFG_DBRP(brp) | - RCANFD_DCFG_DSJW(sjw) | RCANFD_DCFG_DTSEG2(tseg2)); + cfg = (RCANFD_DCFG_DTSEG1(tseg1) | RCANFD_DCFG_DBRP(brp) | + RCANFD_DCFG_DSJW(sjw) | RCANFD_DCFG_DTSEG2(tseg2)); - rcar_canfd_write(priv->base, RCANFD_F_DCFG(ch), cfg); - netdev_dbg(priv->ndev, "drate: brp %u, sjw %u, tseg1 %u, tseg2 %u\n", - brp, sjw, tseg1, tseg2); + rcar_canfd_write(priv->base, RCANFD_F_DCFG(ch), cfg); + netdev_dbg(priv->ndev, "drate: brp %u, sjw %u, tseg1 %u, tseg2 %u\n", + brp, sjw, tseg1, tseg2); + } else { + /* Classical CAN only mode */ + cfg = (RCANFD_CFG_TSEG1(tseg1) | RCANFD_CFG_BRP(brp) | + RCANFD_CFG_SJW(sjw) | RCANFD_CFG_TSEG2(tseg2)); + + rcar_canfd_write(priv->base, RCANFD_CCFG(ch), cfg); + netdev_dbg(priv->ndev, + "rate: brp %u, sjw %u, tseg1 %u, tseg2 %u\n", + brp, sjw, tseg1, tseg2); + } } static int rcar_canfd_start(struct net_device *ndev) @@ -1233,27 +1325,37 @@ static netdev_tx_t rcar_canfd_start_xmit(struct sk_buff *skb, if (cf->can_id & CAN_RTR_FLAG) id |= RCANFD_CFID_CFRTR; - rcar_canfd_write(priv->base, - RCANFD_F_CFID(ch, RCANFD_CFFIFO_IDX), id); dlc = RCANFD_CFPTR_CFDLC(can_len2dlc(cf->len)); - rcar_canfd_write(priv->base, - RCANFD_F_CFPTR(ch, RCANFD_CFFIFO_IDX), dlc); - if (can_is_canfd_skb(skb)) { - /* CAN FD frame format */ - sts |= RCANFD_CFFDCSTS_CFFDF; - if (cf->flags & CANFD_BRS) - sts |= RCANFD_CFFDCSTS_CFBRS; + if (priv->can.ctrlmode & CAN_CTRLMODE_FD) { + rcar_canfd_write(priv->base, + RCANFD_F_CFID(ch, RCANFD_CFFIFO_IDX), id); + rcar_canfd_write(priv->base, + RCANFD_F_CFPTR(ch, RCANFD_CFFIFO_IDX), dlc); - if (priv->can.state == CAN_STATE_ERROR_PASSIVE) - sts |= RCANFD_CFFDCSTS_CFESI; - } + if (can_is_canfd_skb(skb)) { + /* CAN FD frame format */ + sts |= RCANFD_CFFDCSTS_CFFDF; + if (cf->flags & CANFD_BRS) + sts |= RCANFD_CFFDCSTS_CFBRS; - rcar_canfd_write(priv->base, RCANFD_F_CFFDCSTS(ch, RCANFD_CFFIFO_IDX), - sts); + if (priv->can.state == CAN_STATE_ERROR_PASSIVE) + sts |= RCANFD_CFFDCSTS_CFESI; + } + + rcar_canfd_write(priv->base, + RCANFD_F_CFFDCSTS(ch, RCANFD_CFFIFO_IDX), sts); - rcar_canfd_put_data(priv, cf, - RCANFD_F_CFDF(ch, RCANFD_CFFIFO_IDX, 0)); + rcar_canfd_put_data(priv, cf, + RCANFD_F_CFDF(ch, RCANFD_CFFIFO_IDX, 0)); + } else { + rcar_canfd_write(priv->base, + RCANFD_C_CFID(ch, RCANFD_CFFIFO_IDX), id); + rcar_canfd_write(priv->base, + RCANFD_C_CFPTR(ch, RCANFD_CFFIFO_IDX), dlc); + rcar_canfd_put_data(priv, cf, + RCANFD_C_CFDF(ch, RCANFD_CFFIFO_IDX, 0)); + } priv->tx_len[priv->tx_head % RCANFD_FIFO_DEPTH] = cf->len; can_put_echo_skb(skb, ndev, priv->tx_head % RCANFD_FIFO_DEPTH); @@ -1280,47 +1382,61 @@ static void rcar_canfd_rx_pkt(struct rcar_canfd_channel *priv) struct net_device_stats *stats = &priv->ndev->stats; struct canfd_frame *cf; struct sk_buff *skb; - u32 sts = 0, id, ptr; + u32 sts = 0, id, dlc; u32 ch = priv->channel; u32 ridx = ch + RCANFD_RFFIFO_IDX; - id = rcar_canfd_read(priv->base, RCANFD_F_RFID(ridx)); - ptr = rcar_canfd_read(priv->base, RCANFD_F_RFPTR(ridx)); + if (priv->can.ctrlmode & CAN_CTRLMODE_FD) { + id = rcar_canfd_read(priv->base, RCANFD_F_RFID(ridx)); + dlc = rcar_canfd_read(priv->base, RCANFD_F_RFPTR(ridx)); - sts = rcar_canfd_read(priv->base, RCANFD_F_RFFDSTS(ridx)); - if (sts & RCANFD_RFFDSTS_RFFDF) - skb = alloc_canfd_skb(priv->ndev, &cf); - else - skb = alloc_can_skb(priv->ndev, - (struct can_frame **)&cf); + sts = rcar_canfd_read(priv->base, RCANFD_F_RFFDSTS(ridx)); + if (sts & RCANFD_RFFDSTS_RFFDF) + skb = alloc_canfd_skb(priv->ndev, &cf); + else + skb = alloc_can_skb(priv->ndev, + (struct can_frame **)&cf); + } else { + id = rcar_canfd_read(priv->base, RCANFD_C_RFID(ridx)); + dlc = rcar_canfd_read(priv->base, RCANFD_C_RFPTR(ridx)); + skb = alloc_can_skb(priv->ndev, (struct can_frame **)&cf); + } if (!skb) { stats->rx_dropped++; return; } - if (sts & RCANFD_RFFDSTS_RFFDF) - cf->len = can_dlc2len(RCANFD_RFPTR_RFDLC(ptr)); - else - cf->len = get_can_dlc(RCANFD_RFPTR_RFDLC(ptr)); - - if (sts & RCANFD_RFFDSTS_RFESI) { - cf->flags |= CANFD_ESI; - netdev_dbg(priv->ndev, "ESI Error\n"); - } - if (id & RCANFD_RFID_RFIDE) cf->can_id = (id & CAN_EFF_MASK) | CAN_EFF_FLAG; else cf->can_id = id & CAN_SFF_MASK; - if (!(sts & RCANFD_RFFDSTS_RFFDF) && (id & RCANFD_RFID_RFRTR)) { - cf->can_id |= CAN_RTR_FLAG; - } else { - if (sts & RCANFD_RFFDSTS_RFBRS) - cf->flags |= CANFD_BRS; + if (priv->can.ctrlmode & CAN_CTRLMODE_FD) { + if (sts & RCANFD_RFFDSTS_RFFDF) + cf->len = can_dlc2len(RCANFD_RFPTR_RFDLC(dlc)); + else + cf->len = get_can_dlc(RCANFD_RFPTR_RFDLC(dlc)); + + if (sts & RCANFD_RFFDSTS_RFESI) { + cf->flags |= CANFD_ESI; + netdev_dbg(priv->ndev, "ESI Error\n"); + } + + if (!(sts & RCANFD_RFFDSTS_RFFDF) && (id & RCANFD_RFID_RFRTR)) { + cf->can_id |= CAN_RTR_FLAG; + } else { + if (sts & RCANFD_RFFDSTS_RFBRS) + cf->flags |= CANFD_BRS; - rcar_canfd_get_data(priv, cf, RCANFD_F_RFDF(ridx, 0)); + rcar_canfd_get_data(priv, cf, RCANFD_F_RFDF(ridx, 0)); + } + } else { + cf->len = get_can_dlc(RCANFD_RFPTR_RFDLC(dlc)); + if (id & RCANFD_RFID_RFRTR) + cf->can_id |= CAN_RTR_FLAG; + else + rcar_canfd_get_data(priv, cf, RCANFD_C_RFDF(ridx, 0)); } /* Write 0xff to RFPC to increment the CPU-side @@ -1428,13 +1544,19 @@ static int rcar_canfd_channel_probe(struct rcar_canfd_global *gpriv, u32 ch, priv->can.clock.freq = fcan_freq; dev_info(&pdev->dev, "can_clk rate is %u\n", priv->can.clock.freq); - priv->can.bittiming_const = &rcar_canfd_nom_bittiming_const; - priv->can.data_bittiming_const = - &rcar_canfd_data_bittiming_const; + if (gpriv->fdmode) { + priv->can.bittiming_const = &rcar_canfd_nom_bittiming_const; + priv->can.data_bittiming_const = + &rcar_canfd_data_bittiming_const; - /* Controller starts in CAN FD only mode */ - can_set_static_ctrlmode(ndev, CAN_CTRLMODE_FD); - priv->can.ctrlmode_supported = CAN_CTRLMODE_BERR_REPORTING; + /* Controller starts in CAN FD only mode */ + can_set_static_ctrlmode(ndev, CAN_CTRLMODE_FD); + priv->can.ctrlmode_supported = CAN_CTRLMODE_BERR_REPORTING; + } else { + /* Controller starts in Classical CAN only mode */ + priv->can.bittiming_const = &rcar_canfd_bittiming_const; + priv->can.ctrlmode_supported = CAN_CTRLMODE_BERR_REPORTING; + } priv->can.do_set_mode = rcar_canfd_do_set_mode; priv->can.do_get_berr_counter = rcar_canfd_get_berr_counter; @@ -1482,6 +1604,10 @@ static int rcar_canfd_probe(struct platform_device *pdev) struct device_node *of_child; unsigned long channels_mask = 0; int err, ch_irq, g_irq; + bool fdmode = true; /* CAN FD only mode - default */ + + if (of_property_read_bool(pdev->dev.of_node, "renesas,no-can-fd")) + fdmode = false; /* Classical CAN only mode */ of_child = of_get_child_by_name(pdev->dev.of_node, "channel0"); if (of_child && of_device_is_available(of_child)) @@ -1513,6 +1639,7 @@ static int rcar_canfd_probe(struct platform_device *pdev) } gpriv->pdev = pdev; gpriv->channels_mask = channels_mask; + gpriv->fdmode = fdmode; /* Peripheral clock */ gpriv->clkp = devm_clk_get(&pdev->dev, "fck"); @@ -1623,8 +1750,8 @@ static int rcar_canfd_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, gpriv); - dev_info(&pdev->dev, "global operational state (clk %d)\n", - gpriv->fcan); + dev_info(&pdev->dev, "global operational state (clk %d, fdmode %d)\n", + gpriv->fcan, gpriv->fdmode); return 0; fail_channel: -- cgit v1.2.3 From e7ffd81233334b7755050523cb7e0456ae3d2e53 Mon Sep 17 00:00:00 2001 From: Xing Zheng Date: Tue, 21 Jun 2016 20:33:28 +0800 Subject: net: stmmac: dwmac-rk: add rk3228-specific data Add constants and callback functions for the dwmac on rk3228/rk3229 socs. As can be seen, the base structure is the same, only registers and the bits in them moved slightly. Signed-off-by: Xing Zheng Reviewed-by: Heiko Stuebner Acked-by: Rob Herring Signed-off-by: David S. Miller --- .../devicetree/bindings/net/rockchip-dwmac.txt | 3 +- drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c | 117 +++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/rockchip-dwmac.txt b/Documentation/devicetree/bindings/net/rockchip-dwmac.txt index 93eac7ce1446..cccd945fc45b 100644 --- a/Documentation/devicetree/bindings/net/rockchip-dwmac.txt +++ b/Documentation/devicetree/bindings/net/rockchip-dwmac.txt @@ -3,7 +3,8 @@ Rockchip SoC RK3288 10/100/1000 Ethernet driver(GMAC) The device node has following properties. Required properties: - - compatible: Can be one of "rockchip,rk3288-gmac", "rockchip,rk3368-gmac" + - compatible: Can be one of "rockchip,rk3228-gmac", "rockchip,rk3288-gmac", + "rockchip,rk3368-gmac" - reg: addresses and length of the register sets for the device. - interrupts: Should contain the GMAC interrupts. - interrupt-names: Should contain the interrupt names "macirq". diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c index 63c2e4fda169..92105916ef40 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c @@ -73,6 +73,122 @@ struct rk_priv_data { #define GRF_BIT(nr) (BIT(nr) | BIT(nr+16)) #define GRF_CLR_BIT(nr) (BIT(nr+16)) +#define RK3228_GRF_MAC_CON0 0x0900 +#define RK3228_GRF_MAC_CON1 0x0904 + +/* RK3228_GRF_MAC_CON0 */ +#define RK3228_GMAC_CLK_RX_DL_CFG(val) HIWORD_UPDATE(val, 0x7F, 7) +#define RK3228_GMAC_CLK_TX_DL_CFG(val) HIWORD_UPDATE(val, 0x7F, 0) + +/* RK3228_GRF_MAC_CON1 */ +#define RK3228_GMAC_PHY_INTF_SEL_RGMII \ + (GRF_BIT(4) | GRF_CLR_BIT(5) | GRF_CLR_BIT(6)) +#define RK3228_GMAC_PHY_INTF_SEL_RMII \ + (GRF_CLR_BIT(4) | GRF_CLR_BIT(5) | GRF_BIT(6)) +#define RK3228_GMAC_FLOW_CTRL GRF_BIT(3) +#define RK3228_GMAC_FLOW_CTRL_CLR GRF_CLR_BIT(3) +#define RK3228_GMAC_SPEED_10M GRF_CLR_BIT(2) +#define RK3228_GMAC_SPEED_100M GRF_BIT(2) +#define RK3228_GMAC_RMII_CLK_25M GRF_BIT(7) +#define RK3228_GMAC_RMII_CLK_2_5M GRF_CLR_BIT(7) +#define RK3228_GMAC_CLK_125M (GRF_CLR_BIT(8) | GRF_CLR_BIT(9)) +#define RK3228_GMAC_CLK_25M (GRF_BIT(8) | GRF_BIT(9)) +#define RK3228_GMAC_CLK_2_5M (GRF_CLR_BIT(8) | GRF_BIT(9)) +#define RK3228_GMAC_RMII_MODE GRF_BIT(10) +#define RK3228_GMAC_RMII_MODE_CLR GRF_CLR_BIT(10) +#define RK3228_GMAC_TXCLK_DLY_ENABLE GRF_BIT(0) +#define RK3228_GMAC_TXCLK_DLY_DISABLE GRF_CLR_BIT(0) +#define RK3228_GMAC_RXCLK_DLY_ENABLE GRF_BIT(1) +#define RK3228_GMAC_RXCLK_DLY_DISABLE GRF_CLR_BIT(1) + +static void rk3228_set_to_rgmii(struct rk_priv_data *bsp_priv, + int tx_delay, int rx_delay) +{ + struct device *dev = &bsp_priv->pdev->dev; + + if (IS_ERR(bsp_priv->grf)) { + dev_err(dev, "Missing rockchip,grf property\n"); + return; + } + + regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON1, + RK3228_GMAC_PHY_INTF_SEL_RGMII | + RK3228_GMAC_RMII_MODE_CLR | + RK3228_GMAC_RXCLK_DLY_ENABLE | + RK3228_GMAC_TXCLK_DLY_ENABLE); + + regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON0, + RK3228_GMAC_CLK_RX_DL_CFG(rx_delay) | + RK3228_GMAC_CLK_TX_DL_CFG(tx_delay)); +} + +static void rk3228_set_to_rmii(struct rk_priv_data *bsp_priv) +{ + struct device *dev = &bsp_priv->pdev->dev; + + if (IS_ERR(bsp_priv->grf)) { + dev_err(dev, "Missing rockchip,grf property\n"); + return; + } + + regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON1, + RK3228_GMAC_PHY_INTF_SEL_RMII | + RK3228_GMAC_RMII_MODE); + + /* set MAC to RMII mode */ + regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON1, GRF_BIT(11)); +} + +static void rk3228_set_rgmii_speed(struct rk_priv_data *bsp_priv, int speed) +{ + struct device *dev = &bsp_priv->pdev->dev; + + if (IS_ERR(bsp_priv->grf)) { + dev_err(dev, "Missing rockchip,grf property\n"); + return; + } + + if (speed == 10) + regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON1, + RK3228_GMAC_CLK_2_5M); + else if (speed == 100) + regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON1, + RK3228_GMAC_CLK_25M); + else if (speed == 1000) + regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON1, + RK3228_GMAC_CLK_125M); + else + dev_err(dev, "unknown speed value for RGMII! speed=%d", speed); +} + +static void rk3228_set_rmii_speed(struct rk_priv_data *bsp_priv, int speed) +{ + struct device *dev = &bsp_priv->pdev->dev; + + if (IS_ERR(bsp_priv->grf)) { + dev_err(dev, "Missing rockchip,grf property\n"); + return; + } + + if (speed == 10) + regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON1, + RK3228_GMAC_RMII_CLK_2_5M | + RK3228_GMAC_SPEED_10M); + else if (speed == 100) + regmap_write(bsp_priv->grf, RK3228_GRF_MAC_CON1, + RK3228_GMAC_RMII_CLK_25M | + RK3228_GMAC_SPEED_100M); + else + dev_err(dev, "unknown speed value for RMII! speed=%d", speed); +} + +static const struct rk_gmac_ops rk3228_ops = { + .set_to_rgmii = rk3228_set_to_rgmii, + .set_to_rmii = rk3228_set_to_rmii, + .set_rgmii_speed = rk3228_set_rgmii_speed, + .set_rmii_speed = rk3228_set_rmii_speed, +}; + #define RK3288_GRF_SOC_CON1 0x0248 #define RK3288_GRF_SOC_CON3 0x0250 @@ -642,6 +758,7 @@ static int rk_gmac_probe(struct platform_device *pdev) } static const struct of_device_id rk_gmac_dwmac_match[] = { + { .compatible = "rockchip,rk3228-gmac", .data = &rk3228_ops }, { .compatible = "rockchip,rk3288-gmac", .data = &rk3288_ops }, { .compatible = "rockchip,rk3368-gmac", .data = &rk3368_ops }, { } -- cgit v1.2.3 From 02e57b9d7c8ce9e403f15f48fb91dd6549aaf465 Mon Sep 17 00:00:00 2001 From: Giuseppe CAVALLARO Date: Fri, 24 Jun 2016 15:16:26 +0200 Subject: drivers: net: stmmac: add port selection programming In case of SGMII more, for example when a MAC2MAC connection is needed, the port selection bits (inside the MAC configuration registers) have to be programmed according to the link selected. So the patch adds a new DT parameter to pass the port selection and to programmed related PCS and CORE to use it. Signed-off-by: Giuseppe Cavallaro Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/stmmac.txt | 3 +++ drivers/net/ethernet/stmicro/stmmac/common.h | 1 + drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c | 15 +++++++++++++++ drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 15 +++++++++++++++ drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 3 ++- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 15 ++++++++++++++- drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 2 ++ include/linux/stmmac.h | 1 + 8 files changed, 53 insertions(+), 2 deletions(-) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/stmmac.txt b/Documentation/devicetree/bindings/net/stmmac.txt index 95816c5fc589..41b49e6075f5 100644 --- a/Documentation/devicetree/bindings/net/stmmac.txt +++ b/Documentation/devicetree/bindings/net/stmmac.txt @@ -47,6 +47,9 @@ Optional properties: supported by this device instance - snps,perfect-filter-entries: Number of perfect filter entries supported by this device instance +- snps,ps-speed: port selection speed that can be passed to the core when + PCS is supported. For example, this is used in case of SGMII + and MAC2MAC connection. - AXI BUS Mode parameters: below the list of all the parameters to program the AXI register inside the DMA module: - snps,lpi_en: enable Low Power Interface diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 51077a8dc1f5..2533b91f1421 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -531,6 +531,7 @@ struct mac_device_info { unsigned int rx_csum; unsigned int pcs; unsigned int pmt; + unsigned int ps; }; struct mac_device_info *dwmac1000_setup(void __iomem *ioaddr, int mcbins, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 0d31f2f80d07..cbefe9e2207c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -46,6 +46,21 @@ static void dwmac1000_core_init(struct mac_device_info *hw, int mtu) if (mtu > 2000) value |= GMAC_CONTROL_JE; + if (hw->ps) { + value |= GMAC_CONTROL_TE; + + if (hw->ps == SPEED_1000) { + value &= ~GMAC_CONTROL_PS; + } else { + value |= GMAC_CONTROL_PS; + + if (hw->ps == SPEED_10) + value &= ~GMAC_CONTROL_FES; + else + value |= GMAC_CONTROL_FES; + } + } + writel(value, ioaddr + GMAC_CONTROL); /* Mask GMAC interrupts */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 747f3cf1e20b..df5580dcdfed 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -32,6 +32,21 @@ static void dwmac4_core_init(struct mac_device_info *hw, int mtu) if (mtu > 2000) value |= GMAC_CONFIG_JE; + if (hw->ps) { + value |= GMAC_CONFIG_TE; + + if (hw->ps == SPEED_1000) { + value &= ~GMAC_CONFIG_PS; + } else { + value |= GMAC_CONFIG_PS; + + if (hw->ps == SPEED_10) + value &= ~GMAC_CONFIG_FES; + else + value |= GMAC_CONFIG_FES; + } + } + writel(value, ioaddr + GMAC_CONFIG); /* Mask GMAC interrupts */ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index da2d9b5b740a..1e06173fc9d7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -380,7 +380,8 @@ static int stmmac_ethtool_setsettings(struct net_device *dev, spin_lock(&priv->lock); if (priv->hw->mac->pcs_ctrl_ane) - priv->hw->mac->pcs_ctrl_ane(priv->ioaddr, 1, 0, 0); + priv->hw->mac->pcs_ctrl_ane(priv->ioaddr, 1, + priv->hw->ps, 0); spin_unlock(&priv->lock); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 6142fce9ffac..aab777c1ba33 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1666,6 +1666,19 @@ static int stmmac_hw_setup(struct net_device *dev, bool init_ptp) if (priv->plat->bus_setup) priv->plat->bus_setup(priv->ioaddr); + /* PS and related bits will be programmed according to the speed */ + if (priv->hw->pcs) { + int speed = priv->plat->mac_port_sel_speed; + + if ((speed == SPEED_10) || (speed == SPEED_100) || + (speed == SPEED_1000)) { + priv->hw->ps = speed; + } else { + dev_warn(priv->device, "invalid port speed\n"); + priv->hw->ps = 0; + } + } + /* Initialize the MAC Core */ priv->hw->mac->core_init(priv->hw, dev->mtu); @@ -1716,7 +1729,7 @@ static int stmmac_hw_setup(struct net_device *dev, bool init_ptp) } if (priv->hw->pcs && priv->hw->mac->pcs_ctrl_ane) - priv->hw->mac->pcs_ctrl_ane(priv->hw, 1, 0, 0); + priv->hw->mac->pcs_ctrl_ane(priv->hw, 1, priv->hw->ps, 0); /* set TX ring length */ if (priv->hw->dma->set_tx_ring_len) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index a96714d34560..f7dfc0ae8e9c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -319,6 +319,8 @@ stmmac_probe_config_dt(struct platform_device *pdev, const char **mac) pr_warn("force_sf_dma_mode is ignored if force_thresh_dma_mode is set."); } + of_property_read_u32(np, "snps,ps-speed", &plat->mac_port_sel_speed); + plat->axi = stmmac_axi_setup(pdev); return plat; diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index 0507dbfbf63c..705840e0438f 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -141,5 +141,6 @@ struct plat_stmmacenet_data { struct stmmac_axi *axi; int has_gmac4; bool tso_en; + int mac_port_sel_speed; }; #endif -- cgit v1.2.3 From beb1a04619f6bcf0e0452780b4c3eaab995e0b43 Mon Sep 17 00:00:00 2001 From: Grygorii Strashko Date: Fri, 24 Jun 2016 21:23:52 +0300 Subject: net: davinci_mdio: document missed "ti, am4372-mdio" compat string Document missed "ti,am4372-mdio" compat string used for TI am437x SoC (am4372.dtsi). Signed-off-by: Grygorii Strashko Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/davinci-mdio.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/davinci-mdio.txt b/Documentation/devicetree/bindings/net/davinci-mdio.txt index 0369e25aabd2..b6a4f487a823 100644 --- a/Documentation/devicetree/bindings/net/davinci-mdio.txt +++ b/Documentation/devicetree/bindings/net/davinci-mdio.txt @@ -2,7 +2,9 @@ TI SoC Davinci/Keystone2 MDIO Controller Device Tree Bindings --------------------------------------------------- Required properties: -- compatible : Should be "ti,davinci_mdio" or "ti,keystone_mdio" +- compatible : Should be "ti,davinci_mdio" + and "ti,keystone_mdio" for Keystone 2 SoCs + and "ti,am4372-mdio" for am472x SoC - reg : physical base address and size of the davinci mdio registers map - bus_freq : Mdio Bus frequency -- cgit v1.2.3 From 22899eca586b862d5761f1b4795a3951fc0e502f Mon Sep 17 00:00:00 2001 From: Grygorii Strashko Date: Fri, 24 Jun 2016 21:23:53 +0300 Subject: net: davinci_mdio: introduce "ti,cpsw-mdio" compat string Introduce "ti,cpsw-mdio" compatible string for Davinci MDIO, because it's required to distinguish the case when MDIO is part of TI CPSW to enable features supported by TI CPSW (for example, enable PM management). Signed-off-by: Grygorii Strashko Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/davinci-mdio.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/davinci-mdio.txt b/Documentation/devicetree/bindings/net/davinci-mdio.txt index b6a4f487a823..621156ca4ffd 100644 --- a/Documentation/devicetree/bindings/net/davinci-mdio.txt +++ b/Documentation/devicetree/bindings/net/davinci-mdio.txt @@ -4,6 +4,7 @@ TI SoC Davinci/Keystone2 MDIO Controller Device Tree Bindings Required properties: - compatible : Should be "ti,davinci_mdio" and "ti,keystone_mdio" for Keystone 2 SoCs + and "ti,cpsw-mdio" for am335x, am472x, am57xx/dra7, dm814x SoCs and "ti,am4372-mdio" for am472x SoC - reg : physical base address and size of the davinci mdio registers map -- cgit v1.2.3 From 9b9a553c902e79867b6527301fe64e45be23e36b Mon Sep 17 00:00:00 2001 From: Masanari Iida Date: Wed, 29 Jun 2016 23:36:20 +0900 Subject: net: netcp: Fix a typo in keystone-netcp.txt This patch fix a spelling typo in keystone-netcp.txt Signed-off-by: Masanari Iida Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/keystone-netcp.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/keystone-netcp.txt b/Documentation/devicetree/bindings/net/keystone-netcp.txt index b30ab6b5cbfa..04ba1dc34fd6 100644 --- a/Documentation/devicetree/bindings/net/keystone-netcp.txt +++ b/Documentation/devicetree/bindings/net/keystone-netcp.txt @@ -2,7 +2,7 @@ This document describes the device tree bindings associated with the keystone network coprocessor(NetCP) driver support. The network coprocessor (NetCP) is a hardware accelerator that processes -Ethernet packets. NetCP has a gigabit Ethernet (GbE) subsytem with a ethernet +Ethernet packets. NetCP has a gigabit Ethernet (GbE) subsystem with a ethernet switch sub-module to send and receive packets. NetCP also includes a packet accelerator (PA) module to perform packet classification operations such as header matching, and packet modification operations such as checksum -- cgit v1.2.3 From fb3bbdb859891e6bc27fd1afb3a07319f82c2ee4 Mon Sep 17 00:00:00 2001 From: Tien Hock Loh Date: Thu, 7 Jul 2016 20:23:30 -0700 Subject: net: ethernet: Add TSE PCS support to dwmac-socfpga This adds support for TSE PCS that uses SGMII adapter when the phy-mode of the dwmac is set to sgmii. Signed-off-by: Tien Hock Loh Acked-by: Rob Herring Signed-off-by: David S. Miller --- .../devicetree/bindings/net/socfpga-dwmac.txt | 19 ++ drivers/net/ethernet/stmicro/stmmac/Makefile | 3 +- drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.c | 274 +++++++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.h | 36 +++ .../net/ethernet/stmicro/stmmac/dwmac-socfpga.c | 140 +++++++++-- 5 files changed, 450 insertions(+), 22 deletions(-) create mode 100644 drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.c create mode 100644 drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.h (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/socfpga-dwmac.txt b/Documentation/devicetree/bindings/net/socfpga-dwmac.txt index 72d82d684342..2e68a3cd8513 100644 --- a/Documentation/devicetree/bindings/net/socfpga-dwmac.txt +++ b/Documentation/devicetree/bindings/net/socfpga-dwmac.txt @@ -17,9 +17,26 @@ Required properties: Optional properties: altr,emac-splitter: Should be the phandle to the emac splitter soft IP node if DWMAC controller is connected emac splitter. +phy-mode: The phy mode the ethernet operates in +altr,sgmii-to-sgmii-converter: phandle to the TSE SGMII converter + +This device node has additional phandle dependency, the sgmii converter: + +Required properties: + - compatible : Should be altr,gmii-to-sgmii-2.0 + - reg-names : Should be "eth_tse_control_port" Example: +gmii_to_sgmii_converter: phy@0x100000240 { + compatible = "altr,gmii-to-sgmii-2.0"; + reg = <0x00000001 0x00000240 0x00000008>, + <0x00000001 0x00000200 0x00000040>; + reg-names = "eth_tse_control_port"; + clocks = <&sgmii_1_clk_0 &emac1 1 &sgmii_clk_125 &sgmii_clk_125>; + clock-names = "tse_pcs_ref_clk_clock_connection", "tse_rx_cdr_refclk"; +}; + gmac0: ethernet@ff700000 { compatible = "altr,socfpga-stmmac", "snps,dwmac-3.70a", "snps,dwmac"; altr,sysmgr-syscon = <&sysmgr 0x60 0>; @@ -30,4 +47,6 @@ gmac0: ethernet@ff700000 { mac-address = [00 00 00 00 00 00];/* Filled in by U-Boot */ clocks = <&emac_0_clk>; clock-names = "stmmaceth"; + phy-mode = "sgmii"; + altr,gmii-to-sgmii-converter = <&gmii_to_sgmii_converter>; }; diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile index 0fb362d5a722..44b630cd1755 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Makefile +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile @@ -11,11 +11,12 @@ obj-$(CONFIG_DWMAC_IPQ806X) += dwmac-ipq806x.o obj-$(CONFIG_DWMAC_LPC18XX) += dwmac-lpc18xx.o obj-$(CONFIG_DWMAC_MESON) += dwmac-meson.o obj-$(CONFIG_DWMAC_ROCKCHIP) += dwmac-rk.o -obj-$(CONFIG_DWMAC_SOCFPGA) += dwmac-socfpga.o +obj-$(CONFIG_DWMAC_SOCFPGA) += dwmac-altr-socfpga.o obj-$(CONFIG_DWMAC_STI) += dwmac-sti.o obj-$(CONFIG_DWMAC_SUNXI) += dwmac-sunxi.o obj-$(CONFIG_DWMAC_GENERIC) += dwmac-generic.o stmmac-platform-objs:= stmmac_platform.o +dwmac-altr-socfpga-objs := altr_tse_pcs.o dwmac-socfpga.o obj-$(CONFIG_STMMAC_PCI) += stmmac-pci.o stmmac-pci-objs:= stmmac_pci.o diff --git a/drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.c b/drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.c new file mode 100644 index 000000000000..2920e2ee3864 --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.c @@ -0,0 +1,274 @@ +/* Copyright Altera Corporation (C) 2016. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author: Tien Hock Loh + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stmmac.h" +#include "stmmac_platform.h" +#include "altr_tse_pcs.h" + +#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII 0 +#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RGMII BIT(1) +#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RMII BIT(2) +#define SYSMGR_EMACGRP_CTRL_PHYSEL_WIDTH 2 +#define SYSMGR_EMACGRP_CTRL_PHYSEL_MASK GENMASK(1, 0) + +#define TSE_PCS_CONTROL_AN_EN_MASK BIT(12) +#define TSE_PCS_CONTROL_REG 0x00 +#define TSE_PCS_CONTROL_RESTART_AN_MASK BIT(9) +#define TSE_PCS_IF_MODE_REG 0x28 +#define TSE_PCS_LINK_TIMER_0_REG 0x24 +#define TSE_PCS_LINK_TIMER_1_REG 0x26 +#define TSE_PCS_SIZE 0x40 +#define TSE_PCS_STATUS_AN_COMPLETED_MASK BIT(5) +#define TSE_PCS_STATUS_LINK_MASK 0x0004 +#define TSE_PCS_STATUS_REG 0x02 +#define TSE_PCS_SGMII_SPEED_1000 BIT(3) +#define TSE_PCS_SGMII_SPEED_100 BIT(2) +#define TSE_PCS_SGMII_SPEED_10 0x0 +#define TSE_PCS_SW_RST_MASK 0x8000 +#define TSE_PCS_PARTNER_ABILITY_REG 0x0A +#define TSE_PCS_PARTNER_DUPLEX_FULL 0x1000 +#define TSE_PCS_PARTNER_DUPLEX_HALF 0x0000 +#define TSE_PCS_PARTNER_DUPLEX_MASK 0x1000 +#define TSE_PCS_PARTNER_SPEED_MASK GENMASK(11, 10) +#define TSE_PCS_PARTNER_SPEED_1000 BIT(11) +#define TSE_PCS_PARTNER_SPEED_100 BIT(10) +#define TSE_PCS_PARTNER_SPEED_10 0x0000 +#define TSE_PCS_PARTNER_SPEED_1000 BIT(11) +#define TSE_PCS_PARTNER_SPEED_100 BIT(10) +#define TSE_PCS_PARTNER_SPEED_10 0x0000 +#define TSE_PCS_SGMII_SPEED_MASK GENMASK(3, 2) +#define TSE_PCS_SGMII_LINK_TIMER_0 0x0D40 +#define TSE_PCS_SGMII_LINK_TIMER_1 0x0003 +#define TSE_PCS_SW_RESET_TIMEOUT 100 +#define TSE_PCS_USE_SGMII_AN_MASK BIT(2) +#define TSE_PCS_USE_SGMII_ENA BIT(1) + +#define SGMII_ADAPTER_CTRL_REG 0x00 +#define SGMII_ADAPTER_DISABLE 0x0001 +#define SGMII_ADAPTER_ENABLE 0x0000 + +#define AUTONEGO_LINK_TIMER 20 + +static int tse_pcs_reset(void __iomem *base, struct tse_pcs *pcs) +{ + int counter = 0; + u16 val; + + val = readw(base + TSE_PCS_CONTROL_REG); + val |= TSE_PCS_SW_RST_MASK; + writew(val, base + TSE_PCS_CONTROL_REG); + + while (counter < TSE_PCS_SW_RESET_TIMEOUT) { + val = readw(base + TSE_PCS_CONTROL_REG); + val &= TSE_PCS_SW_RST_MASK; + if (val == 0) + break; + counter++; + udelay(1); + } + if (counter >= TSE_PCS_SW_RESET_TIMEOUT) { + dev_err(pcs->dev, "PCS could not get out of sw reset\n"); + return -ETIMEDOUT; + } + + return 0; +} + +int tse_pcs_init(void __iomem *base, struct tse_pcs *pcs) +{ + int ret = 0; + + writew(TSE_PCS_USE_SGMII_ENA, base + TSE_PCS_IF_MODE_REG); + + writew(TSE_PCS_SGMII_LINK_TIMER_0, base + TSE_PCS_LINK_TIMER_0_REG); + writew(TSE_PCS_SGMII_LINK_TIMER_1, base + TSE_PCS_LINK_TIMER_1_REG); + + ret = tse_pcs_reset(base, pcs); + if (ret == 0) + writew(SGMII_ADAPTER_ENABLE, + pcs->sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG); + + return ret; +} + +static void pcs_link_timer_callback(unsigned long data) +{ + u16 val = 0; + struct tse_pcs *pcs = (struct tse_pcs *)data; + void __iomem *tse_pcs_base = pcs->tse_pcs_base; + void __iomem *sgmii_adapter_base = pcs->sgmii_adapter_base; + + val = readw(tse_pcs_base + TSE_PCS_STATUS_REG); + val &= TSE_PCS_STATUS_LINK_MASK; + + if (val != 0) { + dev_dbg(pcs->dev, "Adapter: Link is established\n"); + writew(SGMII_ADAPTER_ENABLE, + sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG); + } else { + mod_timer(&pcs->aneg_link_timer, jiffies + + msecs_to_jiffies(AUTONEGO_LINK_TIMER)); + } +} + +static void auto_nego_timer_callback(unsigned long data) +{ + u16 val = 0; + u16 speed = 0; + u16 duplex = 0; + struct tse_pcs *pcs = (struct tse_pcs *)data; + void __iomem *tse_pcs_base = pcs->tse_pcs_base; + void __iomem *sgmii_adapter_base = pcs->sgmii_adapter_base; + + val = readw(tse_pcs_base + TSE_PCS_STATUS_REG); + val &= TSE_PCS_STATUS_AN_COMPLETED_MASK; + + if (val != 0) { + dev_dbg(pcs->dev, "Adapter: Auto Negotiation is completed\n"); + val = readw(tse_pcs_base + TSE_PCS_PARTNER_ABILITY_REG); + speed = val & TSE_PCS_PARTNER_SPEED_MASK; + duplex = val & TSE_PCS_PARTNER_DUPLEX_MASK; + + if (speed == TSE_PCS_PARTNER_SPEED_10 && + duplex == TSE_PCS_PARTNER_DUPLEX_FULL) + dev_dbg(pcs->dev, + "Adapter: Link Partner is Up - 10/Full\n"); + else if (speed == TSE_PCS_PARTNER_SPEED_100 && + duplex == TSE_PCS_PARTNER_DUPLEX_FULL) + dev_dbg(pcs->dev, + "Adapter: Link Partner is Up - 100/Full\n"); + else if (speed == TSE_PCS_PARTNER_SPEED_1000 && + duplex == TSE_PCS_PARTNER_DUPLEX_FULL) + dev_dbg(pcs->dev, + "Adapter: Link Partner is Up - 1000/Full\n"); + else if (speed == TSE_PCS_PARTNER_SPEED_10 && + duplex == TSE_PCS_PARTNER_DUPLEX_HALF) + dev_err(pcs->dev, + "Adapter does not support Half Duplex\n"); + else if (speed == TSE_PCS_PARTNER_SPEED_100 && + duplex == TSE_PCS_PARTNER_DUPLEX_HALF) + dev_err(pcs->dev, + "Adapter does not support Half Duplex\n"); + else if (speed == TSE_PCS_PARTNER_SPEED_1000 && + duplex == TSE_PCS_PARTNER_DUPLEX_HALF) + dev_err(pcs->dev, + "Adapter does not support Half Duplex\n"); + else + dev_err(pcs->dev, + "Adapter: Invalid Partner Speed and Duplex\n"); + + if (duplex == TSE_PCS_PARTNER_DUPLEX_FULL && + (speed == TSE_PCS_PARTNER_SPEED_10 || + speed == TSE_PCS_PARTNER_SPEED_100 || + speed == TSE_PCS_PARTNER_SPEED_1000)) + writew(SGMII_ADAPTER_ENABLE, + sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG); + } else { + val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG); + val |= TSE_PCS_CONTROL_RESTART_AN_MASK; + writew(val, tse_pcs_base + TSE_PCS_CONTROL_REG); + + tse_pcs_reset(tse_pcs_base, pcs); + mod_timer(&pcs->aneg_link_timer, jiffies + + msecs_to_jiffies(AUTONEGO_LINK_TIMER)); + } +} + +static void aneg_link_timer_callback(unsigned long data) +{ + struct tse_pcs *pcs = (struct tse_pcs *)data; + + if (pcs->autoneg == AUTONEG_ENABLE) + auto_nego_timer_callback(data); + else if (pcs->autoneg == AUTONEG_DISABLE) + pcs_link_timer_callback(data); +} + +void tse_pcs_fix_mac_speed(struct tse_pcs *pcs, struct phy_device *phy_dev, + unsigned int speed) +{ + void __iomem *tse_pcs_base = pcs->tse_pcs_base; + void __iomem *sgmii_adapter_base = pcs->sgmii_adapter_base; + u32 val; + + writew(SGMII_ADAPTER_ENABLE, + sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG); + + pcs->autoneg = phy_dev->autoneg; + + if (phy_dev->autoneg == AUTONEG_ENABLE) { + val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG); + val |= TSE_PCS_CONTROL_AN_EN_MASK; + writew(val, tse_pcs_base + TSE_PCS_CONTROL_REG); + + val = readw(tse_pcs_base + TSE_PCS_IF_MODE_REG); + val |= TSE_PCS_USE_SGMII_AN_MASK; + writew(val, tse_pcs_base + TSE_PCS_IF_MODE_REG); + + val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG); + val |= TSE_PCS_CONTROL_RESTART_AN_MASK; + + tse_pcs_reset(tse_pcs_base, pcs); + + setup_timer(&pcs->aneg_link_timer, + aneg_link_timer_callback, (unsigned long)pcs); + mod_timer(&pcs->aneg_link_timer, jiffies + + msecs_to_jiffies(AUTONEGO_LINK_TIMER)); + } else if (phy_dev->autoneg == AUTONEG_DISABLE) { + val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG); + val &= ~TSE_PCS_CONTROL_AN_EN_MASK; + writew(val, tse_pcs_base + TSE_PCS_CONTROL_REG); + + val = readw(tse_pcs_base + TSE_PCS_IF_MODE_REG); + val &= ~TSE_PCS_USE_SGMII_AN_MASK; + writew(val, tse_pcs_base + TSE_PCS_IF_MODE_REG); + + val = readw(tse_pcs_base + TSE_PCS_IF_MODE_REG); + val &= ~TSE_PCS_SGMII_SPEED_MASK; + + switch (speed) { + case 1000: + val |= TSE_PCS_SGMII_SPEED_1000; + break; + case 100: + val |= TSE_PCS_SGMII_SPEED_100; + break; + case 10: + val |= TSE_PCS_SGMII_SPEED_10; + break; + default: + return; + } + writew(val, tse_pcs_base + TSE_PCS_IF_MODE_REG); + + tse_pcs_reset(tse_pcs_base, pcs); + + setup_timer(&pcs->aneg_link_timer, + aneg_link_timer_callback, (unsigned long)pcs); + mod_timer(&pcs->aneg_link_timer, jiffies + + msecs_to_jiffies(AUTONEGO_LINK_TIMER)); + } +} diff --git a/drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.h b/drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.h new file mode 100644 index 000000000000..2f5882450b06 --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.h @@ -0,0 +1,36 @@ +/* Copyright Altera Corporation (C) 2016. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author: Tien Hock Loh + */ + +#ifndef __TSE_PCS_H__ +#define __TSE_PCS_H__ + +#include +#include + +struct tse_pcs { + struct device *dev; + void __iomem *tse_pcs_base; + void __iomem *sgmii_adapter_base; + struct timer_list aneg_link_timer; + int autoneg; +}; + +int tse_pcs_init(void __iomem *base, struct tse_pcs *pcs); +void tse_pcs_fix_mac_speed(struct tse_pcs *pcs, struct phy_device *phy_dev, + unsigned int speed); + +#endif /* __TSE_PCS_H__ */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c index f13499fa1f58..4bee2f934a73 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c @@ -27,6 +27,11 @@ #include "stmmac.h" #include "stmmac_platform.h" +#include "altr_tse_pcs.h" + +#define SGMII_ADAPTER_CTRL_REG 0x00 +#define SGMII_ADAPTER_DISABLE 0x0001 + #define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII 0x0 #define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RGMII 0x1 #define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RMII 0x2 @@ -52,35 +57,46 @@ struct socfpga_dwmac { struct reset_control *stmmac_rst; void __iomem *splitter_base; bool f2h_ptp_ref_clk; + struct tse_pcs pcs; }; static void socfpga_dwmac_fix_mac_speed(void *priv, unsigned int speed) { struct socfpga_dwmac *dwmac = (struct socfpga_dwmac *)priv; void __iomem *splitter_base = dwmac->splitter_base; + void __iomem *tse_pcs_base = dwmac->pcs.tse_pcs_base; + void __iomem *sgmii_adapter_base = dwmac->pcs.sgmii_adapter_base; + struct device *dev = dwmac->dev; + struct net_device *ndev = dev_get_drvdata(dev); + struct phy_device *phy_dev = ndev->phydev; u32 val; - if (!splitter_base) - return; - - val = readl(splitter_base + EMAC_SPLITTER_CTRL_REG); - val &= ~EMAC_SPLITTER_CTRL_SPEED_MASK; - - switch (speed) { - case 1000: - val |= EMAC_SPLITTER_CTRL_SPEED_1000; - break; - case 100: - val |= EMAC_SPLITTER_CTRL_SPEED_100; - break; - case 10: - val |= EMAC_SPLITTER_CTRL_SPEED_10; - break; - default: - return; + if ((tse_pcs_base) && (sgmii_adapter_base)) + writew(SGMII_ADAPTER_DISABLE, + sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG); + + if (splitter_base) { + val = readl(splitter_base + EMAC_SPLITTER_CTRL_REG); + val &= ~EMAC_SPLITTER_CTRL_SPEED_MASK; + + switch (speed) { + case 1000: + val |= EMAC_SPLITTER_CTRL_SPEED_1000; + break; + case 100: + val |= EMAC_SPLITTER_CTRL_SPEED_100; + break; + case 10: + val |= EMAC_SPLITTER_CTRL_SPEED_10; + break; + default: + return; + } + writel(val, splitter_base + EMAC_SPLITTER_CTRL_REG); } - writel(val, splitter_base + EMAC_SPLITTER_CTRL_REG); + if (tse_pcs_base && sgmii_adapter_base) + tse_pcs_fix_mac_speed(&dwmac->pcs, phy_dev, speed); } static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device *dev) @@ -88,9 +104,12 @@ static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device * struct device_node *np = dev->of_node; struct regmap *sys_mgr_base_addr; u32 reg_offset, reg_shift; - int ret; - struct device_node *np_splitter; + int ret, index; + struct device_node *np_splitter = NULL; + struct device_node *np_sgmii_adapter = NULL; struct resource res_splitter; + struct resource res_tse_pcs; + struct resource res_sgmii_adapter; dwmac->interface = of_get_phy_mode(np); @@ -128,6 +147,77 @@ static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device * } } + np_sgmii_adapter = of_parse_phandle(np, + "altr,gmii-to-sgmii-converter", 0); + if (np_sgmii_adapter) { + index = of_property_match_string(np_sgmii_adapter, "reg-names", + "hps_emac_interface_splitter_avalon_slave"); + + if (index >= 0) { + if (of_address_to_resource(np_sgmii_adapter, index, + &res_splitter)) { + dev_err(dev, + "%s: ERROR: missing emac splitter address\n", + __func__); + return -EINVAL; + } + + dwmac->splitter_base = + devm_ioremap_resource(dev, &res_splitter); + + if (IS_ERR(dwmac->splitter_base)) { + dev_err(dev, + "%s: ERROR: failed mapping emac splitter\n", + __func__); + return PTR_ERR(dwmac->splitter_base); + } + } + + index = of_property_match_string(np_sgmii_adapter, "reg-names", + "gmii_to_sgmii_adapter_avalon_slave"); + + if (index >= 0) { + if (of_address_to_resource(np_sgmii_adapter, index, + &res_sgmii_adapter)) { + dev_err(dev, + "%s: ERROR: failed mapping adapter\n", + __func__); + return -EINVAL; + } + + dwmac->pcs.sgmii_adapter_base = + devm_ioremap_resource(dev, &res_sgmii_adapter); + + if (IS_ERR(dwmac->pcs.sgmii_adapter_base)) { + dev_err(dev, "%s: failed to mapping adapter\n", + __func__); + return PTR_ERR(dwmac->pcs.sgmii_adapter_base); + } + } + + index = of_property_match_string(np_sgmii_adapter, "reg-names", + "eth_tse_control_port"); + + if (index >= 0) { + if (of_address_to_resource(np_sgmii_adapter, index, + &res_tse_pcs)) { + dev_err(dev, + "%s: ERROR: failed mapping tse control port\n", + __func__); + return -EINVAL; + } + + dwmac->pcs.tse_pcs_base = + devm_ioremap_resource(dev, &res_tse_pcs); + + if (IS_ERR(dwmac->pcs.tse_pcs_base)) { + dev_err(dev, + "%s: ERROR: failed mapping tse control port\n", + __func__); + return PTR_ERR(dwmac->pcs.sgmii_adapter_base); + } + } + } dwmac->reg_offset = reg_offset; dwmac->reg_shift = reg_shift; dwmac->sys_mgr_base_addr = sys_mgr_base_addr; @@ -151,6 +241,7 @@ static int socfpga_dwmac_set_phy_mode(struct socfpga_dwmac *dwmac) break; case PHY_INTERFACE_MODE_MII: case PHY_INTERFACE_MODE_GMII: + case PHY_INTERFACE_MODE_SGMII: val = SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII; break; default: @@ -191,6 +282,12 @@ static int socfpga_dwmac_set_phy_mode(struct socfpga_dwmac *dwmac) */ if (dwmac->stmmac_rst) reset_control_deassert(dwmac->stmmac_rst); + if (phymode == PHY_INTERFACE_MODE_SGMII) { + if (tse_pcs_init(dwmac->pcs.tse_pcs_base, &dwmac->pcs) != 0) { + dev_err(dwmac->dev, "Unable to initialize TSE PCS"); + return -EINVAL; + } + } return 0; } @@ -225,6 +322,7 @@ static int socfpga_dwmac_probe(struct platform_device *pdev) plat_dat->fix_mac_speed = socfpga_dwmac_fix_mac_speed; ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); + if (!ret) { struct net_device *ndev = platform_get_drvdata(pdev); struct stmmac_priv *stpriv = netdev_priv(ndev); -- cgit v1.2.3 From 991a36bb464589f169d6c9e3d01f5b99299a73fa Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Fri, 8 Jul 2016 11:39:13 -0700 Subject: net: dsa: b53: Add support for BCM585xx/586xx/88312 integrated switch Update the SRAB, core driver and binding document to support the BCM585xx/586xx/88312 integrated switch (Northstar Plus SoCs family). Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/dsa/b53.txt | 9 +++++++++ drivers/net/dsa/b53/b53_common.c | 12 ++++++++++++ drivers/net/dsa/b53/b53_priv.h | 1 + drivers/net/dsa/b53/b53_srab.c | 8 ++++++++ 4 files changed, 30 insertions(+) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/dsa/b53.txt b/Documentation/devicetree/bindings/net/dsa/b53.txt index ca752db14dff..d6c6e41648d4 100644 --- a/Documentation/devicetree/bindings/net/dsa/b53.txt +++ b/Documentation/devicetree/bindings/net/dsa/b53.txt @@ -20,6 +20,15 @@ Required properties: "brcm,bcm53018-srab" "brcm,bcm53019-srab" and the mandatory "brcm,bcm5301x-srab" string + For the BCM585xx/586XX/88312 SoCs with an integrated switch, must be one of: + "brcm,bcm58522-srab" + "brcm,bcm58523-srab" + "brcm,bcm58525-srab" + "brcm,bcm58622-srab" + "brcm,bcm58623-srab" + "brcm,bcm58625-srab" + "brcm,bcm88312-srab" and the mandatory "brcm,nsp-srab string + For the BCM63xx/33xx SoCs with an integrated switch, must be one of: "brcm,bcm3384-switch" "brcm,bcm6328-switch" diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 444de66667b9..bda37d336736 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -1581,6 +1581,18 @@ static const struct b53_chip_data b53_switch_chips[] = { .jumbo_pm_reg = B53_JUMBO_PORT_MASK, .jumbo_size_reg = B53_JUMBO_MAX_SIZE, }, + { + .chip_id = BCM58XX_DEVICE_ID, + .dev_name = "BCM585xx/586xx/88312", + .vlans = 4096, + .enabled_ports = 0x1ff, + .arl_entries = 4, + .cpu_port = B53_CPU_PORT_25, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + }, }; static int b53_switch_init(struct b53_device *dev) diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index 5d8c602fb877..835a744f206e 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -59,6 +59,7 @@ enum { BCM53012_DEVICE_ID = 0x53012, BCM53018_DEVICE_ID = 0x53018, BCM53019_DEVICE_ID = 0x53019, + BCM58XX_DEVICE_ID = 0x5800, }; #define B53_N_PORTS 9 diff --git a/drivers/net/dsa/b53/b53_srab.c b/drivers/net/dsa/b53/b53_srab.c index de2b9e710041..2b304eaeb8e8 100644 --- a/drivers/net/dsa/b53/b53_srab.c +++ b/drivers/net/dsa/b53/b53_srab.c @@ -364,6 +364,14 @@ static const struct of_device_id b53_srab_of_match[] = { { .compatible = "brcm,bcm53018-srab" }, { .compatible = "brcm,bcm53019-srab" }, { .compatible = "brcm,bcm5301x-srab" }, + { .compatible = "brcm,bcm58522-srab", .data = (void *)BCM58XX_DEVICE_ID }, + { .compatible = "brcm,bcm58525-srab", .data = (void *)BCM58XX_DEVICE_ID }, + { .compatible = "brcm,bcm58535-srab", .data = (void *)BCM58XX_DEVICE_ID }, + { .compatible = "brcm,bcm58622-srab", .data = (void *)BCM58XX_DEVICE_ID }, + { .compatible = "brcm,bcm58623-srab", .data = (void *)BCM58XX_DEVICE_ID }, + { .compatible = "brcm,bcm58625-srab", .data = (void *)BCM58XX_DEVICE_ID }, + { .compatible = "brcm,bcm88312-srab", .data = (void *)BCM58XX_DEVICE_ID }, + { .compatible = "brcm,nsp-srab", .data = (void *)BCM58XX_DEVICE_ID }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, b53_srab_of_match); -- cgit v1.2.3 From 77501a79cec40eac65c59ee7af3f786c703ead9c Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 14 Jul 2016 16:29:43 +0200 Subject: net: phy: micrel: Add KSZ8041FTL fiber mode support We can't detect the FXEN (fiber mode) bootstrap pin, so configure it via a boolean device tree property "micrel,fiber-mode". If it is enabled, auto-negotiation is not supported. The only available modes are 100base-fx (full duplex and half duplex). Signed-off-by: Philipp Zabel Signed-off-by: David S. Miller --- Documentation/devicetree/bindings/net/micrel.txt | 10 +++++++ drivers/net/phy/micrel.c | 34 ++++++++++++++++++++++-- include/linux/micrel_phy.h | 1 + 3 files changed, 43 insertions(+), 2 deletions(-) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/micrel.txt b/Documentation/devicetree/bindings/net/micrel.txt index 87496a8c64ab..8d157f0295a5 100644 --- a/Documentation/devicetree/bindings/net/micrel.txt +++ b/Documentation/devicetree/bindings/net/micrel.txt @@ -35,3 +35,13 @@ Optional properties: supported clocks: - KSZ8021, KSZ8031, KSZ8081, KSZ8091: "rmii-ref": The RMII reference input clock. Used to determine the XI input clock. + + - micrel,fiber-mode: If present the PHY is configured to operate in fiber mode + + Some PHYs, such as the KSZ8041FTL variant, support fiber mode, enabled + by the FXEN boot strapping pin. It can't be determined from the PHY + registers whether the PHY is in fiber mode, so this boolean device tree + property can be used to describe it. + + In fiber mode, auto-negotiation is disabled and the PHY can only work in + 100base-fx (full and half duplex) modes. diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 5a8fefc25157..059f13b60fe0 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -311,6 +311,36 @@ static int kszphy_config_init(struct phy_device *phydev) return 0; } +static int ksz8041_config_init(struct phy_device *phydev) +{ + struct device_node *of_node = phydev->mdio.dev.of_node; + + /* Limit supported and advertised modes in fiber mode */ + if (of_property_read_bool(of_node, "micrel,fiber-mode")) { + phydev->dev_flags |= MICREL_PHY_FXEN; + phydev->supported &= SUPPORTED_FIBRE | + SUPPORTED_100baseT_Full | + SUPPORTED_100baseT_Half; + phydev->advertising &= ADVERTISED_FIBRE | + ADVERTISED_100baseT_Full | + ADVERTISED_100baseT_Half; + phydev->autoneg = AUTONEG_DISABLE; + } + + return kszphy_config_init(phydev); +} + +static int ksz8041_config_aneg(struct phy_device *phydev) +{ + /* Skip auto-negotiation in fiber mode */ + if (phydev->dev_flags & MICREL_PHY_FXEN) { + phydev->speed = SPEED_100; + return 0; + } + + return genphy_config_aneg(phydev); +} + static int ksz9021_load_values_from_of(struct phy_device *phydev, const struct device_node *of_node, u16 reg, @@ -788,8 +818,8 @@ static struct phy_driver ksphy_driver[] = { .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, .driver_data = &ksz8041_type, .probe = kszphy_probe, - .config_init = kszphy_config_init, - .config_aneg = genphy_config_aneg, + .config_init = ksz8041_config_init, + .config_aneg = ksz8041_config_aneg, .read_status = genphy_read_status, .ack_interrupt = kszphy_ack_interrupt, .config_intr = kszphy_config_intr, diff --git a/include/linux/micrel_phy.h b/include/linux/micrel_phy.h index 2e5b194b9b19..257173e0095e 100644 --- a/include/linux/micrel_phy.h +++ b/include/linux/micrel_phy.h @@ -37,6 +37,7 @@ /* struct phy_device dev_flags definitions */ #define MICREL_PHY_50MHZ_CLK 0x00000001 +#define MICREL_PHY_FXEN 0x00000002 #define MICREL_KSZ9021_EXTREG_CTRL 0xB #define MICREL_KSZ9021_EXTREG_DATA_WRITE 0xC -- cgit v1.2.3 From 4960e4b1e69908ee70c716755a9415079e0d554f Mon Sep 17 00:00:00 2001 From: Dongpo Li Date: Fri, 15 Jul 2016 16:26:33 +0800 Subject: net: Add MDIO bus driver for the Hisilicon FEMAC This patch adds a separate driver for the MDIO interface of the Hisilicon Fast Ethernet MAC. Signed-off-by: Dongpo Li Reviewed-by: Jiancheng Xue Acked-by: Rob Herring Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- .../bindings/net/hisilicon-femac-mdio.txt | 22 +++ drivers/net/phy/Kconfig | 7 + drivers/net/phy/Makefile | 1 + drivers/net/phy/mdio-hisi-femac.c | 166 +++++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/hisilicon-femac-mdio.txt create mode 100644 drivers/net/phy/mdio-hisi-femac.c (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/hisilicon-femac-mdio.txt b/Documentation/devicetree/bindings/net/hisilicon-femac-mdio.txt new file mode 100644 index 000000000000..23a39a309d17 --- /dev/null +++ b/Documentation/devicetree/bindings/net/hisilicon-femac-mdio.txt @@ -0,0 +1,22 @@ +Hisilicon Fast Ethernet MDIO Controller interface + +Required properties: +- compatible: should be "hisilicon,hisi-femac-mdio". +- reg: address and length of the register set for the device. +- clocks: A phandle to the reference clock for this device. + +- PHY subnode: inherits from phy binding [1] +[1] Documentation/devicetree/bindings/net/phy.txt + +Example: +mdio: mdio@10091100 { + compatible = "hisilicon,hisi-femac-mdio"; + reg = <0x10091100 0x10>; + clocks = <&crg HI3516CV300_MDIO_CLK>; + #address-cells = <1>; + #size-cells = <0>; + + phy0: phy@1 { + reg = <1>; + }; +}; diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index f96829415ce6..1d7b208b1629 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -294,6 +294,13 @@ config INTEL_XWAY_PHY PEF 7061, PEF 7071 and PEF 7072 or integrated into the Intel SoCs xRX200, xRX300, xRX330, xRX350 and xRX550. +config MDIO_HISI_FEMAC + tristate "Hisilicon FEMAC MDIO bus controller" + depends on HAS_IOMEM && OF_MDIO + help + This module provides a driver for the MDIO busses found in the + Hisilicon SoC that have an Fast Ethernet MAC. + endif # PHYLIB config MICREL_KS8995MA diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 7158274327d0..19e38a97963a 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -47,3 +47,4 @@ obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o obj-$(CONFIG_MICROCHIP_PHY) += microchip.o obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o obj-$(CONFIG_INTEL_XWAY_PHY) += intel-xway.o +obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o diff --git a/drivers/net/phy/mdio-hisi-femac.c b/drivers/net/phy/mdio-hisi-femac.c new file mode 100644 index 000000000000..b03fedd6c1d8 --- /dev/null +++ b/drivers/net/phy/mdio-hisi-femac.c @@ -0,0 +1,166 @@ +/* + * Hisilicon Fast Ethernet MDIO Bus Driver + * + * Copyright (c) 2016 HiSilicon Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MDIO_RWCTRL 0x00 +#define MDIO_RO_DATA 0x04 +#define MDIO_WRITE BIT(13) +#define MDIO_RW_FINISH BIT(15) +#define BIT_PHY_ADDR_OFFSET 8 +#define BIT_WR_DATA_OFFSET 16 + +struct hisi_femac_mdio_data { + struct clk *clk; + void __iomem *membase; +}; + +static int hisi_femac_mdio_wait_ready(struct hisi_femac_mdio_data *data) +{ + u32 val; + + return readl_poll_timeout(data->membase + MDIO_RWCTRL, + val, val & MDIO_RW_FINISH, 20, 10000); +} + +static int hisi_femac_mdio_read(struct mii_bus *bus, int mii_id, int regnum) +{ + struct hisi_femac_mdio_data *data = bus->priv; + int ret; + + ret = hisi_femac_mdio_wait_ready(data); + if (ret) + return ret; + + writel((mii_id << BIT_PHY_ADDR_OFFSET) | regnum, + data->membase + MDIO_RWCTRL); + + ret = hisi_femac_mdio_wait_ready(data); + if (ret) + return ret; + + return readl(data->membase + MDIO_RO_DATA) & 0xFFFF; +} + +static int hisi_femac_mdio_write(struct mii_bus *bus, int mii_id, int regnum, + u16 value) +{ + struct hisi_femac_mdio_data *data = bus->priv; + int ret; + + ret = hisi_femac_mdio_wait_ready(data); + if (ret) + return ret; + + writel(MDIO_WRITE | (value << BIT_WR_DATA_OFFSET) | + (mii_id << BIT_PHY_ADDR_OFFSET) | regnum, + data->membase + MDIO_RWCTRL); + + return hisi_femac_mdio_wait_ready(data); +} + +static int hisi_femac_mdio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mii_bus *bus; + struct hisi_femac_mdio_data *data; + struct resource *res; + int ret; + + bus = mdiobus_alloc_size(sizeof(*data)); + if (!bus) + return -ENOMEM; + + bus->name = "hisi_femac_mii_bus"; + bus->read = &hisi_femac_mdio_read; + bus->write = &hisi_femac_mdio_write; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name); + bus->parent = &pdev->dev; + + data = bus->priv; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->membase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->membase)) { + ret = PTR_ERR(data->membase); + goto err_out_free_mdiobus; + } + + data->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(data->clk)) { + ret = PTR_ERR(data->clk); + goto err_out_free_mdiobus; + } + + ret = clk_prepare_enable(data->clk); + if (ret) + goto err_out_free_mdiobus; + + ret = of_mdiobus_register(bus, np); + if (ret) + goto err_out_disable_clk; + + platform_set_drvdata(pdev, bus); + + return 0; + +err_out_disable_clk: + clk_disable_unprepare(data->clk); +err_out_free_mdiobus: + mdiobus_free(bus); + return ret; +} + +static int hisi_femac_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + struct hisi_femac_mdio_data *data = bus->priv; + + mdiobus_unregister(bus); + clk_disable_unprepare(data->clk); + mdiobus_free(bus); + + return 0; +} + +static const struct of_device_id hisi_femac_mdio_dt_ids[] = { + { .compatible = "hisilicon,hisi-femac-mdio" }, + { } +}; +MODULE_DEVICE_TABLE(of, hisi_femac_mdio_dt_ids); + +static struct platform_driver hisi_femac_mdio_driver = { + .probe = hisi_femac_mdio_probe, + .remove = hisi_femac_mdio_remove, + .driver = { + .name = "hisi-femac-mdio", + .of_match_table = hisi_femac_mdio_dt_ids, + }, +}; + +module_platform_driver(hisi_femac_mdio_driver); + +MODULE_DESCRIPTION("Hisilicon Fast Ethernet MAC MDIO interface driver"); +MODULE_AUTHOR("Dongpo Li "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 542ae60af24f02e130e62cb3b7c23163a2350056 Mon Sep 17 00:00:00 2001 From: Dongpo Li Date: Fri, 15 Jul 2016 16:26:35 +0800 Subject: net: hisilicon: Add Fast Ethernet MAC driver This patch adds the Hisilicon Fast Ethernet MAC(FEMAC) driver. The FEMAC supports max speed 100Mbps and has been used in many Hisilicon SoC. Signed-off-by: Dongpo Li Reviewed-by: Jiancheng Xue Signed-off-by: David S. Miller --- .../devicetree/bindings/net/hisilicon-femac.txt | 39 + drivers/net/ethernet/hisilicon/Kconfig | 12 + drivers/net/ethernet/hisilicon/Makefile | 1 + drivers/net/ethernet/hisilicon/hisi_femac.c | 1007 ++++++++++++++++++++ 4 files changed, 1059 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/hisilicon-femac.txt create mode 100644 drivers/net/ethernet/hisilicon/hisi_femac.c (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/hisilicon-femac.txt b/Documentation/devicetree/bindings/net/hisilicon-femac.txt new file mode 100644 index 000000000000..d11af5ecace8 --- /dev/null +++ b/Documentation/devicetree/bindings/net/hisilicon-femac.txt @@ -0,0 +1,39 @@ +Hisilicon Fast Ethernet MAC controller + +Required properties: +- compatible: should contain one of the following version strings: + * "hisilicon,hisi-femac-v1" + * "hisilicon,hisi-femac-v2" + and the soc string "hisilicon,hi3516cv300-femac". +- reg: specifies base physical address(s) and size of the device registers. + The first region is the MAC core register base and size. + The second region is the global MAC control register. +- interrupts: should contain the MAC interrupt. +- clocks: A phandle to the MAC main clock. +- resets: should contain the phandle to the MAC reset signal(required) and + the PHY reset signal(optional). +- reset-names: should contain the reset signal name "mac"(required) + and "phy"(optional). +- mac-address: see ethernet.txt [1]. +- phy-mode: see ethernet.txt [1]. +- phy-handle: see ethernet.txt [1]. +- hisilicon,phy-reset-delays-us: triplet of delays if PHY reset signal given. + 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. + +[1] Documentation/devicetree/bindings/net/ethernet.txt + +Example: + hisi_femac: ethernet@10090000 { + compatible = "hisilicon,hi3516cv300-femac","hisilicon,hisi-femac-v2"; + reg = <0x10090000 0x1000>,<0x10091300 0x200>; + interrupts = <12>; + clocks = <&crg HI3518EV200_ETH_CLK>; + resets = <&crg 0xec 0>,<&crg 0xec 3>; + reset-names = "mac","phy"; + mac-address = [00 00 00 00 00 00]; + phy-mode = "mii"; + phy-handle = <&phy0>; + hisilicon,phy-reset-delays-us = <10000 20000 20000>; + }; diff --git a/drivers/net/ethernet/hisilicon/Kconfig b/drivers/net/ethernet/hisilicon/Kconfig index 2e2566230e27..d11287e11371 100644 --- a/drivers/net/ethernet/hisilicon/Kconfig +++ b/drivers/net/ethernet/hisilicon/Kconfig @@ -23,6 +23,18 @@ config HIX5HD2_GMAC help This selects the hix5hd2 mac family network device. +config HISI_FEMAC + tristate "Hisilicon Fast Ethernet MAC device support" + depends on HAS_IOMEM + select PHYLIB + select RESET_CONTROLLER + help + This selects the Hisilicon Fast Ethernet MAC device(FEMAC). + The FEMAC receives and transmits data over Ethernet + ports at 10/100 Mbps in full-duplex or half-duplex mode. + The FEMAC exchanges data with the CPU, and supports + the energy efficient Ethernet (EEE). + config HIP04_ETH tristate "HISILICON P04 Ethernet support" depends on HAS_IOMEM # For MFD_SYSCON diff --git a/drivers/net/ethernet/hisilicon/Makefile b/drivers/net/ethernet/hisilicon/Makefile index 390b71fb3000..8661695024dc 100644 --- a/drivers/net/ethernet/hisilicon/Makefile +++ b/drivers/net/ethernet/hisilicon/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_HIX5HD2_GMAC) += hix5hd2_gmac.o obj-$(CONFIG_HIP04_ETH) += hip04_eth.o obj-$(CONFIG_HNS_MDIO) += hns_mdio.o obj-$(CONFIG_HNS) += hns/ +obj-$(CONFIG_HISI_FEMAC) += hisi_femac.o diff --git a/drivers/net/ethernet/hisilicon/hisi_femac.c b/drivers/net/ethernet/hisilicon/hisi_femac.c new file mode 100644 index 000000000000..b5d7ad0252a0 --- /dev/null +++ b/drivers/net/ethernet/hisilicon/hisi_femac.c @@ -0,0 +1,1007 @@ +/* + * Hisilicon Fast Ethernet MAC Driver + * + * Copyright (c) 2016 HiSilicon Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* MAC control register list */ +#define MAC_PORTSEL 0x0200 +#define MAC_PORTSEL_STAT_CPU BIT(0) +#define MAC_PORTSEL_RMII BIT(1) +#define MAC_PORTSET 0x0208 +#define MAC_PORTSET_DUPLEX_FULL BIT(0) +#define MAC_PORTSET_LINKED BIT(1) +#define MAC_PORTSET_SPEED_100M BIT(2) +#define MAC_SET 0x0210 +#define MAX_FRAME_SIZE 1600 +#define MAX_FRAME_SIZE_MASK GENMASK(10, 0) +#define BIT_PAUSE_EN BIT(18) +#define RX_COALESCE_SET 0x0340 +#define RX_COALESCED_FRAME_OFFSET 24 +#define RX_COALESCED_FRAMES 8 +#define RX_COALESCED_TIMER 0x74 +#define QLEN_SET 0x0344 +#define RX_DEPTH_OFFSET 8 +#define MAX_HW_FIFO_DEPTH 64 +#define HW_TX_FIFO_DEPTH 12 +#define HW_RX_FIFO_DEPTH (MAX_HW_FIFO_DEPTH - HW_TX_FIFO_DEPTH) +#define IQFRM_DES 0x0354 +#define RX_FRAME_LEN_MASK GENMASK(11, 0) +#define IQ_ADDR 0x0358 +#define EQ_ADDR 0x0360 +#define EQFRM_LEN 0x0364 +#define ADDRQ_STAT 0x036C +#define TX_CNT_INUSE_MASK GENMASK(5, 0) +#define BIT_TX_READY BIT(24) +#define BIT_RX_READY BIT(25) +/* global control register list */ +#define GLB_HOSTMAC_L32 0x0000 +#define GLB_HOSTMAC_H16 0x0004 +#define GLB_SOFT_RESET 0x0008 +#define SOFT_RESET_ALL BIT(0) +#define GLB_FWCTRL 0x0010 +#define FWCTRL_VLAN_ENABLE BIT(0) +#define FWCTRL_FW2CPU_ENA BIT(5) +#define FWCTRL_FWALL2CPU BIT(7) +#define GLB_MACTCTRL 0x0014 +#define MACTCTRL_UNI2CPU BIT(1) +#define MACTCTRL_MULTI2CPU BIT(3) +#define MACTCTRL_BROAD2CPU BIT(5) +#define MACTCTRL_MACT_ENA BIT(7) +#define GLB_IRQ_STAT 0x0030 +#define GLB_IRQ_ENA 0x0034 +#define IRQ_ENA_PORT0_MASK GENMASK(7, 0) +#define IRQ_ENA_PORT0 BIT(18) +#define IRQ_ENA_ALL BIT(19) +#define GLB_IRQ_RAW 0x0038 +#define IRQ_INT_RX_RDY BIT(0) +#define IRQ_INT_TX_PER_PACKET BIT(1) +#define IRQ_INT_TX_FIFO_EMPTY BIT(6) +#define IRQ_INT_MULTI_RXRDY BIT(7) +#define DEF_INT_MASK (IRQ_INT_MULTI_RXRDY | \ + IRQ_INT_TX_PER_PACKET | \ + IRQ_INT_TX_FIFO_EMPTY) +#define GLB_MAC_L32_BASE 0x0100 +#define GLB_MAC_H16_BASE 0x0104 +#define MACFLT_HI16_MASK GENMASK(15, 0) +#define BIT_MACFLT_ENA BIT(17) +#define BIT_MACFLT_FW2CPU BIT(21) +#define GLB_MAC_H16(reg) (GLB_MAC_H16_BASE + ((reg) * 0x8)) +#define GLB_MAC_L32(reg) (GLB_MAC_L32_BASE + ((reg) * 0x8)) +#define MAX_MAC_FILTER_NUM 8 +#define MAX_UNICAST_ADDRESSES 2 +#define MAX_MULTICAST_ADDRESSES (MAX_MAC_FILTER_NUM - \ + MAX_UNICAST_ADDRESSES) +/* software tx and rx queue number, should be power of 2 */ +#define TXQ_NUM 64 +#define RXQ_NUM 128 +#define FEMAC_POLL_WEIGHT 16 + +#define PHY_RESET_DELAYS_PROPERTY "hisilicon,phy-reset-delays-us" + +enum phy_reset_delays { + PRE_DELAY, + PULSE, + POST_DELAY, + DELAYS_NUM, +}; + +struct hisi_femac_queue { + struct sk_buff **skb; + dma_addr_t *dma_phys; + int num; + unsigned int head; + unsigned int tail; +}; + +struct hisi_femac_priv { + void __iomem *port_base; + void __iomem *glb_base; + struct clk *clk; + struct reset_control *mac_rst; + struct reset_control *phy_rst; + u32 phy_reset_delays[DELAYS_NUM]; + u32 link_status; + + struct device *dev; + struct net_device *ndev; + + struct hisi_femac_queue txq; + struct hisi_femac_queue rxq; + u32 tx_fifo_used_cnt; + struct napi_struct napi; +}; + +static void hisi_femac_irq_enable(struct hisi_femac_priv *priv, int irqs) +{ + u32 val; + + val = readl(priv->glb_base + GLB_IRQ_ENA); + writel(val | irqs, priv->glb_base + GLB_IRQ_ENA); +} + +static void hisi_femac_irq_disable(struct hisi_femac_priv *priv, int irqs) +{ + u32 val; + + val = readl(priv->glb_base + GLB_IRQ_ENA); + writel(val & (~irqs), priv->glb_base + GLB_IRQ_ENA); +} + +static void hisi_femac_tx_dma_unmap(struct hisi_femac_priv *priv, + struct sk_buff *skb, unsigned int pos) +{ + dma_addr_t dma_addr; + + dma_addr = priv->txq.dma_phys[pos]; + dma_unmap_single(priv->dev, dma_addr, skb->len, DMA_TO_DEVICE); +} + +static void hisi_femac_xmit_reclaim(struct net_device *dev) +{ + struct sk_buff *skb; + struct hisi_femac_priv *priv = netdev_priv(dev); + struct hisi_femac_queue *txq = &priv->txq; + unsigned int bytes_compl = 0, pkts_compl = 0; + u32 val; + + netif_tx_lock(dev); + + val = readl(priv->port_base + ADDRQ_STAT) & TX_CNT_INUSE_MASK; + while (val < priv->tx_fifo_used_cnt) { + skb = txq->skb[txq->tail]; + if (unlikely(!skb)) { + netdev_err(dev, "xmitq_cnt_inuse=%d, tx_fifo_used=%d\n", + val, priv->tx_fifo_used_cnt); + break; + } + hisi_femac_tx_dma_unmap(priv, skb, txq->tail); + pkts_compl++; + bytes_compl += skb->len; + dev_kfree_skb_any(skb); + + priv->tx_fifo_used_cnt--; + + val = readl(priv->port_base + ADDRQ_STAT) & TX_CNT_INUSE_MASK; + txq->skb[txq->tail] = NULL; + txq->tail = (txq->tail + 1) % txq->num; + } + + netdev_completed_queue(dev, pkts_compl, bytes_compl); + + if (unlikely(netif_queue_stopped(dev)) && pkts_compl) + netif_wake_queue(dev); + + netif_tx_unlock(dev); +} + +static void hisi_femac_adjust_link(struct net_device *dev) +{ + struct hisi_femac_priv *priv = netdev_priv(dev); + struct phy_device *phy = dev->phydev; + u32 status = 0; + + if (phy->link) + status |= MAC_PORTSET_LINKED; + if (phy->duplex == DUPLEX_FULL) + status |= MAC_PORTSET_DUPLEX_FULL; + if (phy->speed == SPEED_100) + status |= MAC_PORTSET_SPEED_100M; + + if ((status != priv->link_status) && + ((status | priv->link_status) & MAC_PORTSET_LINKED)) { + writel(status, priv->port_base + MAC_PORTSET); + priv->link_status = status; + phy_print_status(phy); + } +} + +static void hisi_femac_rx_refill(struct hisi_femac_priv *priv) +{ + struct hisi_femac_queue *rxq = &priv->rxq; + struct sk_buff *skb; + u32 pos; + u32 len = MAX_FRAME_SIZE; + dma_addr_t addr; + + pos = rxq->head; + while (readl(priv->port_base + ADDRQ_STAT) & BIT_RX_READY) { + if (!CIRC_SPACE(pos, rxq->tail, rxq->num)) + break; + if (unlikely(rxq->skb[pos])) { + netdev_err(priv->ndev, "err skb[%d]=%p\n", + pos, rxq->skb[pos]); + break; + } + skb = netdev_alloc_skb_ip_align(priv->ndev, len); + if (unlikely(!skb)) + break; + + addr = dma_map_single(priv->dev, skb->data, len, + DMA_FROM_DEVICE); + if (dma_mapping_error(priv->dev, addr)) { + dev_kfree_skb_any(skb); + break; + } + rxq->dma_phys[pos] = addr; + rxq->skb[pos] = skb; + writel(addr, priv->port_base + IQ_ADDR); + pos = (pos + 1) % rxq->num; + } + rxq->head = pos; +} + +static int hisi_femac_rx(struct net_device *dev, int limit) +{ + struct hisi_femac_priv *priv = netdev_priv(dev); + struct hisi_femac_queue *rxq = &priv->rxq; + struct sk_buff *skb; + dma_addr_t addr; + u32 rx_pkt_info, pos, len, rx_pkts_num = 0; + + pos = rxq->tail; + while (readl(priv->glb_base + GLB_IRQ_RAW) & IRQ_INT_RX_RDY) { + rx_pkt_info = readl(priv->port_base + IQFRM_DES); + len = rx_pkt_info & RX_FRAME_LEN_MASK; + len -= ETH_FCS_LEN; + + /* tell hardware we will deal with this packet */ + writel(IRQ_INT_RX_RDY, priv->glb_base + GLB_IRQ_RAW); + + rx_pkts_num++; + + skb = rxq->skb[pos]; + if (unlikely(!skb)) { + netdev_err(dev, "rx skb NULL. pos=%d\n", pos); + break; + } + rxq->skb[pos] = NULL; + + addr = rxq->dma_phys[pos]; + dma_unmap_single(priv->dev, addr, MAX_FRAME_SIZE, + DMA_FROM_DEVICE); + skb_put(skb, len); + if (unlikely(skb->len > MAX_FRAME_SIZE)) { + netdev_err(dev, "rcv len err, len = %d\n", skb->len); + dev->stats.rx_errors++; + dev->stats.rx_length_errors++; + dev_kfree_skb_any(skb); + goto next; + } + + skb->protocol = eth_type_trans(skb, dev); + napi_gro_receive(&priv->napi, skb); + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; +next: + pos = (pos + 1) % rxq->num; + if (rx_pkts_num >= limit) + break; + } + rxq->tail = pos; + + hisi_femac_rx_refill(priv); + + return rx_pkts_num; +} + +static int hisi_femac_poll(struct napi_struct *napi, int budget) +{ + struct hisi_femac_priv *priv = container_of(napi, + struct hisi_femac_priv, napi); + struct net_device *dev = priv->ndev; + int work_done = 0, task = budget; + int ints, num; + + do { + hisi_femac_xmit_reclaim(dev); + num = hisi_femac_rx(dev, task); + work_done += num; + task -= num; + if (work_done >= budget) + break; + + ints = readl(priv->glb_base + GLB_IRQ_RAW); + writel(ints & DEF_INT_MASK, + priv->glb_base + GLB_IRQ_RAW); + } while (ints & DEF_INT_MASK); + + if (work_done < budget) { + napi_complete(napi); + hisi_femac_irq_enable(priv, DEF_INT_MASK & + (~IRQ_INT_TX_PER_PACKET)); + } + + return work_done; +} + +static irqreturn_t hisi_femac_interrupt(int irq, void *dev_id) +{ + int ints; + struct net_device *dev = (struct net_device *)dev_id; + struct hisi_femac_priv *priv = netdev_priv(dev); + + ints = readl(priv->glb_base + GLB_IRQ_RAW); + + if (likely(ints & DEF_INT_MASK)) { + writel(ints & DEF_INT_MASK, + priv->glb_base + GLB_IRQ_RAW); + hisi_femac_irq_disable(priv, DEF_INT_MASK); + napi_schedule(&priv->napi); + } + + return IRQ_HANDLED; +} + +static int hisi_femac_init_queue(struct device *dev, + struct hisi_femac_queue *queue, + unsigned int num) +{ + queue->skb = devm_kcalloc(dev, num, sizeof(struct sk_buff *), + GFP_KERNEL); + if (!queue->skb) + return -ENOMEM; + + queue->dma_phys = devm_kcalloc(dev, num, sizeof(dma_addr_t), + GFP_KERNEL); + if (!queue->dma_phys) + return -ENOMEM; + + queue->num = num; + queue->head = 0; + queue->tail = 0; + + return 0; +} + +static int hisi_femac_init_tx_and_rx_queues(struct hisi_femac_priv *priv) +{ + int ret; + + ret = hisi_femac_init_queue(priv->dev, &priv->txq, TXQ_NUM); + if (ret) + return ret; + + ret = hisi_femac_init_queue(priv->dev, &priv->rxq, RXQ_NUM); + if (ret) + return ret; + + priv->tx_fifo_used_cnt = 0; + + return 0; +} + +static void hisi_femac_free_skb_rings(struct hisi_femac_priv *priv) +{ + struct hisi_femac_queue *txq = &priv->txq; + struct hisi_femac_queue *rxq = &priv->rxq; + struct sk_buff *skb; + dma_addr_t dma_addr; + u32 pos; + + pos = rxq->tail; + while (pos != rxq->head) { + skb = rxq->skb[pos]; + if (unlikely(!skb)) { + netdev_err(priv->ndev, "NULL rx skb. pos=%d, head=%d\n", + pos, rxq->head); + continue; + } + + dma_addr = rxq->dma_phys[pos]; + dma_unmap_single(priv->dev, dma_addr, MAX_FRAME_SIZE, + DMA_FROM_DEVICE); + + dev_kfree_skb_any(skb); + rxq->skb[pos] = NULL; + pos = (pos + 1) % rxq->num; + } + rxq->tail = pos; + + pos = txq->tail; + while (pos != txq->head) { + skb = txq->skb[pos]; + if (unlikely(!skb)) { + netdev_err(priv->ndev, "NULL tx skb. pos=%d, head=%d\n", + pos, txq->head); + continue; + } + hisi_femac_tx_dma_unmap(priv, skb, pos); + dev_kfree_skb_any(skb); + txq->skb[pos] = NULL; + pos = (pos + 1) % txq->num; + } + txq->tail = pos; + priv->tx_fifo_used_cnt = 0; +} + +static int hisi_femac_set_hw_mac_addr(struct hisi_femac_priv *priv, + unsigned char *mac) +{ + u32 reg; + + reg = mac[1] | (mac[0] << 8); + writel(reg, priv->glb_base + GLB_HOSTMAC_H16); + + reg = mac[5] | (mac[4] << 8) | (mac[3] << 16) | (mac[2] << 24); + writel(reg, priv->glb_base + GLB_HOSTMAC_L32); + + return 0; +} + +static int hisi_femac_port_reset(struct hisi_femac_priv *priv) +{ + u32 val; + + val = readl(priv->glb_base + GLB_SOFT_RESET); + val |= SOFT_RESET_ALL; + writel(val, priv->glb_base + GLB_SOFT_RESET); + + usleep_range(500, 800); + + val &= ~SOFT_RESET_ALL; + writel(val, priv->glb_base + GLB_SOFT_RESET); + + return 0; +} + +static int hisi_femac_net_open(struct net_device *dev) +{ + struct hisi_femac_priv *priv = netdev_priv(dev); + + hisi_femac_port_reset(priv); + hisi_femac_set_hw_mac_addr(priv, dev->dev_addr); + hisi_femac_rx_refill(priv); + + netif_carrier_off(dev); + netdev_reset_queue(dev); + netif_start_queue(dev); + napi_enable(&priv->napi); + + priv->link_status = 0; + if (dev->phydev) + phy_start(dev->phydev); + + writel(IRQ_ENA_PORT0_MASK, priv->glb_base + GLB_IRQ_RAW); + hisi_femac_irq_enable(priv, IRQ_ENA_ALL | IRQ_ENA_PORT0 | DEF_INT_MASK); + + return 0; +} + +static int hisi_femac_net_close(struct net_device *dev) +{ + struct hisi_femac_priv *priv = netdev_priv(dev); + + hisi_femac_irq_disable(priv, IRQ_ENA_PORT0); + + if (dev->phydev) + phy_stop(dev->phydev); + + netif_stop_queue(dev); + napi_disable(&priv->napi); + + hisi_femac_free_skb_rings(priv); + + return 0; +} + +static netdev_tx_t hisi_femac_net_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct hisi_femac_priv *priv = netdev_priv(dev); + struct hisi_femac_queue *txq = &priv->txq; + dma_addr_t addr; + u32 val; + + val = readl(priv->port_base + ADDRQ_STAT); + val &= BIT_TX_READY; + if (!val) { + hisi_femac_irq_enable(priv, IRQ_INT_TX_PER_PACKET); + dev->stats.tx_dropped++; + dev->stats.tx_fifo_errors++; + netif_stop_queue(dev); + return NETDEV_TX_BUSY; + } + + if (unlikely(!CIRC_SPACE(txq->head, txq->tail, + txq->num))) { + hisi_femac_irq_enable(priv, IRQ_INT_TX_PER_PACKET); + dev->stats.tx_dropped++; + dev->stats.tx_fifo_errors++; + netif_stop_queue(dev); + return NETDEV_TX_BUSY; + } + + addr = dma_map_single(priv->dev, skb->data, + skb->len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(priv->dev, addr))) { + dev_kfree_skb_any(skb); + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + txq->dma_phys[txq->head] = addr; + + txq->skb[txq->head] = skb; + txq->head = (txq->head + 1) % txq->num; + + writel(addr, priv->port_base + EQ_ADDR); + writel(skb->len + ETH_FCS_LEN, priv->port_base + EQFRM_LEN); + + priv->tx_fifo_used_cnt++; + + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + netdev_sent_queue(dev, skb->len); + + return NETDEV_TX_OK; +} + +static int hisi_femac_set_mac_address(struct net_device *dev, void *p) +{ + struct hisi_femac_priv *priv = netdev_priv(dev); + struct sockaddr *skaddr = p; + + if (!is_valid_ether_addr(skaddr->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(dev->dev_addr, skaddr->sa_data, dev->addr_len); + dev->addr_assign_type &= ~NET_ADDR_RANDOM; + + hisi_femac_set_hw_mac_addr(priv, dev->dev_addr); + + return 0; +} + +static void hisi_femac_enable_hw_addr_filter(struct hisi_femac_priv *priv, + unsigned int reg_n, bool enable) +{ + u32 val; + + val = readl(priv->glb_base + GLB_MAC_H16(reg_n)); + if (enable) + val |= BIT_MACFLT_ENA; + else + val &= ~BIT_MACFLT_ENA; + writel(val, priv->glb_base + GLB_MAC_H16(reg_n)); +} + +static void hisi_femac_set_hw_addr_filter(struct hisi_femac_priv *priv, + unsigned char *addr, + unsigned int reg_n) +{ + unsigned int high, low; + u32 val; + + high = GLB_MAC_H16(reg_n); + low = GLB_MAC_L32(reg_n); + + val = (addr[2] << 24) | (addr[3] << 16) | (addr[4] << 8) | addr[5]; + writel(val, priv->glb_base + low); + + val = readl(priv->glb_base + high); + val &= ~MACFLT_HI16_MASK; + val |= ((addr[0] << 8) | addr[1]); + val |= (BIT_MACFLT_ENA | BIT_MACFLT_FW2CPU); + writel(val, priv->glb_base + high); +} + +static void hisi_femac_set_promisc_mode(struct hisi_femac_priv *priv, + bool promisc_mode) +{ + u32 val; + + val = readl(priv->glb_base + GLB_FWCTRL); + if (promisc_mode) + val |= FWCTRL_FWALL2CPU; + else + val &= ~FWCTRL_FWALL2CPU; + writel(val, priv->glb_base + GLB_FWCTRL); +} + +/* Handle multiple multicast addresses (perfect filtering)*/ +static void hisi_femac_set_mc_addr_filter(struct hisi_femac_priv *priv) +{ + struct net_device *dev = priv->ndev; + u32 val; + + val = readl(priv->glb_base + GLB_MACTCTRL); + if ((netdev_mc_count(dev) > MAX_MULTICAST_ADDRESSES) || + (dev->flags & IFF_ALLMULTI)) { + val |= MACTCTRL_MULTI2CPU; + } else { + int reg = MAX_UNICAST_ADDRESSES; + int i; + struct netdev_hw_addr *ha; + + for (i = reg; i < MAX_MAC_FILTER_NUM; i++) + hisi_femac_enable_hw_addr_filter(priv, i, false); + + netdev_for_each_mc_addr(ha, dev) { + hisi_femac_set_hw_addr_filter(priv, ha->addr, reg); + reg++; + } + val &= ~MACTCTRL_MULTI2CPU; + } + writel(val, priv->glb_base + GLB_MACTCTRL); +} + +/* Handle multiple unicast addresses (perfect filtering)*/ +static void hisi_femac_set_uc_addr_filter(struct hisi_femac_priv *priv) +{ + struct net_device *dev = priv->ndev; + u32 val; + + val = readl(priv->glb_base + GLB_MACTCTRL); + if (netdev_uc_count(dev) > MAX_UNICAST_ADDRESSES) { + val |= MACTCTRL_UNI2CPU; + } else { + int reg = 0; + int i; + struct netdev_hw_addr *ha; + + for (i = reg; i < MAX_UNICAST_ADDRESSES; i++) + hisi_femac_enable_hw_addr_filter(priv, i, false); + + netdev_for_each_uc_addr(ha, dev) { + hisi_femac_set_hw_addr_filter(priv, ha->addr, reg); + reg++; + } + val &= ~MACTCTRL_UNI2CPU; + } + writel(val, priv->glb_base + GLB_MACTCTRL); +} + +static void hisi_femac_net_set_rx_mode(struct net_device *dev) +{ + struct hisi_femac_priv *priv = netdev_priv(dev); + + if (dev->flags & IFF_PROMISC) { + hisi_femac_set_promisc_mode(priv, true); + } else { + hisi_femac_set_promisc_mode(priv, false); + hisi_femac_set_mc_addr_filter(priv); + hisi_femac_set_uc_addr_filter(priv); + } +} + +static int hisi_femac_net_ioctl(struct net_device *dev, + struct ifreq *ifreq, int cmd) +{ + if (!netif_running(dev)) + return -EINVAL; + + if (!dev->phydev) + return -EINVAL; + + return phy_mii_ioctl(dev->phydev, ifreq, cmd); +} + +static struct ethtool_ops hisi_femac_ethtools_ops = { + .get_link = ethtool_op_get_link, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, +}; + +static const struct net_device_ops hisi_femac_netdev_ops = { + .ndo_open = hisi_femac_net_open, + .ndo_stop = hisi_femac_net_close, + .ndo_start_xmit = hisi_femac_net_xmit, + .ndo_do_ioctl = hisi_femac_net_ioctl, + .ndo_set_mac_address = hisi_femac_set_mac_address, + .ndo_set_rx_mode = hisi_femac_net_set_rx_mode, + .ndo_change_mtu = eth_change_mtu, +}; + +static void hisi_femac_core_reset(struct hisi_femac_priv *priv) +{ + reset_control_assert(priv->mac_rst); + reset_control_deassert(priv->mac_rst); +} + +static void hisi_femac_sleep_us(u32 time_us) +{ + u32 time_ms; + + if (!time_us) + return; + + time_ms = DIV_ROUND_UP(time_us, 1000); + if (time_ms < 20) + usleep_range(time_us, time_us + 500); + else + msleep(time_ms); +} + +static void hisi_femac_phy_reset(struct hisi_femac_priv *priv) +{ + /* To make sure PHY hardware reset success, + * we must keep PHY in deassert state first and + * then complete the hardware reset operation + */ + reset_control_deassert(priv->phy_rst); + hisi_femac_sleep_us(priv->phy_reset_delays[PRE_DELAY]); + + reset_control_assert(priv->phy_rst); + /* delay some time to ensure reset ok, + * this depends on PHY hardware feature + */ + hisi_femac_sleep_us(priv->phy_reset_delays[PULSE]); + reset_control_deassert(priv->phy_rst); + /* delay some time to ensure later MDIO access */ + hisi_femac_sleep_us(priv->phy_reset_delays[POST_DELAY]); +} + +static void hisi_femac_port_init(struct hisi_femac_priv *priv) +{ + u32 val; + + /* MAC gets link status info and phy mode by software config */ + val = MAC_PORTSEL_STAT_CPU; + if (priv->ndev->phydev->interface == PHY_INTERFACE_MODE_RMII) + val |= MAC_PORTSEL_RMII; + writel(val, priv->port_base + MAC_PORTSEL); + + /*clear all interrupt status */ + writel(IRQ_ENA_PORT0_MASK, priv->glb_base + GLB_IRQ_RAW); + hisi_femac_irq_disable(priv, IRQ_ENA_PORT0_MASK | IRQ_ENA_PORT0); + + val = readl(priv->glb_base + GLB_FWCTRL); + val &= ~(FWCTRL_VLAN_ENABLE | FWCTRL_FWALL2CPU); + val |= FWCTRL_FW2CPU_ENA; + writel(val, priv->glb_base + GLB_FWCTRL); + + val = readl(priv->glb_base + GLB_MACTCTRL); + val |= (MACTCTRL_BROAD2CPU | MACTCTRL_MACT_ENA); + writel(val, priv->glb_base + GLB_MACTCTRL); + + val = readl(priv->port_base + MAC_SET); + val &= ~MAX_FRAME_SIZE_MASK; + val |= MAX_FRAME_SIZE; + writel(val, priv->port_base + MAC_SET); + + val = RX_COALESCED_TIMER | + (RX_COALESCED_FRAMES << RX_COALESCED_FRAME_OFFSET); + writel(val, priv->port_base + RX_COALESCE_SET); + + val = (HW_RX_FIFO_DEPTH << RX_DEPTH_OFFSET) | HW_TX_FIFO_DEPTH; + writel(val, priv->port_base + QLEN_SET); +} + +static int hisi_femac_drv_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct resource *res; + struct net_device *ndev; + struct hisi_femac_priv *priv; + struct phy_device *phy; + const char *mac_addr; + int ret; + + ndev = alloc_etherdev(sizeof(*priv)); + if (!ndev) + return -ENOMEM; + + platform_set_drvdata(pdev, ndev); + + priv = netdev_priv(ndev); + priv->dev = dev; + priv->ndev = ndev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->port_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->port_base)) { + ret = PTR_ERR(priv->port_base); + goto out_free_netdev; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + priv->glb_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->glb_base)) { + ret = PTR_ERR(priv->glb_base); + goto out_free_netdev; + } + + priv->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get clk\n"); + ret = -ENODEV; + goto out_free_netdev; + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(dev, "failed to enable clk %d\n", ret); + goto out_free_netdev; + } + + priv->mac_rst = devm_reset_control_get(dev, "mac"); + if (IS_ERR(priv->mac_rst)) { + ret = PTR_ERR(priv->mac_rst); + goto out_disable_clk; + } + hisi_femac_core_reset(priv); + + priv->phy_rst = devm_reset_control_get(dev, "phy"); + if (IS_ERR(priv->phy_rst)) { + priv->phy_rst = NULL; + } else { + ret = of_property_read_u32_array(node, + PHY_RESET_DELAYS_PROPERTY, + priv->phy_reset_delays, + DELAYS_NUM); + if (ret) + goto out_disable_clk; + hisi_femac_phy_reset(priv); + } + + phy = of_phy_get_and_connect(ndev, node, hisi_femac_adjust_link); + if (!phy) { + dev_err(dev, "connect to PHY failed!\n"); + ret = -ENODEV; + goto out_disable_clk; + } + + phy_attached_print(phy, "phy_id=0x%.8lx, phy_mode=%s\n", + (unsigned long)phy->phy_id, + phy_modes(phy->interface)); + + mac_addr = of_get_mac_address(node); + if (mac_addr) + ether_addr_copy(ndev->dev_addr, mac_addr); + if (!is_valid_ether_addr(ndev->dev_addr)) { + eth_hw_addr_random(ndev); + dev_warn(dev, "using random MAC address %pM\n", + ndev->dev_addr); + } + + ndev->watchdog_timeo = 6 * HZ; + ndev->priv_flags |= IFF_UNICAST_FLT; + ndev->netdev_ops = &hisi_femac_netdev_ops; + ndev->ethtool_ops = &hisi_femac_ethtools_ops; + netif_napi_add(ndev, &priv->napi, hisi_femac_poll, FEMAC_POLL_WEIGHT); + SET_NETDEV_DEV(ndev, &pdev->dev); + + hisi_femac_port_init(priv); + + ret = hisi_femac_init_tx_and_rx_queues(priv); + if (ret) + goto out_disconnect_phy; + + ndev->irq = platform_get_irq(pdev, 0); + if (ndev->irq <= 0) { + dev_err(dev, "No irq resource\n"); + ret = -ENODEV; + goto out_disconnect_phy; + } + + ret = devm_request_irq(dev, ndev->irq, hisi_femac_interrupt, + IRQF_SHARED, pdev->name, ndev); + if (ret) { + dev_err(dev, "devm_request_irq %d failed!\n", ndev->irq); + goto out_disconnect_phy; + } + + ret = register_netdev(ndev); + if (ret) { + dev_err(dev, "register_netdev failed!\n"); + goto out_disconnect_phy; + } + + return ret; + +out_disconnect_phy: + netif_napi_del(&priv->napi); + phy_disconnect(phy); +out_disable_clk: + clk_disable_unprepare(priv->clk); +out_free_netdev: + free_netdev(ndev); + + return ret; +} + +static int hisi_femac_drv_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct hisi_femac_priv *priv = netdev_priv(ndev); + + netif_napi_del(&priv->napi); + unregister_netdev(ndev); + + phy_disconnect(ndev->phydev); + clk_disable_unprepare(priv->clk); + free_netdev(ndev); + + return 0; +} + +#ifdef CONFIG_PM +int hisi_femac_drv_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct hisi_femac_priv *priv = netdev_priv(ndev); + + disable_irq(ndev->irq); + if (netif_running(ndev)) { + hisi_femac_net_close(ndev); + netif_device_detach(ndev); + } + + clk_disable_unprepare(priv->clk); + + return 0; +} + +int hisi_femac_drv_resume(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct hisi_femac_priv *priv = netdev_priv(ndev); + + clk_prepare_enable(priv->clk); + if (priv->phy_rst) + hisi_femac_phy_reset(priv); + + if (netif_running(ndev)) { + hisi_femac_port_init(priv); + hisi_femac_net_open(ndev); + netif_device_attach(ndev); + } + enable_irq(ndev->irq); + + return 0; +} +#endif + +static const struct of_device_id hisi_femac_match[] = { + {.compatible = "hisilicon,hisi-femac-v1",}, + {.compatible = "hisilicon,hisi-femac-v2",}, + {.compatible = "hisilicon,hi3516cv300-femac",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, hisi_femac_match); + +static struct platform_driver hisi_femac_driver = { + .driver = { + .name = "hisi-femac", + .of_match_table = hisi_femac_match, + }, + .probe = hisi_femac_drv_probe, + .remove = hisi_femac_drv_remove, +#ifdef CONFIG_PM + .suspend = hisi_femac_drv_suspend, + .resume = hisi_femac_drv_resume, +#endif +}; + +module_platform_driver(hisi_femac_driver); + +MODULE_DESCRIPTION("Hisilicon Fast Ethernet MAC driver"); +MODULE_AUTHOR("Dongpo Li "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:hisi-femac"); -- cgit v1.2.3 From 01efe65aba6585c78c6fb90d63c3d10773b6f868 Mon Sep 17 00:00:00 2001 From: Eyal Reizer Date: Tue, 19 Jul 2016 09:58:02 +0300 Subject: wlcore: spi: add wl18xx support Add support for using with both wl12xx and wl18xx. - all wilink family needs special init command for entering wspi mode. extra clock cycles should be sent after the spi init command while the cs pin is high. - Use inverted chip select for sending a dummy 4 bytes command that completes the init stage. Signed-off-by: Eyal Reizer Acked-by: Rob Herring Signed-off-by: Kalle Valo --- .../bindings/net/wireless/ti,wlcore,spi.txt | 41 +++++-- drivers/net/wireless/ti/wlcore/spi.c | 123 ++++++++++++++++++--- 2 files changed, 137 insertions(+), 27 deletions(-) (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/wireless/ti,wlcore,spi.txt b/Documentation/devicetree/bindings/net/wireless/ti,wlcore,spi.txt index 9180724e182c..8f9ced076fe1 100644 --- a/Documentation/devicetree/bindings/net/wireless/ti,wlcore,spi.txt +++ b/Documentation/devicetree/bindings/net/wireless/ti,wlcore,spi.txt @@ -1,19 +1,30 @@ -* Texas Instruments wl1271 wireless lan controller +* Texas Instruments wl12xx/wl18xx wireless lan controller -The wl1271 chip can be connected via SPI or via SDIO. This +The wl12xx/wl18xx chips can be connected via SPI or via SDIO. This document describes the binding for the SPI connected chip. Required properties: -- compatible : Should be "ti,wl1271" +- compatible : Should be one of the following: + * "ti,wl1271" + * "ti,wl1273" + * "ti,wl1281" + * "ti,wl1283" + * "ti,wl1801" + * "ti,wl1805" + * "ti,wl1807" + * "ti,wl1831" + * "ti,wl1835" + * "ti,wl1837" - reg : Chip select address of device - spi-max-frequency : Maximum SPI clocking speed of device in Hz -- ref-clock-frequency : Reference clock frequency - interrupt-parent, interrupts : Should contain parameters for 1 interrupt line. Interrupt parameters: parent, line number, type. -- vwlan-supply : Point the node of the regulator that powers/enable the wl1271 chip +- vwlan-supply : Point the node of the regulator that powers/enable the + wl12xx/wl18xx chip Optional properties: +- ref-clock-frequency : Reference clock frequency (should be set for wl12xx) - clock-xtal : boolean, clock is generated from XTAL - Please consult Documentation/devicetree/bindings/spi/spi-bus.txt @@ -21,16 +32,28 @@ Optional properties: Examples: +For wl12xx family: &spi1 { - wl1271@1 { + wlcore: wlcore@1 { compatible = "ti,wl1271"; - reg = <1>; spi-max-frequency = <48000000>; - clock-xtal; - ref-clock-frequency = <38400000>; interrupt-parent = <&gpio3>; interrupts = <8 IRQ_TYPE_LEVEL_HIGH>; vwlan-supply = <&vwlan_fixed>; + clock-xtal; + ref-clock-frequency = <38400000>; + }; +}; + +For wl18xx family: +&spi0 { + wlcore: wlcore@0 { + compatible = "ti,wl1835"; + reg = <0>; + spi-max-frequency = <48000000>; + interrupt-parent = <&gpio0>; + interrupts = <27 IRQ_TYPE_EDGE_RISING>; + vwlan-supply = <&vwlan_fixed>; }; }; diff --git a/drivers/net/wireless/ti/wlcore/spi.c b/drivers/net/wireless/ti/wlcore/spi.c index cea9443c22a6..73fbcf15545c 100644 --- a/drivers/net/wireless/ti/wlcore/spi.c +++ b/drivers/net/wireless/ti/wlcore/spi.c @@ -70,16 +70,30 @@ #define WSPI_MAX_CHUNK_SIZE 4092 /* - * only support SPI for 12xx - this code should be reworked when 18xx - * support is introduced + * wl18xx driver aggregation buffer size is (13 * PAGE_SIZE) compared to + * (4 * PAGE_SIZE) for wl12xx, so use the larger buffer needed for wl18xx */ -#define SPI_AGGR_BUFFER_SIZE (4 * PAGE_SIZE) +#define SPI_AGGR_BUFFER_SIZE (13 * PAGE_SIZE) /* Maximum number of SPI write chunks */ #define WSPI_MAX_NUM_OF_CHUNKS \ ((SPI_AGGR_BUFFER_SIZE / WSPI_MAX_CHUNK_SIZE) + 1) +struct wilink_familiy_data { + char name[8]; +}; + +const struct wilink_familiy_data *wilink_data; + +static const struct wilink_familiy_data wl18xx_data = { + .name = "wl18xx", +}; + +static const struct wilink_familiy_data wl12xx_data = { + .name = "wl12xx", +}; + struct wl12xx_spi_glue { struct device *dev; struct platform_device *core; @@ -119,6 +133,7 @@ static void wl12xx_spi_init(struct device *child) struct wl12xx_spi_glue *glue = dev_get_drvdata(child->parent); struct spi_transfer t; struct spi_message m; + struct spi_device *spi = to_spi_device(glue->dev); u8 *cmd = kzalloc(WSPI_INIT_CMD_LEN, GFP_KERNEL); if (!cmd) { @@ -151,6 +166,7 @@ static void wl12xx_spi_init(struct device *child) cmd[6] |= WSPI_INIT_CMD_EN_FIXEDBUSY; cmd[7] = crc7_be(0, cmd+2, WSPI_INIT_CMD_CRC_LEN) | WSPI_INIT_CMD_END; + /* * The above is the logical order; it must actually be stored * in the buffer byte-swapped. @@ -163,6 +179,28 @@ static void wl12xx_spi_init(struct device *child) spi_message_add_tail(&t, &m); spi_sync(to_spi_device(glue->dev), &m); + + /* Send extra clocks with inverted CS (high). this is required + * by the wilink family in order to successfully enter WSPI mode. + */ + spi->mode ^= SPI_CS_HIGH; + memset(&m, 0, sizeof(m)); + spi_message_init(&m); + + cmd[0] = 0xff; + cmd[1] = 0xff; + cmd[2] = 0xff; + cmd[3] = 0xff; + __swab32s((u32 *)cmd); + + t.tx_buf = cmd; + t.len = 4; + spi_message_add_tail(&t, &m); + + spi_sync(to_spi_device(glue->dev), &m); + + /* Restore chip select configration to normal */ + spi->mode ^= SPI_CS_HIGH; kfree(cmd); } @@ -270,22 +308,25 @@ static int __must_check wl12xx_spi_raw_read(struct device *child, int addr, return 0; } -static int __must_check wl12xx_spi_raw_write(struct device *child, int addr, - void *buf, size_t len, bool fixed) +static int __wl12xx_spi_raw_write(struct device *child, int addr, + void *buf, size_t len, bool fixed) { struct wl12xx_spi_glue *glue = dev_get_drvdata(child->parent); - /* SPI write buffers - 2 for each chunk */ - struct spi_transfer t[2 * WSPI_MAX_NUM_OF_CHUNKS]; + struct spi_transfer *t; struct spi_message m; u32 commands[WSPI_MAX_NUM_OF_CHUNKS]; /* 1 command per chunk */ u32 *cmd; u32 chunk_len; int i; + /* SPI write buffers - 2 for each chunk */ + t = kzalloc(sizeof(*t) * 2 * WSPI_MAX_NUM_OF_CHUNKS, GFP_KERNEL); + if (!t) + return -ENOMEM; + WARN_ON(len > SPI_AGGR_BUFFER_SIZE); spi_message_init(&m); - memset(t, 0, sizeof(t)); cmd = &commands[0]; i = 0; @@ -318,9 +359,26 @@ static int __must_check wl12xx_spi_raw_write(struct device *child, int addr, spi_sync(to_spi_device(glue->dev), &m); + kfree(t); return 0; } +static int __must_check wl12xx_spi_raw_write(struct device *child, int addr, + void *buf, size_t len, bool fixed) +{ + int ret; + + /* The ELP wakeup write may fail the first time due to internal + * hardware latency. It is safer to send the wakeup command twice to + * avoid unexpected failures. + */ + if (addr == HW_ACCESS_ELP_CTRL_REG) + ret = __wl12xx_spi_raw_write(child, addr, buf, len, fixed); + ret = __wl12xx_spi_raw_write(child, addr, buf, len, fixed); + + return ret; +} + /** * wl12xx_spi_set_power - power on/off the wl12xx unit * @child: wl12xx device handle. @@ -349,17 +407,38 @@ static int wl12xx_spi_set_power(struct device *child, bool enable) return ret; } +/** + * wl12xx_spi_set_block_size + * + * This function is not needed for spi mode, but need to be present. + * Without it defined the wlcore fallback to use the wrong packet + * allignment on tx. + */ +static void wl12xx_spi_set_block_size(struct device *child, + unsigned int blksz) +{ +} + static struct wl1271_if_operations spi_ops = { .read = wl12xx_spi_raw_read, .write = wl12xx_spi_raw_write, .reset = wl12xx_spi_reset, .init = wl12xx_spi_init, .power = wl12xx_spi_set_power, - .set_block_size = NULL, + .set_block_size = wl12xx_spi_set_block_size, }; static const struct of_device_id wlcore_spi_of_match_table[] = { - { .compatible = "ti,wl1271" }, + { .compatible = "ti,wl1271", .data = &wl12xx_data}, + { .compatible = "ti,wl1273", .data = &wl12xx_data}, + { .compatible = "ti,wl1281", .data = &wl12xx_data}, + { .compatible = "ti,wl1283", .data = &wl12xx_data}, + { .compatible = "ti,wl1801", .data = &wl18xx_data}, + { .compatible = "ti,wl1805", .data = &wl18xx_data}, + { .compatible = "ti,wl1807", .data = &wl18xx_data}, + { .compatible = "ti,wl1831", .data = &wl18xx_data}, + { .compatible = "ti,wl1835", .data = &wl18xx_data}, + { .compatible = "ti,wl1837", .data = &wl18xx_data}, { } }; MODULE_DEVICE_TABLE(of, wlcore_spi_of_match_table); @@ -376,17 +455,24 @@ static int wlcore_probe_of(struct spi_device *spi, struct wl12xx_spi_glue *glue, { struct device_node *dt_node = spi->dev.of_node; int ret; + const struct of_device_id *of_id; + + of_id = of_match_node(wlcore_spi_of_match_table, dt_node); + if (!of_id) + return -ENODEV; + + wilink_data = of_id->data; + dev_info(&spi->dev, "selected chip familiy is %s\n", + wilink_data->name); if (of_find_property(dt_node, "clock-xtal", NULL)) pdev_data->ref_clock_xtal = true; - ret = of_property_read_u32(dt_node, "ref-clock-frequency", - &pdev_data->ref_clock_freq); - if (ret) { - dev_err(glue->dev, - "can't get reference clock frequency (%d)\n", ret); - return ret; - } + /* optional clock frequency params */ + of_property_read_u32(dt_node, "ref-clock-frequency", + &pdev_data->ref_clock_freq); + of_property_read_u32(dt_node, "tcxo-clock-frequency", + &pdev_data->tcxo_clock_freq); return 0; } @@ -437,7 +523,8 @@ static int wl1271_probe(struct spi_device *spi) return ret; } - glue->core = platform_device_alloc("wl12xx", PLATFORM_DEVID_AUTO); + glue->core = platform_device_alloc(wilink_data->name, + PLATFORM_DEVID_AUTO); if (!glue->core) { dev_err(glue->dev, "can't allocate platform_device\n"); return -ENOMEM; -- cgit v1.2.3 From 792a1107fe713347ed230bc79a53e875b6abe7c7 Mon Sep 17 00:00:00 2001 From: Iyappan Subramanian Date: Mon, 25 Jul 2016 17:12:46 -0700 Subject: Documentation: dtb: xgene: Add MDIO node Signed-off-by: Iyappan Subramanian Tested-by: Fushen Chen Tested-by: Toan Le Acked-by: Rob Herring Signed-off-by: David S. Miller --- .../devicetree/bindings/net/apm-xgene-mdio.txt | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/apm-xgene-mdio.txt (limited to 'Documentation/devicetree/bindings') diff --git a/Documentation/devicetree/bindings/net/apm-xgene-mdio.txt b/Documentation/devicetree/bindings/net/apm-xgene-mdio.txt new file mode 100644 index 000000000000..78722d74cea8 --- /dev/null +++ b/Documentation/devicetree/bindings/net/apm-xgene-mdio.txt @@ -0,0 +1,37 @@ +APM X-Gene SoC MDIO node + +MDIO node is defined to describe on-chip MDIO controller. + +Required properties: + - compatible: Must be "apm,xgene-mdio-rgmii" or "apm,xgene-mdio-xfi" + - #address-cells: Must be <1>. + - #size-cells: Must be <0>. + - reg: Address and length of the register set + - clocks: Reference to the clock entry + +For the phys on the mdio bus, there must be a node with the following fields: + - compatible: PHY identifier. Please refer ./phy.txt for the format. + - reg: The ID number for the phy. + +Example: + + mdio: mdio@17020000 { + compatible = "apm,xgene-mdio-rgmii"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x0 0x17020000 0x0 0xd100>; + clocks = <&menetclk 0>; + }; + + /* Board-specific peripheral configurations */ + &mdio { + menetphy: phy@3 { + reg = <0x3>; + }; + sgenet0phy: phy@4 { + reg = <0x4>; + }; + sgenet1phy: phy@5 { + reg = <0x5>; + }; + }; -- cgit v1.2.3