linux/drivers/net/dsa/mv88e6060.c
<<
>>
Prefs
   1/*
   2 * net/dsa/mv88e6060.c - Driver for Marvell 88e6060 switch chips
   3 * Copyright (c) 2008-2009 Marvell Semiconductor
   4 *
   5 * This program is free software; you can redistribute it and/or modify
   6 * it under the terms of the GNU General Public License as published by
   7 * the Free Software Foundation; either version 2 of the License, or
   8 * (at your option) any later version.
   9 */
  10
  11#include <linux/delay.h>
  12#include <linux/jiffies.h>
  13#include <linux/list.h>
  14#include <linux/module.h>
  15#include <linux/netdevice.h>
  16#include <linux/phy.h>
  17#include <net/dsa.h>
  18#include "mv88e6060.h"
  19
  20static int reg_read(struct dsa_switch *ds, int addr, int reg)
  21{
  22        struct mv88e6060_priv *priv = ds->priv;
  23
  24        return mdiobus_read_nested(priv->bus, priv->sw_addr + addr, reg);
  25}
  26
  27#define REG_READ(addr, reg)                                     \
  28        ({                                                      \
  29                int __ret;                                      \
  30                                                                \
  31                __ret = reg_read(ds, addr, reg);                \
  32                if (__ret < 0)                                  \
  33                        return __ret;                           \
  34                __ret;                                          \
  35        })
  36
  37
  38static int reg_write(struct dsa_switch *ds, int addr, int reg, u16 val)
  39{
  40        struct mv88e6060_priv *priv = ds->priv;
  41
  42        return mdiobus_write_nested(priv->bus, priv->sw_addr + addr, reg, val);
  43}
  44
  45#define REG_WRITE(addr, reg, val)                               \
  46        ({                                                      \
  47                int __ret;                                      \
  48                                                                \
  49                __ret = reg_write(ds, addr, reg, val);          \
  50                if (__ret < 0)                                  \
  51                        return __ret;                           \
  52        })
  53
  54static const char *mv88e6060_get_name(struct mii_bus *bus, int sw_addr)
  55{
  56        int ret;
  57
  58        ret = mdiobus_read(bus, sw_addr + REG_PORT(0), PORT_SWITCH_ID);
  59        if (ret >= 0) {
  60                if (ret == PORT_SWITCH_ID_6060)
  61                        return "Marvell 88E6060 (A0)";
  62                if (ret == PORT_SWITCH_ID_6060_R1 ||
  63                    ret == PORT_SWITCH_ID_6060_R2)
  64                        return "Marvell 88E6060 (B0)";
  65                if ((ret & PORT_SWITCH_ID_6060_MASK) == PORT_SWITCH_ID_6060)
  66                        return "Marvell 88E6060";
  67        }
  68
  69        return NULL;
  70}
  71
  72static enum dsa_tag_protocol mv88e6060_get_tag_protocol(struct dsa_switch *ds)
  73{
  74        return DSA_TAG_PROTO_TRAILER;
  75}
  76
  77static const char *mv88e6060_drv_probe(struct device *dsa_dev,
  78                                       struct device *host_dev, int sw_addr,
  79                                       void **_priv)
  80{
  81        struct mii_bus *bus = dsa_host_dev_to_mii_bus(host_dev);
  82        struct mv88e6060_priv *priv;
  83        const char *name;
  84
  85        name = mv88e6060_get_name(bus, sw_addr);
  86        if (name) {
  87                priv = devm_kzalloc(dsa_dev, sizeof(*priv), GFP_KERNEL);
  88                if (!priv)
  89                        return NULL;
  90                *_priv = priv;
  91                priv->bus = bus;
  92                priv->sw_addr = sw_addr;
  93        }
  94
  95        return name;
  96}
  97
  98static int mv88e6060_switch_reset(struct dsa_switch *ds)
  99{
 100        int i;
 101        int ret;
 102        unsigned long timeout;
 103
 104        /* Set all ports to the disabled state. */
 105        for (i = 0; i < MV88E6060_PORTS; i++) {
 106                ret = REG_READ(REG_PORT(i), PORT_CONTROL);
 107                REG_WRITE(REG_PORT(i), PORT_CONTROL,
 108                          ret & ~PORT_CONTROL_STATE_MASK);
 109        }
 110
 111        /* Wait for transmit queues to drain. */
 112        usleep_range(2000, 4000);
 113
 114        /* Reset the switch. */
 115        REG_WRITE(REG_GLOBAL, GLOBAL_ATU_CONTROL,
 116                  GLOBAL_ATU_CONTROL_SWRESET |
 117                  GLOBAL_ATU_CONTROL_ATUSIZE_1024 |
 118                  GLOBAL_ATU_CONTROL_ATE_AGE_5MIN);
 119
 120        /* Wait up to one second for reset to complete. */
 121        timeout = jiffies + 1 * HZ;
 122        while (time_before(jiffies, timeout)) {
 123                ret = REG_READ(REG_GLOBAL, GLOBAL_STATUS);
 124                if (ret & GLOBAL_STATUS_INIT_READY)
 125                        break;
 126
 127                usleep_range(1000, 2000);
 128        }
 129        if (time_after(jiffies, timeout))
 130                return -ETIMEDOUT;
 131
 132        return 0;
 133}
 134
 135static int mv88e6060_setup_global(struct dsa_switch *ds)
 136{
 137        /* Disable discarding of frames with excessive collisions,
 138         * set the maximum frame size to 1536 bytes, and mask all
 139         * interrupt sources.
 140         */
 141        REG_WRITE(REG_GLOBAL, GLOBAL_CONTROL, GLOBAL_CONTROL_MAX_FRAME_1536);
 142
 143        /* Enable automatic address learning, set the address
 144         * database size to 1024 entries, and set the default aging
 145         * time to 5 minutes.
 146         */
 147        REG_WRITE(REG_GLOBAL, GLOBAL_ATU_CONTROL,
 148                  GLOBAL_ATU_CONTROL_ATUSIZE_1024 |
 149                  GLOBAL_ATU_CONTROL_ATE_AGE_5MIN);
 150
 151        return 0;
 152}
 153
 154static int mv88e6060_setup_port(struct dsa_switch *ds, int p)
 155{
 156        int addr = REG_PORT(p);
 157
 158        /* Do not force flow control, disable Ingress and Egress
 159         * Header tagging, disable VLAN tunneling, and set the port
 160         * state to Forwarding.  Additionally, if this is the CPU
 161         * port, enable Ingress and Egress Trailer tagging mode.
 162         */
 163        REG_WRITE(addr, PORT_CONTROL,
 164                  dsa_is_cpu_port(ds, p) ?
 165                        PORT_CONTROL_TRAILER |
 166                        PORT_CONTROL_INGRESS_MODE |
 167                        PORT_CONTROL_STATE_FORWARDING :
 168                        PORT_CONTROL_STATE_FORWARDING);
 169
 170        /* Port based VLAN map: give each port its own address
 171         * database, allow the CPU port to talk to each of the 'real'
 172         * ports, and allow each of the 'real' ports to only talk to
 173         * the CPU port.
 174         */
 175        REG_WRITE(addr, PORT_VLAN_MAP,
 176                  ((p & 0xf) << PORT_VLAN_MAP_DBNUM_SHIFT) |
 177                   (dsa_is_cpu_port(ds, p) ?
 178                        ds->enabled_port_mask :
 179                        BIT(ds->dst->cpu_dp->index)));
 180
 181        /* Port Association Vector: when learning source addresses
 182         * of packets, add the address to the address database using
 183         * a port bitmap that has only the bit for this port set and
 184         * the other bits clear.
 185         */
 186        REG_WRITE(addr, PORT_ASSOC_VECTOR, BIT(p));
 187
 188        return 0;
 189}
 190
 191static int mv88e6060_setup(struct dsa_switch *ds)
 192{
 193        int ret;
 194        int i;
 195
 196        ret = mv88e6060_switch_reset(ds);
 197        if (ret < 0)
 198                return ret;
 199
 200        /* @@@ initialise atu */
 201
 202        ret = mv88e6060_setup_global(ds);
 203        if (ret < 0)
 204                return ret;
 205
 206        for (i = 0; i < MV88E6060_PORTS; i++) {
 207                ret = mv88e6060_setup_port(ds, i);
 208                if (ret < 0)
 209                        return ret;
 210        }
 211
 212        return 0;
 213}
 214
 215static int mv88e6060_set_addr(struct dsa_switch *ds, u8 *addr)
 216{
 217        /* Use the same MAC Address as FD Pause frames for all ports */
 218        REG_WRITE(REG_GLOBAL, GLOBAL_MAC_01, (addr[0] << 9) | addr[1]);
 219        REG_WRITE(REG_GLOBAL, GLOBAL_MAC_23, (addr[2] << 8) | addr[3]);
 220        REG_WRITE(REG_GLOBAL, GLOBAL_MAC_45, (addr[4] << 8) | addr[5]);
 221
 222        return 0;
 223}
 224
 225static int mv88e6060_port_to_phy_addr(int port)
 226{
 227        if (port >= 0 && port < MV88E6060_PORTS)
 228                return port;
 229        return -1;
 230}
 231
 232static int mv88e6060_phy_read(struct dsa_switch *ds, int port, int regnum)
 233{
 234        int addr;
 235
 236        addr = mv88e6060_port_to_phy_addr(port);
 237        if (addr == -1)
 238                return 0xffff;
 239
 240        return reg_read(ds, addr, regnum);
 241}
 242
 243static int
 244mv88e6060_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val)
 245{
 246        int addr;
 247
 248        addr = mv88e6060_port_to_phy_addr(port);
 249        if (addr == -1)
 250                return 0xffff;
 251
 252        return reg_write(ds, addr, regnum, val);
 253}
 254
 255static const struct dsa_switch_ops mv88e6060_switch_ops = {
 256        .get_tag_protocol = mv88e6060_get_tag_protocol,
 257        .probe          = mv88e6060_drv_probe,
 258        .setup          = mv88e6060_setup,
 259        .set_addr       = mv88e6060_set_addr,
 260        .phy_read       = mv88e6060_phy_read,
 261        .phy_write      = mv88e6060_phy_write,
 262};
 263
 264static struct dsa_switch_driver mv88e6060_switch_drv = {
 265        .ops            = &mv88e6060_switch_ops,
 266};
 267
 268static int __init mv88e6060_init(void)
 269{
 270        register_switch_driver(&mv88e6060_switch_drv);
 271        return 0;
 272}
 273module_init(mv88e6060_init);
 274
 275static void __exit mv88e6060_cleanup(void)
 276{
 277        unregister_switch_driver(&mv88e6060_switch_drv);
 278}
 279module_exit(mv88e6060_cleanup);
 280
 281MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>");
 282MODULE_DESCRIPTION("Driver for Marvell 88E6060 ethernet switch chip");
 283MODULE_LICENSE("GPL");
 284MODULE_ALIAS("platform:mv88e6060");
 285