OpenWrt – Rev 2

Subversion Repositories:
Rev:
/*
 * Lantiq PSB6970 (Tantos) Switch driver
 *
 * Copyright (c) 2009,2010 Team Embedded.
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of the GNU General Public License v2 as published by the
 * Free Software Foundation.
 *
 * The switch programming done in this driver follows the 
 * "Ethernet Traffic Separation using VLAN" Application Note as
 * published by Lantiq.
 */

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/switch.h>
#include <linux/phy.h>

#define PSB6970_MAX_VLANS               16
#define PSB6970_NUM_PORTS               7
#define PSB6970_DEFAULT_PORT_CPU        6
#define PSB6970_IS_CPU_PORT(x)          ((x) > 4)

#define PHYADDR(_reg)           ((_reg >> 5) & 0xff), (_reg & 0x1f)

/* --- Identification --- */
#define PSB6970_CI0             0x0100
#define PSB6970_CI0_MASK        0x000f
#define PSB6970_CI1             0x0101
#define PSB6970_CI1_VAL         0x2599
#define PSB6970_CI1_MASK        0xffff

/* --- VLAN filter table --- */
#define PSB6970_VFxL(i)         ((i)*2+0x10)    /* VLAN Filter Low */
#define PSB6970_VFxL_VV         (1 << 15)       /* VLAN_Valid */

#define PSB6970_VFxH(i)         ((i)*2+0x11)    /* VLAN Filter High */
#define PSB6970_VFxH_TM_SHIFT   7               /* Tagged Member */

/* --- Port registers --- */
#define PSB6970_EC(p)           ((p)*0x20+2)    /* Extended Control */
#define PSB6970_EC_IFNTE        (1 << 1)        /* Input Force No Tag Enable */

#define PSB6970_PBVM(p)         ((p)*0x20+3)    /* Port Base VLAN Map */
#define PSB6970_PBVM_VMCE       (1 << 8)
#define PSB6970_PBVM_AOVTP      (1 << 9)
#define PSB6970_PBVM_VSD        (1 << 10)
#define PSB6970_PBVM_VC         (1 << 11)       /* VID Check with VID table */
#define PSB6970_PBVM_TBVE       (1 << 13)       /* Tag-Based VLAN enable */

#define PSB6970_DVID(p)         ((p)*0x20+4)    /* Default VLAN ID & Priority */

struct psb6970_priv {
        struct switch_dev dev;
        struct phy_device *phy;
        u16 (*read) (struct phy_device* phydev, int reg);
        void (*write) (struct phy_device* phydev, int reg, u16 val);
        struct mutex reg_mutex;

        /* all fields below are cleared on reset */
        bool vlan;
        u16 vlan_id[PSB6970_MAX_VLANS];
        u8 vlan_table[PSB6970_MAX_VLANS];
        u8 vlan_tagged;
        u16 pvid[PSB6970_NUM_PORTS];
};

#define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev)

static u16 psb6970_mii_read(struct phy_device *phydev, int reg)
{
        struct mii_bus *bus = phydev->mdio.bus;

        return bus->read(bus, PHYADDR(reg));
}

static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val)
{
        struct mii_bus *bus = phydev->mdio.bus;

        bus->write(bus, PHYADDR(reg), val);
}

static int
psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr,
                 struct switch_val *val)
{
        struct psb6970_priv *priv = to_psb6970(dev);
        priv->vlan = !!val->value.i;
        return 0;
}

static int
psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr,
                 struct switch_val *val)
{
        struct psb6970_priv *priv = to_psb6970(dev);
        val->value.i = priv->vlan;
        return 0;
}

static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan)
{
        struct psb6970_priv *priv = to_psb6970(dev);

        /* make sure no invalid PVIDs get set */
        if (vlan >= dev->vlans)
                return -EINVAL;

        priv->pvid[port] = vlan;
        return 0;
}

static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan)
{
        struct psb6970_priv *priv = to_psb6970(dev);
        *vlan = priv->pvid[port];
        return 0;
}

static int
psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr,
                struct switch_val *val)
{
        struct psb6970_priv *priv = to_psb6970(dev);
        priv->vlan_id[val->port_vlan] = val->value.i;
        return 0;
}

static int
psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr,
                struct switch_val *val)
{
        struct psb6970_priv *priv = to_psb6970(dev);
        val->value.i = priv->vlan_id[val->port_vlan];
        return 0;
}

static struct switch_attr psb6970_globals[] = {
        {
         .type = SWITCH_TYPE_INT,
         .name = "enable_vlan",
         .description = "Enable VLAN mode",
         .set = psb6970_set_vlan,
         .get = psb6970_get_vlan,
         .max = 1},
};

static struct switch_attr psb6970_port[] = {
};

static struct switch_attr psb6970_vlan[] = {
        {
         .type = SWITCH_TYPE_INT,
         .name = "vid",
         .description = "VLAN ID (0-4094)",
         .set = psb6970_set_vid,
         .get = psb6970_get_vid,
         .max = 4094,
         },
};

static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val)
{
        struct psb6970_priv *priv = to_psb6970(dev);
        u8 ports = priv->vlan_table[val->port_vlan];
        int i;

        val->len = 0;
        for (i = 0; i < PSB6970_NUM_PORTS; i++) {
                struct switch_port *p;

                if (!(ports & (1 << i)))
                        continue;

                p = &val->value.ports[val->len++];
                p->id = i;
                if (priv->vlan_tagged & (1 << i))
                        p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
                else
                        p->flags = 0;
        }
        return 0;
}

static int psb6970_set_ports(struct switch_dev *dev, struct switch_val *val)
{
        struct psb6970_priv *priv = to_psb6970(dev);
        u8 *vt = &priv->vlan_table[val->port_vlan];
        int i, j;

        *vt = 0;
        for (i = 0; i < val->len; i++) {
                struct switch_port *p = &val->value.ports[i];

                if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED))
                        priv->vlan_tagged |= (1 << p->id);
                else {
                        priv->vlan_tagged &= ~(1 << p->id);
                        priv->pvid[p->id] = val->port_vlan;

                        /* make sure that an untagged port does not
                         * appear in other vlans */
                        for (j = 0; j < PSB6970_MAX_VLANS; j++) {
                                if (j == val->port_vlan)
                                        continue;
                                priv->vlan_table[j] &= ~(1 << p->id);
                        }
                }

                *vt |= 1 << p->id;
        }
        return 0;
}

static int psb6970_hw_apply(struct switch_dev *dev)
{
        struct psb6970_priv *priv = to_psb6970(dev);
        int i, j;

        mutex_lock(&priv->reg_mutex);

        if (priv->vlan) {
                /* into the vlan translation unit */
                for (j = 0; j < PSB6970_MAX_VLANS; j++) {
                        u8 vp = priv->vlan_table[j];

                        if (vp) {
                                priv->write(priv->phy, PSB6970_VFxL(j),
                                            PSB6970_VFxL_VV | priv->vlan_id[j]);
                                priv->write(priv->phy, PSB6970_VFxH(j),
                                            ((vp & priv->
                                              vlan_tagged) <<
                                             PSB6970_VFxH_TM_SHIFT) | vp);
                        } else  /* clear VLAN Valid flag for unused vlans */
                                priv->write(priv->phy, PSB6970_VFxL(j), 0);

                }
        }

        /* update the port destination mask registers and tag settings */
        for (i = 0; i < PSB6970_NUM_PORTS; i++) {
                int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0;

                if (priv->vlan) {
                        ec = PSB6970_EC_IFNTE;
                        dvid = priv->vlan_id[priv->pvid[i]];
                        pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE;

                        if ((i << 1) & priv->vlan_tagged)
                                pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC;
                }

                priv->write(priv->phy, PSB6970_PBVM(i), pbvm);

                if (!PSB6970_IS_CPU_PORT(i)) {
                        priv->write(priv->phy, PSB6970_EC(i), ec);
                        priv->write(priv->phy, PSB6970_DVID(i), dvid);
                }
        }

        mutex_unlock(&priv->reg_mutex);
        return 0;
}

static int psb6970_reset_switch(struct switch_dev *dev)
{
        struct psb6970_priv *priv = to_psb6970(dev);
        int i;

        mutex_lock(&priv->reg_mutex);

        memset(&priv->vlan, 0, sizeof(struct psb6970_priv) -
               offsetof(struct psb6970_priv, vlan));

        for (i = 0; i < PSB6970_MAX_VLANS; i++)
                priv->vlan_id[i] = i;

        mutex_unlock(&priv->reg_mutex);

        return psb6970_hw_apply(dev);
}

static const struct switch_dev_ops psb6970_ops = {
        .attr_global = {
                        .attr = psb6970_globals,
                        .n_attr = ARRAY_SIZE(psb6970_globals),
                        },
        .attr_port = {
                      .attr = psb6970_port,
                      .n_attr = ARRAY_SIZE(psb6970_port),
                      },
        .attr_vlan = {
                      .attr = psb6970_vlan,
                      .n_attr = ARRAY_SIZE(psb6970_vlan),
                      },
        .get_port_pvid = psb6970_get_pvid,
        .set_port_pvid = psb6970_set_pvid,
        .get_vlan_ports = psb6970_get_ports,
        .set_vlan_ports = psb6970_set_ports,
        .apply_config = psb6970_hw_apply,
        .reset_switch = psb6970_reset_switch,
};

static int psb6970_config_init(struct phy_device *pdev)
{
        struct psb6970_priv *priv;
        struct net_device *dev = pdev->attached_dev;
        struct switch_dev *swdev;
        int ret;

        priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL);
        if (priv == NULL)
                return -ENOMEM;

        priv->phy = pdev;

        if (pdev->mdio.addr == 0)
                printk(KERN_INFO "%s: psb6970 switch driver attached.\n",
                       pdev->attached_dev->name);

        if (pdev->mdio.addr != 0) {
                kfree(priv);
                return 0;
        }

        pdev->supported = pdev->advertising = SUPPORTED_100baseT_Full;

        mutex_init(&priv->reg_mutex);
        priv->read = psb6970_mii_read;
        priv->write = psb6970_mii_write;

        pdev->priv = priv;

        swdev = &priv->dev;
        swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU;
        swdev->ops = &psb6970_ops;

        swdev->name = "Lantiq PSB6970";
        swdev->vlans = PSB6970_MAX_VLANS;
        swdev->ports = PSB6970_NUM_PORTS;

        if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) {
                kfree(priv);
                goto done;
        }

        ret = psb6970_reset_switch(&priv->dev);
        if (ret) {
                kfree(priv);
                goto done;
        }

        dev->phy_ptr = priv;

done:
        return ret;
}

static int psb6970_read_status(struct phy_device *phydev)
{
        phydev->speed = SPEED_100;
        phydev->duplex = DUPLEX_FULL;
        phydev->link = 1;

        phydev->state = PHY_RUNNING;
        netif_carrier_on(phydev->attached_dev);
        phydev->adjust_link(phydev->attached_dev);

        return 0;
}

static int psb6970_config_aneg(struct phy_device *phydev)
{
        return 0;
}

static int psb6970_probe(struct phy_device *pdev)
{
        return 0;
}

static void psb6970_remove(struct phy_device *pdev)
{
        struct psb6970_priv *priv = pdev->priv;

        if (!priv)
                return;

        if (pdev->mdio.addr == 0)
                unregister_switch(&priv->dev);
        kfree(priv);
}

static int psb6970_fixup(struct phy_device *dev)
{
        struct mii_bus *bus = dev->mdio.bus;
        u16 reg;

        /* look for the switch on the bus */
        reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK;
        if (reg != PSB6970_CI1_VAL)
                return 0;

        dev->phy_id = (reg << 16);
        dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK;

        return 0;
}

static struct phy_driver psb6970_driver = {
        .name = "Lantiq PSB6970",
        .phy_id = PSB6970_CI1_VAL << 16,
        .phy_id_mask = 0xffff0000,
        .features = PHY_BASIC_FEATURES,
        .probe = psb6970_probe,
        .remove = psb6970_remove,
        .config_init = &psb6970_config_init,
        .config_aneg = &psb6970_config_aneg,
        .read_status = &psb6970_read_status,
};

int __init psb6970_init(void)
{
        phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup);
        return phy_driver_register(&psb6970_driver, THIS_MODULE);
}

module_init(psb6970_init);

void __exit psb6970_exit(void)
{
        phy_driver_unregister(&psb6970_driver);
}

module_exit(psb6970_exit);

MODULE_DESCRIPTION("Lantiq PSB6970 Switch");
MODULE_AUTHOR("Ithamar R. Adema <ithamar.adema@team-embedded.nl>");
MODULE_LICENSE("GPL");