From 6a8bf3584a07b3d3c2cf93eb401acf21368417bd Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Tue, 17 Jul 2012 11:31:50 -0400 Subject: [PATCH 002/123] generic openwrt phy files Import of the openwrt phy files from trunk, as of this commit: ---------- commit c1d79f64eed0a7ac36b5b9bca52275b397bec424 Author: nbd Date: Mon Jul 16 16:26:51 2012 +0000 uboot-ar71xx: fix compile on recent mac os x versions git-svn-id: svn://svn.openwrt.org/openwrt/trunk@32750 3c298f89-4303-0410-b956-a3cf2f4a3e73 ---------- Due to the nature of how the openwrt source is managed, new files are found separate from the commits which actually make use of them, so that unused stuff could not easily be left behind, but it is what it is. Path to files in the repo is: target/linux/generic/files Repo is: git://nbd.name/openwrt.git Signed-off-by: Paul Gortmaker diff --git a/drivers/net/phy/adm6996.c b/drivers/net/phy/adm6996.c new file mode 100644 index 0000000..d4fe553 --- /dev/null +++ b/drivers/net/phy/adm6996.c @@ -0,0 +1,737 @@ +/* + * ADM6996 switch driver + * + * swconfig interface based on ar8216.c + * + * Copyright (c) 2008 Felix Fietkau + * VLAN support Copyright (c) 2010, 2011 Peter Lebbing + * + * 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 + */ + +/*#define DEBUG 1*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "adm6996.h" + +MODULE_DESCRIPTION("Infineon ADM6996 Switch"); +MODULE_AUTHOR("Felix Fietkau, Peter Lebbing "); +MODULE_LICENSE("GPL"); + +enum adm6996_model { + ADM6996FC, + ADM6996M +}; + +static const char * const adm6996_model_name[] = +{ + "ADM6996FC", + "ADM6996M" +}; + +struct adm6996_priv { + struct switch_dev dev; + struct phy_device *phydev; + + enum adm6996_model model; + + bool enable_vlan; + bool vlan_enabled; /* Current hardware state */ + +#ifdef DEBUG + u16 addr; /* Debugging: register address to operate on */ +#endif + + u16 pvid[ADM_NUM_PORTS]; /* Primary VLAN ID */ + + u16 vlan_id[ADM_NUM_VLANS]; + u8 vlan_table[ADM_NUM_VLANS]; /* bitmap, 1 = port is member */ + u8 vlan_tagged[ADM_NUM_VLANS]; /* bitmap, 1 = tagged member */ + + struct mutex reg_mutex; + + /* use abstraction for regops, we want to add gpio support in the future */ + u16 (*read)(struct phy_device *phydev, enum admreg reg); + void (*write)(struct phy_device *phydev, enum admreg reg, u16 val); +}; + +#define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev) +#define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv) + +static inline u16 +r16(struct phy_device *pdev, enum admreg reg) +{ + return phy_to_adm(pdev)->read(pdev, reg); +} + +static inline void +w16(struct phy_device *pdev, enum admreg reg, u16 val) +{ + phy_to_adm(pdev)->write(pdev, reg, val); +} + + +static u16 +adm6996_read_mii_reg(struct phy_device *phydev, enum admreg reg) +{ + return phydev->bus->read(phydev->bus, PHYADDR(reg)); +} + +static void +adm6996_write_mii_reg(struct phy_device *phydev, enum admreg reg, u16 val) +{ + phydev->bus->write(phydev->bus, PHYADDR(reg), val); +} + +static int +adm6996_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 1) + return -EINVAL; + + priv->enable_vlan = val->value.i; + + return 0; +}; + +static int +adm6996_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = priv->enable_vlan; + + return 0; +}; + +#ifdef DEBUG + +static int +adm6996_set_addr(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 1023) + return -EINVAL; + + priv->addr = val->value.i; + + return 0; +}; + +static int +adm6996_get_addr(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = priv->addr; + + return 0; +}; + +static int +adm6996_set_data(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 65535) + return -EINVAL; + + w16(priv->phydev, priv->addr, val->value.i); + + return 0; +}; + +static int +adm6996_get_data(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = r16(priv->phydev, priv->addr); + + return 0; +}; + +#endif /* def DEBUG */ + +static int +adm6996_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg (&priv->phydev->dev, "set_pvid port %d vlan %d\n", port + , vlan); + + if (vlan > ADM_VLAN_MAX_ID) + return -EINVAL; + + priv->pvid[port] = vlan; + + return 0; +} + +static int +adm6996_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg (&priv->phydev->dev, "get_pvid port %d\n", port); + *vlan = priv->pvid[port]; + + return 0; +} + +static int +adm6996_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg (&priv->phydev->dev, "set_vid port %d vid %d\n", val->port_vlan, + val->value.i); + + if (val->value.i > ADM_VLAN_MAX_ID) + return -EINVAL; + + priv->vlan_id[val->port_vlan] = val->value.i; + + return 0; +}; + +static int +adm6996_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg (&priv->phydev->dev, "get_vid port %d\n", val->port_vlan); + + val->value.i = priv->vlan_id[val->port_vlan]; + + return 0; +}; + +static int +adm6996_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + u8 tagged = priv->vlan_tagged[val->port_vlan]; + int i; + + dev_dbg (&priv->phydev->dev, "get_ports port_vlan %d\n", + val->port_vlan); + + val->len = 0; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + + return 0; +}; + +static int +adm6996_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + u8 *ports = &priv->vlan_table[val->port_vlan]; + u8 *tagged = &priv->vlan_tagged[val->port_vlan]; + int i; + + dev_dbg (&priv->phydev->dev, "set_ports port_vlan %d ports", + val->port_vlan); + + *ports = 0; + *tagged = 0; + + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + +#ifdef DEBUG + pr_cont(" %d%s", p->id, + ((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" : + "")); +#endif + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) + *tagged |= (1 << p->id); + + *ports |= (1 << p->id); + } + +#ifdef DEBUG + pr_cont("\n"); +#endif + + return 0; +}; + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_enable_vlan(struct adm6996_priv *priv) +{ + u16 reg; + + reg = r16(priv->phydev, ADM_OTBE_P2_PVID); + reg &= ~(ADM_OTBE_MASK); + w16(priv->phydev, ADM_OTBE_P2_PVID, reg); + reg = r16(priv->phydev, ADM_IFNTE); + reg &= ~(ADM_IFNTE_MASK); + w16(priv->phydev, ADM_IFNTE, reg); + reg = r16(priv->phydev, ADM_VID_CHECK); + reg |= ADM_VID_CHECK_MASK; + w16(priv->phydev, ADM_VID_CHECK, reg); + reg = r16(priv->phydev, ADM_SYSC0); + reg |= ADM_NTTE; + reg &= ~(ADM_RVID1); + w16(priv->phydev, ADM_SYSC0, reg); + reg = r16(priv->phydev, ADM_SYSC3); + reg |= ADM_TBV; + w16(priv->phydev, ADM_SYSC3, reg); + +}; + +/* + * Disable VLANs + * + * Sets VLAN mapping for port-based VLAN with all ports connected to + * eachother (this is also the power-on default). + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_disable_vlan(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + reg = ADM_VLAN_FILT_MEMBER_MASK; + w16(priv->phydev, ADM_VLAN_FILT_L(i), reg); + reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1); + w16(priv->phydev, ADM_VLAN_FILT_H(i), reg); + } + + reg = r16(priv->phydev, ADM_OTBE_P2_PVID); + reg |= ADM_OTBE_MASK; + w16(priv->phydev, ADM_OTBE_P2_PVID, reg); + reg = r16(priv->phydev, ADM_IFNTE); + reg |= ADM_IFNTE_MASK; + w16(priv->phydev, ADM_IFNTE, reg); + reg = r16(priv->phydev, ADM_VID_CHECK); + reg &= ~(ADM_VID_CHECK_MASK); + w16(priv->phydev, ADM_VID_CHECK, reg); + reg = r16(priv->phydev, ADM_SYSC0); + reg &= ~(ADM_NTTE); + reg |= ADM_RVID1; + w16(priv->phydev, ADM_SYSC0, reg); + reg = r16(priv->phydev, ADM_SYSC3); + reg &= ~(ADM_TBV); + w16(priv->phydev, ADM_SYSC3, reg); +} + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_apply_port_pvids(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + reg = r16(priv->phydev, adm_portcfg[i]); + reg &= ~(ADM_PORTCFG_PVID_MASK); + reg |= ADM_PORTCFG_PVID(priv->pvid[i]); + w16(priv->phydev, adm_portcfg[i], reg); + } + + w16(priv->phydev, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0])); + w16(priv->phydev, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1])); + reg = r16(priv->phydev, ADM_OTBE_P2_PVID); + reg &= ~(ADM_P2_PVID_MASK); + reg |= ADM_P2_PVID_VAL(priv->pvid[2]); + w16(priv->phydev, ADM_OTBE_P2_PVID, reg); + reg = ADM_P3_PVID_VAL(priv->pvid[3]); + reg |= ADM_P4_PVID_VAL(priv->pvid[4]); + w16(priv->phydev, ADM_P3_P4_PVID, reg); + w16(priv->phydev, ADM_P5_PVID, ADM_P5_PVID_VAL(priv->pvid[5])); +} + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_apply_vlan_filters(struct adm6996_priv *priv) +{ + u8 ports, tagged; + u16 vid, reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + vid = priv->vlan_id[i]; + ports = priv->vlan_table[i]; + tagged = priv->vlan_tagged[i]; + + if (ports == 0) { + /* Disable VLAN entry */ + w16(priv->phydev, ADM_VLAN_FILT_H(i), 0); + w16(priv->phydev, ADM_VLAN_FILT_L(i), 0); + continue; + } + + reg = ADM_VLAN_FILT_MEMBER(ports); + reg |= ADM_VLAN_FILT_TAGGED(tagged); + w16(priv->phydev, ADM_VLAN_FILT_L(i), reg); + reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid); + w16(priv->phydev, ADM_VLAN_FILT_H(i), reg); + } +} + +static int +adm6996_hw_apply(struct switch_dev *dev) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg(&priv->phydev->dev, "hw_apply\n"); + + mutex_lock(&priv->reg_mutex); + + if (!priv->enable_vlan) { + if (priv->vlan_enabled) { + adm6996_disable_vlan(priv); + priv->vlan_enabled = 0; + } + goto out; + } + + if (!priv->vlan_enabled) { + adm6996_enable_vlan(priv); + priv->vlan_enabled = 1; + } + + adm6996_apply_port_pvids(priv); + adm6996_apply_vlan_filters(priv); + +out: + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +/* + * Reset the switch + * + * The ADM6996 can't do a software-initiated reset, so we just initialise the + * registers we support in this driver. + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_perform_reset (struct adm6996_priv *priv) +{ + int i; + + /* initialize port and vlan settings */ + for (i = 0; i < ADM_NUM_PORTS - 1; i++) { + w16(priv->phydev, adm_portcfg[i], ADM_PORTCFG_INIT | + ADM_PORTCFG_PVID(0)); + } + w16(priv->phydev, adm_portcfg[5], ADM_PORTCFG_CPU); + + /* reset all PHY ports */ + for (i = 0; i < ADM_PHY_PORTS; i++) { + w16(priv->phydev, ADM_PHY_PORT(i), ADM_PHYCFG_INIT); + } + + priv->enable_vlan = 0; + priv->vlan_enabled = 0; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + priv->pvid[i] = 0; + } + + for (i = 0; i < ADM_NUM_VLANS; i++) { + priv->vlan_id[i] = i; + priv->vlan_table[i] = 0; + priv->vlan_tagged[i] = 0; + } + + if (priv->model == ADM6996M) { + /* Clear VLAN priority map so prio's are unused */ + w16 (priv->phydev, ADM_VLAN_PRIOMAP, 0); + + adm6996_disable_vlan(priv); + adm6996_apply_port_pvids(priv); + } +} + +static int +adm6996_reset_switch(struct switch_dev *dev) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg (&priv->phydev->dev, "reset\n"); + mutex_lock(&priv->reg_mutex); + adm6996_perform_reset (priv); + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static struct switch_attr adm6996_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLANs", + .set = adm6996_set_enable_vlan, + .get = adm6996_get_enable_vlan, + }, +#ifdef DEBUG + { + .type = SWITCH_TYPE_INT, + .name = "addr", + .description = + "Direct register access: set register address (0 - 1023)", + .set = adm6996_set_addr, + .get = adm6996_get_addr, + }, + { + .type = SWITCH_TYPE_INT, + .name = "data", + .description = + "Direct register access: read/write to register (0 - 65535)", + .set = adm6996_set_data, + .get = adm6996_get_data, + }, +#endif /* def DEBUG */ +}; + +static struct switch_attr adm6996_port[] = { +}; + +static struct switch_attr adm6996_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID", + .set = adm6996_set_vid, + .get = adm6996_get_vid, + }, +}; + +static const struct switch_dev_ops adm6996_ops = { + .attr_global = { + .attr = adm6996_globals, + .n_attr = ARRAY_SIZE(adm6996_globals), + }, + .attr_port = { + .attr = adm6996_port, + .n_attr = ARRAY_SIZE(adm6996_port), + }, + .attr_vlan = { + .attr = adm6996_vlan, + .n_attr = ARRAY_SIZE(adm6996_vlan), + }, + .get_port_pvid = adm6996_get_pvid, + .set_port_pvid = adm6996_set_pvid, + .get_vlan_ports = adm6996_get_ports, + .set_vlan_ports = adm6996_set_ports, + .apply_config = adm6996_hw_apply, + .reset_switch = adm6996_reset_switch, +}; + +static int adm6996_config_init(struct phy_device *pdev) +{ + struct adm6996_priv *priv; + struct switch_dev *swdev; + + int ret; + u16 test, old; + + pdev->supported = ADVERTISED_100baseT_Full; + pdev->advertising = ADVERTISED_100baseT_Full; + + if (pdev->addr != 0) { + pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n" + , pdev->attached_dev->name, pdev->addr); + return 0; + } + + priv = kzalloc(sizeof(struct adm6996_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + mutex_init(&priv->reg_mutex); + priv->phydev = pdev; + priv->read = adm6996_read_mii_reg; + priv->write = adm6996_write_mii_reg; + pdev->priv = priv; + + /* Detect type of chip */ + old = r16(pdev, ADM_VID_CHECK); + test = old ^ (1 << 12); + w16(pdev, ADM_VID_CHECK, test); + test ^= r16(pdev, ADM_VID_CHECK); + if (test & (1 << 12)) { + /* + * Bit 12 of this register is read-only. + * This is the FC model. + */ + priv->model = ADM6996FC; + } else { + /* Bit 12 is read-write. This is the M model. */ + priv->model = ADM6996M; + w16(pdev, ADM_VID_CHECK, old); + } + + swdev = &priv->dev; + swdev->name = (adm6996_model_name[priv->model]); + swdev->cpu_port = ADM_CPU_PORT; + swdev->ports = ADM_NUM_PORTS; + swdev->vlans = ADM_NUM_VLANS; + swdev->ops = &adm6996_ops; + + pr_info ("%s: %s model PHY found.\n", pdev->attached_dev->name, + swdev->name); + + mutex_lock(&priv->reg_mutex); + adm6996_perform_reset (priv); + mutex_unlock(&priv->reg_mutex); + + if (priv->model == ADM6996M) { + if ((ret = register_switch(swdev, pdev->attached_dev)) < 0) { + kfree(priv); + return ret; + } + } + + return 0; +} + +/* + * Warning: phydev->priv is NULL if phydev->addr != 0 + */ +static int adm6996_read_status(struct phy_device *phydev) +{ + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + return 0; +} + +/* + * Warning: phydev->priv is NULL if phydev->addr != 0 + */ +static int adm6996_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int adm6996_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->bus; + u16 reg; + + /* Our custom registers are at PHY addresses 0-10. Claim those. */ + if (dev->addr > 10) + return 0; + + /* look for the switch on the bus */ + reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK; + if (reg != ADM_SIG0_VAL) + return 0; + + reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK; + if (reg != ADM_SIG1_VAL) + return 0; + + dev->phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL; + + return 0; +} + +static int adm6996_probe(struct phy_device *pdev) +{ + return 0; +} + +static void adm6996_remove(struct phy_device *pdev) +{ + struct adm6996_priv *priv = phy_to_adm(pdev); + + if (priv != NULL && priv->model == ADM6996M) + unregister_switch(&priv->dev); + + kfree(priv); +} + + +static struct phy_driver adm6996_driver = { + .name = "Infineon ADM6996", + .phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = adm6996_probe, + .remove = adm6996_remove, + .config_init = &adm6996_config_init, + .config_aneg = &adm6996_config_aneg, + .read_status = &adm6996_read_status, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init adm6996_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, adm6996_fixup); + return phy_driver_register(&adm6996_driver); +} + +static void __exit adm6996_exit(void) +{ + phy_driver_unregister(&adm6996_driver); +} + +module_init(adm6996_init); +module_exit(adm6996_exit); diff --git a/drivers/net/phy/adm6996.h b/drivers/net/phy/adm6996.h new file mode 100644 index 0000000..0990ca9 --- /dev/null +++ b/drivers/net/phy/adm6996.h @@ -0,0 +1,162 @@ +/* + * ADM6996 switch driver + * + * Copyright (c) 2008 Felix Fietkau + * Copyright (c) 2010,2011 Peter Lebbing + * + * 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 + */ +#ifndef __ADM6996_H +#define __ADM6996_H + +/* + * ADM_PHY_PORTS: Number of ports with a PHY. + * We only control ports 0 to 3, because if 4 is connected, it is most likely + * not connected to the switch but to a separate MII and MAC for the WAN port. + */ +#define ADM_PHY_PORTS 4 +#define ADM_NUM_PORTS 6 +#define ADM_CPU_PORT 5 + +#define ADM_NUM_VLANS 16 +#define ADM_VLAN_MAX_ID 4094 + +enum admreg { + ADM_EEPROM_BASE = 0x0, + ADM_P0_CFG = ADM_EEPROM_BASE + 1, + ADM_P1_CFG = ADM_EEPROM_BASE + 3, + ADM_P2_CFG = ADM_EEPROM_BASE + 5, + ADM_P3_CFG = ADM_EEPROM_BASE + 7, + ADM_P4_CFG = ADM_EEPROM_BASE + 8, + ADM_P5_CFG = ADM_EEPROM_BASE + 9, + ADM_SYSC0 = ADM_EEPROM_BASE + 0xa, + ADM_VLAN_PRIOMAP = ADM_EEPROM_BASE + 0xe, + ADM_SYSC3 = ADM_EEPROM_BASE + 0x11, + /* Input Force No Tag Enable */ + ADM_IFNTE = ADM_EEPROM_BASE + 0x20, + ADM_VID_CHECK = ADM_EEPROM_BASE + 0x26, + ADM_P0_PVID = ADM_EEPROM_BASE + 0x28, + ADM_P1_PVID = ADM_EEPROM_BASE + 0x29, + /* Output Tag Bypass Enable and P2 PVID */ + ADM_OTBE_P2_PVID = ADM_EEPROM_BASE + 0x2a, + ADM_P3_P4_PVID = ADM_EEPROM_BASE + 0x2b, + ADM_P5_PVID = ADM_EEPROM_BASE + 0x2c, + ADM_EEPROM_EXT_BASE = 0x40, +#define ADM_VLAN_FILT_L(n) (ADM_EEPROM_EXT_BASE + 2 * (n)) +#define ADM_VLAN_FILT_H(n) (ADM_EEPROM_EXT_BASE + 1 + 2 * (n)) + ADM_COUNTER_BASE = 0xa0, + ADM_SIG0 = ADM_COUNTER_BASE + 0, + ADM_SIG1 = ADM_COUNTER_BASE + 1, + ADM_PHY_BASE = 0x200, +#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n)) +}; + +/* Chip identification patterns */ +#define ADM_SIG0_MASK 0xffff +#define ADM_SIG0_VAL 0x1023 +#define ADM_SIG1_MASK 0xffff +#define ADM_SIG1_VAL 0x0007 + +enum { + ADM_PHYCFG_COLTST = (1 << 7), /* Enable collision test */ + ADM_PHYCFG_DPLX = (1 << 8), /* Enable full duplex */ + ADM_PHYCFG_ANEN_RST = (1 << 9), /* Restart auto negotiation (self clear) */ + ADM_PHYCFG_ISO = (1 << 10), /* Isolate PHY */ + ADM_PHYCFG_PDN = (1 << 11), /* Power down PHY */ + ADM_PHYCFG_ANEN = (1 << 12), /* Enable auto negotiation */ + ADM_PHYCFG_SPEED_100 = (1 << 13), /* Enable 100 Mbit/s */ + ADM_PHYCFG_LPBK = (1 << 14), /* Enable loopback operation */ + ADM_PHYCFG_RST = (1 << 15), /* Reset the port (self clear) */ + ADM_PHYCFG_INIT = ( + ADM_PHYCFG_RST | + ADM_PHYCFG_SPEED_100 | + ADM_PHYCFG_ANEN | + ADM_PHYCFG_ANEN_RST + ) +}; + +enum { + ADM_PORTCFG_FC = (1 << 0), /* Enable 802.x flow control */ + ADM_PORTCFG_AN = (1 << 1), /* Enable auto-negotiation */ + ADM_PORTCFG_SPEED_100 = (1 << 2), /* Enable 100 Mbit/s */ + ADM_PORTCFG_DPLX = (1 << 3), /* Enable full duplex */ + ADM_PORTCFG_OT = (1 << 4), /* Output tagged packets */ + ADM_PORTCFG_PD = (1 << 5), /* Port disable */ + ADM_PORTCFG_TV_PRIO = (1 << 6), /* 0 = VLAN based priority + * 1 = TOS based priority */ + ADM_PORTCFG_PPE = (1 << 7), /* Port based priority enable */ + ADM_PORTCFG_PP_S = (1 << 8), /* Port based priority, 2 bits */ + ADM_PORTCFG_PVID_BASE = (1 << 10), /* Primary VLAN id, 4 bits */ + ADM_PORTCFG_FSE = (1 << 14), /* Fx select enable */ + ADM_PORTCFG_CAM = (1 << 15), /* Crossover Auto MDIX */ + + ADM_PORTCFG_INIT = ( + ADM_PORTCFG_FC | + ADM_PORTCFG_AN | + ADM_PORTCFG_SPEED_100 | + ADM_PORTCFG_DPLX | + ADM_PORTCFG_CAM + ), + ADM_PORTCFG_CPU = ( + ADM_PORTCFG_FC | + ADM_PORTCFG_SPEED_100 | + ADM_PORTCFG_OT | + ADM_PORTCFG_DPLX + ), +}; + +#define ADM_PORTCFG_PPID(n) ((n & 0x3) << 8) +#define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10) +#define ADM_PORTCFG_PVID_MASK (0xf << 10) + +#define ADM_IFNTE_MASK (0x3f << 9) +#define ADM_VID_CHECK_MASK (0x3f << 6) + +#define ADM_P0_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P1_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P2_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P3_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P4_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 8) +#define ADM_P5_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P2_PVID_MASK 0xff + +#define ADM_OTBE(n) (((n) & 0x3f) << 8) +#define ADM_OTBE_MASK (0x3f << 8) + +/* ADM_SYSC0 */ +enum { + ADM_NTTE = (1 << 2), /* New Tag Transmit Enable */ + ADM_RVID1 = (1 << 8) /* Replace VLAN ID 1 */ +}; + +/* Tag Based VLAN in ADM_SYSC3 */ +#define ADM_TBV (1 << 5) + +static const u8 adm_portcfg[] = { + [0] = ADM_P0_CFG, + [1] = ADM_P1_CFG, + [2] = ADM_P2_CFG, + [3] = ADM_P3_CFG, + [4] = ADM_P4_CFG, + [5] = ADM_P5_CFG, +}; + +/* Fields in ADM_VLAN_FILT_L(x) */ +#define ADM_VLAN_FILT_FID(n) (((n) & 0xf) << 12) +#define ADM_VLAN_FILT_TAGGED(n) (((n) & 0x3f) << 6) +#define ADM_VLAN_FILT_MEMBER(n) (((n) & 0x3f) << 0) +#define ADM_VLAN_FILT_MEMBER_MASK 0x3f +/* Fields in ADM_VLAN_FILT_H(x) */ +#define ADM_VLAN_FILT_VALID (1 << 15) +#define ADM_VLAN_FILT_VID(n) (((n) & 0xfff) << 0) + + +/* + * Split the register address in phy id and register + * it will get combined again by the mdio bus op + */ +#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) + +#endif diff --git a/drivers/net/phy/ar8216.c b/drivers/net/phy/ar8216.c new file mode 100644 index 0000000..469e528 --- /dev/null +++ b/drivers/net/phy/ar8216.c @@ -0,0 +1,1535 @@ +/* + * ar8216.c: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * Copyright (C) 2011-2012 Gabor Juhos + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ar8216.h" + +/* size of the vlan table */ +#define AR8X16_MAX_VLANS 128 +#define AR8X16_PROBE_RETRIES 10 +#define AR8X16_MAX_PORTS 8 + +struct ar8216_priv; + +#define AR8XXX_CAP_GIGE BIT(0) + +enum { + AR8XXX_VER_AR8216 = 0x01, + AR8XXX_VER_AR8236 = 0x03, + AR8XXX_VER_AR8316 = 0x10, + AR8XXX_VER_AR8327 = 0x12, +}; + +struct ar8xxx_chip { + unsigned long caps; + + int (*hw_init)(struct ar8216_priv *priv); + void (*init_globals)(struct ar8216_priv *priv); + void (*init_port)(struct ar8216_priv *priv, int port); + void (*setup_port)(struct ar8216_priv *priv, int port, u32 egress, + u32 ingress, u32 members, u32 pvid); + u32 (*read_port_status)(struct ar8216_priv *priv, int port); + int (*atu_flush)(struct ar8216_priv *priv); + void (*vtu_flush)(struct ar8216_priv *priv); + void (*vtu_load_vlan)(struct ar8216_priv *priv, u32 vid, u32 port_mask); +}; + +struct ar8216_priv { + struct switch_dev dev; + struct phy_device *phy; + u32 (*read)(struct ar8216_priv *priv, int reg); + void (*write)(struct ar8216_priv *priv, int reg, u32 val); + const struct net_device_ops *ndo_old; + struct net_device_ops ndo; + struct mutex reg_mutex; + u8 chip_ver; + u8 chip_rev; + const struct ar8xxx_chip *chip; + bool initialized; + bool port4_phy; + char buf[80]; + + bool init; + bool mii_lo_first; + + /* all fields below are cleared on reset */ + bool vlan; + u16 vlan_id[AR8X16_MAX_VLANS]; + u8 vlan_table[AR8X16_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[AR8X16_MAX_PORTS]; +}; + +#define to_ar8216(_dev) container_of(_dev, struct ar8216_priv, dev) + +static inline bool ar8xxx_has_gige(struct ar8216_priv *priv) +{ + return priv->chip->caps & AR8XXX_CAP_GIGE; +} + +static inline bool chip_is_ar8216(struct ar8216_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8216; +} + +static inline bool chip_is_ar8236(struct ar8216_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8236; +} + +static inline bool chip_is_ar8316(struct ar8216_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8316; +} + +static inline bool chip_is_ar8327(struct ar8216_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8327; +} + +static inline void +split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) +{ + regaddr >>= 1; + *r1 = regaddr & 0x1e; + + regaddr >>= 5; + *r2 = regaddr & 0x7; + + regaddr >>= 3; + *page = regaddr & 0x1ff; +} + +static u32 +ar8216_mii_read(struct ar8216_priv *priv, int reg) +{ + struct phy_device *phy = priv->phy; + struct mii_bus *bus = phy->bus; + u16 r1, r2, page; + u16 lo, hi; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + usleep_range(1000, 2000); /* wait for the page switch to propagate */ + lo = bus->read(bus, 0x10 | r2, r1); + hi = bus->read(bus, 0x10 | r2, r1 + 1); + + mutex_unlock(&bus->mdio_lock); + + return (hi << 16) | lo; +} + +static void +ar8216_mii_write(struct ar8216_priv *priv, int reg, u32 val) +{ + struct phy_device *phy = priv->phy; + struct mii_bus *bus = phy->bus; + u16 r1, r2, r3; + u16 lo, hi; + + split_addr((u32) reg, &r1, &r2, &r3); + lo = val & 0xffff; + hi = (u16) (val >> 16); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, r3); + usleep_range(1000, 2000); /* wait for the page switch to propagate */ + if (priv->mii_lo_first) { + bus->write(bus, 0x10 | r2, r1, lo); + bus->write(bus, 0x10 | r2, r1 + 1, hi); + } else { + bus->write(bus, 0x10 | r2, r1 + 1, hi); + bus->write(bus, 0x10 | r2, r1, lo); + } + + mutex_unlock(&bus->mdio_lock); +} + +static void +ar8216_phy_dbg_write(struct ar8216_priv *priv, int phy_addr, + u16 dbg_addr, u16 dbg_data) +{ + struct mii_bus *bus = priv->phy->bus; + + mutex_lock(&bus->mdio_lock); + bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr); + bus->write(bus, phy_addr, MII_ATH_DBG_DATA, dbg_data); + mutex_unlock(&bus->mdio_lock); +} + +static void +ar8216_phy_mmd_write(struct ar8216_priv *priv, int phy_addr, u16 addr, u16 data) +{ + struct mii_bus *bus = priv->phy->bus; + + mutex_lock(&bus->mdio_lock); + bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr); + bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data); + mutex_unlock(&bus->mdio_lock); +} + +static u32 +ar8216_rmw(struct ar8216_priv *priv, int reg, u32 mask, u32 val) +{ + u32 v; + + lockdep_assert_held(&priv->reg_mutex); + + v = priv->read(priv, reg); + v &= ~mask; + v |= val; + priv->write(priv, reg, v); + + return v; +} + +static void +ar8216_read_port_link(struct ar8216_priv *priv, int port, + struct switch_port_link *link) +{ + u32 status; + u32 speed; + + memset(link, '\0', sizeof(*link)); + + status = priv->chip->read_port_status(priv, port); + + link->aneg = !!(status & AR8216_PORT_STATUS_LINK_AUTO); + if (link->aneg) { + link->link = !!(status & AR8216_PORT_STATUS_LINK_UP); + if (!link->link) + return; + } else { + link->link = true; + } + + link->duplex = !!(status & AR8216_PORT_STATUS_DUPLEX); + link->tx_flow = !!(status & AR8216_PORT_STATUS_TXFLOW); + link->rx_flow = !!(status & AR8216_PORT_STATUS_RXFLOW); + + speed = (status & AR8216_PORT_STATUS_SPEED) >> + AR8216_PORT_STATUS_SPEED_S; + + switch (speed) { + case AR8216_PORT_SPEED_10M: + link->speed = SWITCH_PORT_SPEED_10; + break; + case AR8216_PORT_SPEED_100M: + link->speed = SWITCH_PORT_SPEED_100; + break; + case AR8216_PORT_SPEED_1000M: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } +} + +static struct sk_buff * +ar8216_mangle_tx(struct net_device *dev, struct sk_buff *skb) +{ + struct ar8216_priv *priv = dev->phy_ptr; + unsigned char *buf; + + if (unlikely(!priv)) + goto error; + + if (!priv->vlan) + goto send; + + if (unlikely(skb_headroom(skb) < 2)) { + if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) + goto error; + } + + buf = skb_push(skb, 2); + buf[0] = 0x10; + buf[1] = 0x80; + +send: + return skb; + +error: + dev_kfree_skb_any(skb); + return NULL; +} + +static void +ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb) +{ + struct ar8216_priv *priv; + unsigned char *buf; + int port, vlan; + + priv = dev->phy_ptr; + if (!priv) + return; + + /* don't strip the header if vlan mode is disabled */ + if (!priv->vlan) + return; + + /* strip header, get vlan id */ + buf = skb->data; + skb_pull(skb, 2); + + /* check for vlan header presence */ + if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) + return; + + port = buf[0] & 0xf; + + /* no need to fix up packets coming from a tagged source */ + if (priv->vlan_tagged & (1 << port)) + return; + + /* lookup port vid from local table, the switch passes an invalid vlan id */ + vlan = priv->vlan_id[priv->pvid[port]]; + + buf[14 + 2] &= 0xf0; + buf[14 + 2] |= vlan >> 8; + buf[15 + 2] = vlan & 0xff; +} + +static int +ar8216_wait_bit(struct ar8216_priv *priv, int reg, u32 mask, u32 val) +{ + int timeout = 20; + u32 t = 0; + + while (1) { + t = priv->read(priv, reg); + if ((t & mask) == val) + return 0; + + if (timeout-- <= 0) + break; + + udelay(10); + } + + pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n", + (unsigned int) reg, t, mask, val); + return -ETIMEDOUT; +} + +static void +ar8216_vtu_op(struct ar8216_priv *priv, u32 op, u32 val) +{ + if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) + return; + if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { + val &= AR8216_VTUDATA_MEMBER; + val |= AR8216_VTUDATA_VALID; + priv->write(priv, AR8216_REG_VTU_DATA, val); + } + op |= AR8216_VTU_ACTIVE; + priv->write(priv, AR8216_REG_VTU, op); +} + +static void +ar8216_vtu_flush(struct ar8216_priv *priv) +{ + ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); +} + +static void +ar8216_vtu_load_vlan(struct ar8216_priv *priv, u32 vid, u32 port_mask) +{ + u32 op; + + op = AR8216_VTU_OP_LOAD | (vid << AR8216_VTU_VID_S); + ar8216_vtu_op(priv, op, port_mask); +} + +static int +ar8216_atu_flush(struct ar8216_priv *priv) +{ + int ret; + + ret = ar8216_wait_bit(priv, AR8216_REG_ATU, AR8216_ATU_ACTIVE, 0); + if (!ret) + priv->write(priv, AR8216_REG_ATU, AR8216_ATU_OP_FLUSH); + + return ret; +} + +static u32 +ar8216_read_port_status(struct ar8216_priv *priv, int port) +{ + return priv->read(priv, AR8216_REG_PORT_STATUS(port)); +} + +static void +ar8216_setup_port(struct ar8216_priv *priv, int port, u32 egress, u32 ingress, + u32 members, u32 pvid) +{ + u32 header; + + if (chip_is_ar8216(priv) && priv->vlan && port == AR8216_PORT_CPU) + header = AR8216_PORT_CTRL_HEADER; + else + header = 0; + + ar8216_rmw(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | + AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | + AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, + AR8216_PORT_CTRL_LEARN | header | + (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | + (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + + ar8216_rmw(priv, AR8216_REG_PORT_VLAN(port), + AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | + AR8216_PORT_VLAN_DEFAULT_ID, + (members << AR8216_PORT_VLAN_DEST_PORTS_S) | + (ingress << AR8216_PORT_VLAN_MODE_S) | + (pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); +} + +static int +ar8216_hw_init(struct ar8216_priv *priv) +{ + return 0; +} + +static void +ar8216_init_globals(struct ar8216_priv *priv) +{ + /* standard atheros magic */ + priv->write(priv, 0x38, 0xc000050e); + + ar8216_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8216_GCTRL_MTU, 1518 + 8 + 2); +} + +static void +ar8216_init_port(struct ar8216_priv *priv, int port) +{ + /* Enable port learning and tx */ + priv->write(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | + (4 << AR8216_PORT_CTRL_STATE_S)); + + priv->write(priv, AR8216_REG_PORT_VLAN(port), 0); + + if (port == AR8216_PORT_CPU) { + priv->write(priv, AR8216_REG_PORT_STATUS(port), + AR8216_PORT_STATUS_LINK_UP | + (ar8xxx_has_gige(priv) ? + AR8216_PORT_SPEED_1000M : AR8216_PORT_SPEED_100M) | + AR8216_PORT_STATUS_TXMAC | + AR8216_PORT_STATUS_RXMAC | + (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_RXFLOW : 0) | + (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_TXFLOW : 0) | + AR8216_PORT_STATUS_DUPLEX); + } else { + priv->write(priv, AR8216_REG_PORT_STATUS(port), + AR8216_PORT_STATUS_LINK_AUTO); + } +} + +static const struct ar8xxx_chip ar8216_chip = { + .hw_init = ar8216_hw_init, + .init_globals = ar8216_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8216_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, +}; + +static void +ar8236_setup_port(struct ar8216_priv *priv, int port, u32 egress, u32 ingress, + u32 members, u32 pvid) +{ + ar8216_rmw(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | + AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | + AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, + AR8216_PORT_CTRL_LEARN | + (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | + (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + + ar8216_rmw(priv, AR8236_REG_PORT_VLAN(port), + AR8236_PORT_VLAN_DEFAULT_ID, + (pvid << AR8236_PORT_VLAN_DEFAULT_ID_S)); + + ar8216_rmw(priv, AR8236_REG_PORT_VLAN2(port), + AR8236_PORT_VLAN2_VLAN_MODE | + AR8236_PORT_VLAN2_MEMBER, + (ingress << AR8236_PORT_VLAN2_VLAN_MODE_S) | + (members << AR8236_PORT_VLAN2_MEMBER_S)); +} + +static int +ar8236_hw_init(struct ar8216_priv *priv) +{ + int i; + struct mii_bus *bus; + + if (priv->initialized) + return 0; + + /* Initialize the PHYs */ + bus = priv->phy->bus; + for (i = 0; i < 5; i++) { + mdiobus_write(bus, i, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | + ADVERTISE_PAUSE_ASYM); + mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); + } + msleep(1000); + + priv->initialized = true; + return 0; +} + +static void +ar8236_init_globals(struct ar8216_priv *priv) +{ + /* enable jumbo frames */ + ar8216_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8316_GCTRL_MTU, 9018 + 8 + 2); +} + +static const struct ar8xxx_chip ar8236_chip = { + .hw_init = ar8236_hw_init, + .init_globals = ar8236_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8236_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, +}; + +static int +ar8316_hw_init(struct ar8216_priv *priv) +{ + int i; + u32 val, newval; + struct mii_bus *bus; + + val = priv->read(priv, 0x8); + + if (priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { + if (priv->port4_phy) { + /* value taken from Ubiquiti RouterStation Pro */ + newval = 0x81461bea; + printk(KERN_INFO "ar8316: Using port 4 as PHY\n"); + } else { + newval = 0x01261be2; + printk(KERN_INFO "ar8316: Using port 4 as switch port\n"); + } + } else if (priv->phy->interface == PHY_INTERFACE_MODE_GMII) { + /* value taken from AVM Fritz!Box 7390 sources */ + newval = 0x010e5b71; + } else { + /* no known value for phy interface */ + printk(KERN_ERR "ar8316: unsupported mii mode: %d.\n", + priv->phy->interface); + return -EINVAL; + } + + if (val == newval) + goto out; + + priv->write(priv, 0x8, newval); + + /* Initialize the ports */ + bus = priv->phy->bus; + for (i = 0; i < 5; i++) { + if ((i == 4) && priv->port4_phy && + priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { + /* work around for phy4 rgmii mode */ + ar8216_phy_dbg_write(priv, i, 0x12, 0x480c); + /* rx delay */ + ar8216_phy_dbg_write(priv, i, 0x0, 0x824e); + /* tx delay */ + ar8216_phy_dbg_write(priv, i, 0x5, 0x3d47); + msleep(1000); + } + + /* initialize the port itself */ + mdiobus_write(bus, i, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); + mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); + mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); + msleep(1000); + } + +out: + priv->initialized = true; + return 0; +} + +static void +ar8316_init_globals(struct ar8216_priv *priv) +{ + /* standard atheros magic */ + priv->write(priv, 0x38, 0xc000050e); + + /* enable cpu port to receive multicast and broadcast frames */ + priv->write(priv, AR8216_REG_FLOOD_MASK, 0x003f003f); + + /* enable jumbo frames */ + ar8216_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8316_GCTRL_MTU, 9018 + 8 + 2); +} + +static const struct ar8xxx_chip ar8316_chip = { + .caps = AR8XXX_CAP_GIGE, + .hw_init = ar8316_hw_init, + .init_globals = ar8316_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8216_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, +}; + +static u32 +ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg) +{ + u32 t; + + if (!cfg) + return 0; + + t = 0; + switch (cfg->mode) { + case AR8327_PAD_NC: + break; + + case AR8327_PAD_MAC2MAC_MII: + t = AR8327_PAD_MAC_MII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_MAC_MII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_MAC_MII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC2MAC_GMII: + t = AR8327_PAD_MAC_GMII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_MAC_GMII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_MAC_GMII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC_SGMII: + t = AR8327_PAD_SGMII_EN; + break; + + case AR8327_PAD_MAC2PHY_MII: + t = AR8327_PAD_PHY_MII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_PHY_MII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_PHY_MII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC2PHY_GMII: + t = AR8327_PAD_PHY_GMII_EN; + if (cfg->pipe_rxclk_sel) + t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL; + if (cfg->rxclk_sel) + t |= AR8327_PAD_PHY_GMII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_PHY_GMII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC_RGMII: + t = AR8327_PAD_RGMII_EN; + t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; + t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; + if (cfg->rxclk_delay_en) + t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; + if (cfg->txclk_delay_en) + t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; + break; + + case AR8327_PAD_PHY_GMII: + t = AR8327_PAD_PHYX_GMII_EN; + break; + + case AR8327_PAD_PHY_RGMII: + t = AR8327_PAD_PHYX_RGMII_EN; + break; + + case AR8327_PAD_PHY_MII: + t = AR8327_PAD_PHYX_MII_EN; + break; + } + + return t; +} + +static void +ar8327_phy_fixup(struct ar8216_priv *priv, int phy) +{ + switch (priv->chip_rev) { + case 1: + /* For 100M waveform */ + ar8216_phy_dbg_write(priv, phy, 0, 0x02ea); + /* Turn on Gigabit clock */ + ar8216_phy_dbg_write(priv, phy, 0x3d, 0x68a0); + break; + + case 2: + ar8216_phy_mmd_write(priv, phy, 0x7, 0x3c); + ar8216_phy_mmd_write(priv, phy, 0x4007, 0x0); + /* fallthrough */ + case 4: + ar8216_phy_mmd_write(priv, phy, 0x3, 0x800d); + ar8216_phy_mmd_write(priv, phy, 0x4003, 0x803f); + + ar8216_phy_dbg_write(priv, phy, 0x3d, 0x6860); + ar8216_phy_dbg_write(priv, phy, 0x5, 0x2c46); + ar8216_phy_dbg_write(priv, phy, 0x3c, 0x6000); + break; + } +} + +static int +ar8327_hw_init(struct ar8216_priv *priv) +{ + struct ar8327_platform_data *pdata; + struct ar8327_led_cfg *led_cfg; + struct mii_bus *bus; + u32 pos, new_pos; + u32 t; + int i; + + pdata = priv->phy->dev.platform_data; + if (!pdata) + return -EINVAL; + + t = ar8327_get_pad_cfg(pdata->pad0_cfg); + priv->write(priv, AR8327_REG_PAD0_MODE, t); + t = ar8327_get_pad_cfg(pdata->pad5_cfg); + priv->write(priv, AR8327_REG_PAD5_MODE, t); + t = ar8327_get_pad_cfg(pdata->pad6_cfg); + priv->write(priv, AR8327_REG_PAD6_MODE, t); + + pos = priv->read(priv, AR8327_REG_POWER_ON_STRIP); + new_pos = pos; + + led_cfg = pdata->led_cfg; + if (led_cfg) { + if (led_cfg->open_drain) + new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN; + else + new_pos &= ~AR8327_POWER_ON_STRIP_LED_OPEN_EN; + + priv->write(priv, AR8327_REG_LED_CTRL0, led_cfg->led_ctrl0); + priv->write(priv, AR8327_REG_LED_CTRL1, led_cfg->led_ctrl1); + priv->write(priv, AR8327_REG_LED_CTRL2, led_cfg->led_ctrl2); + priv->write(priv, AR8327_REG_LED_CTRL3, led_cfg->led_ctrl3); + } + + if (new_pos != pos) { + new_pos |= AR8327_POWER_ON_STRIP_POWER_ON_SEL; + priv->write(priv, AR8327_REG_POWER_ON_STRIP, new_pos); + } + + bus = priv->phy->bus; + for (i = 0; i < AR8327_NUM_PHYS; i++) { + ar8327_phy_fixup(priv, i); + + /* start aneg on the PHY */ + mdiobus_write(bus, i, MII_ADVERTISE, ADVERTISE_ALL | + ADVERTISE_PAUSE_CAP | + ADVERTISE_PAUSE_ASYM); + mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); + mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); + } + + msleep(1000); + + return 0; +} + +static void +ar8327_init_globals(struct ar8216_priv *priv) +{ + u32 t; + + /* enable CPU port and disable mirror port */ + t = AR8327_FWD_CTRL0_CPU_PORT_EN | + AR8327_FWD_CTRL0_MIRROR_PORT; + priv->write(priv, AR8327_REG_FWD_CTRL0, t); + + /* forward multicast and broadcast frames to CPU */ + t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) | + (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) | + (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S); + priv->write(priv, AR8327_REG_FWD_CTRL1, t); + + /* setup MTU */ + ar8216_rmw(priv, AR8327_REG_MAX_FRAME_SIZE, + AR8327_MAX_FRAME_SIZE_MTU, 1518 + 8 + 2); +} + +static void +ar8327_init_cpuport(struct ar8216_priv *priv) +{ + struct ar8327_platform_data *pdata; + struct ar8327_port_cfg *cfg; + u32 t; + + pdata = priv->phy->dev.platform_data; + if (!pdata) + return; + + cfg = &pdata->cpuport_cfg; + if (!cfg->force_link) { + priv->write(priv, AR8327_REG_PORT_STATUS(AR8216_PORT_CPU), + AR8216_PORT_STATUS_LINK_AUTO); + return; + } + + t = AR8216_PORT_STATUS_TXMAC | AR8216_PORT_STATUS_RXMAC; + t |= cfg->duplex ? AR8216_PORT_STATUS_DUPLEX : 0; + t |= cfg->rxpause ? AR8216_PORT_STATUS_RXFLOW : 0; + t |= cfg->txpause ? AR8216_PORT_STATUS_TXFLOW : 0; + switch (cfg->speed) { + case AR8327_PORT_SPEED_10: + t |= AR8216_PORT_SPEED_10M; + break; + case AR8327_PORT_SPEED_100: + t |= AR8216_PORT_SPEED_100M; + break; + case AR8327_PORT_SPEED_1000: + t |= AR8216_PORT_SPEED_1000M; + break; + } + + priv->write(priv, AR8327_REG_PORT_STATUS(AR8216_PORT_CPU), t); +} + +static void +ar8327_init_port(struct ar8216_priv *priv, int port) +{ + u32 t; + + if (port == AR8216_PORT_CPU) { + ar8327_init_cpuport(priv); + } else { + t = AR8216_PORT_STATUS_LINK_AUTO; + priv->write(priv, AR8327_REG_PORT_STATUS(port), t); + } + + priv->write(priv, AR8327_REG_PORT_HEADER(port), 0); + + priv->write(priv, AR8327_REG_PORT_VLAN0(port), 0); + + t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S; + priv->write(priv, AR8327_REG_PORT_VLAN1(port), t); + + t = AR8327_PORT_LOOKUP_LEARN; + t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; + priv->write(priv, AR8327_REG_PORT_LOOKUP(port), t); +} + +static u32 +ar8327_read_port_status(struct ar8216_priv *priv, int port) +{ + return priv->read(priv, AR8327_REG_PORT_STATUS(port)); +} + +static int +ar8327_atu_flush(struct ar8216_priv *priv) +{ + int ret; + + ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_BUSY, 0); + if (!ret) + priv->write(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_OP_FLUSH); + + return ret; +} + +static void +ar8327_vtu_op(struct ar8216_priv *priv, u32 op, u32 val) +{ + if (ar8216_wait_bit(priv, AR8327_REG_VTU_FUNC1, + AR8327_VTU_FUNC1_BUSY, 0)) + return; + + if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD) + priv->write(priv, AR8327_REG_VTU_FUNC0, val); + + op |= AR8327_VTU_FUNC1_BUSY; + priv->write(priv, AR8327_REG_VTU_FUNC1, op); +} + +static void +ar8327_vtu_flush(struct ar8216_priv *priv) +{ + ar8327_vtu_op(priv, AR8327_VTU_FUNC1_OP_FLUSH, 0); +} + +static void +ar8327_vtu_load_vlan(struct ar8216_priv *priv, u32 vid, u32 port_mask) +{ + u32 op; + u32 val; + int i; + + op = AR8327_VTU_FUNC1_OP_LOAD | (vid << AR8327_VTU_FUNC1_VID_S); + val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL; + for (i = 0; i < AR8327_NUM_PORTS; i++) { + u32 mode; + + if ((port_mask & BIT(i)) == 0) + mode = AR8327_VTU_FUNC0_EG_MODE_NOT; + else if (priv->vlan == 0) + mode = AR8327_VTU_FUNC0_EG_MODE_KEEP; + else if (priv->vlan_tagged & BIT(i)) + mode = AR8327_VTU_FUNC0_EG_MODE_TAG; + else + mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG; + + val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i); + } + ar8327_vtu_op(priv, op, val); +} + +static void +ar8327_setup_port(struct ar8216_priv *priv, int port, u32 egress, u32 ingress, + u32 members, u32 pvid) +{ + u32 t; + u32 mode; + + t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S; + t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S; + priv->write(priv, AR8327_REG_PORT_VLAN0(port), t); + + mode = AR8327_PORT_VLAN1_OUT_MODE_UNMOD; + switch (egress) { + case AR8216_OUT_KEEP: + mode = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH; + break; + case AR8216_OUT_STRIP_VLAN: + mode = AR8327_PORT_VLAN1_OUT_MODE_UNTAG; + break; + case AR8216_OUT_ADD_VLAN: + mode = AR8327_PORT_VLAN1_OUT_MODE_TAG; + break; + } + + t = AR8327_PORT_VLAN1_PORT_VLAN_PROP; + t |= mode << AR8327_PORT_VLAN1_OUT_MODE_S; + priv->write(priv, AR8327_REG_PORT_VLAN1(port), t); + + t = members; + t |= AR8327_PORT_LOOKUP_LEARN; + t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S; + t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; + priv->write(priv, AR8327_REG_PORT_LOOKUP(port), t); +} + +static const struct ar8xxx_chip ar8327_chip = { + .caps = AR8XXX_CAP_GIGE, + .hw_init = ar8327_hw_init, + .init_globals = ar8327_init_globals, + .init_port = ar8327_init_port, + .setup_port = ar8327_setup_port, + .read_port_status = ar8327_read_port_status, + .atu_flush = ar8327_atu_flush, + .vtu_flush = ar8327_vtu_flush, + .vtu_load_vlan = ar8327_vtu_load_vlan, +}; + +static int +ar8216_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + priv->vlan = !!val->value.i; + return 0; +} + +static int +ar8216_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + val->value.i = priv->vlan; + return 0; +} + + +static int +ar8216_sw_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct ar8216_priv *priv = to_ar8216(dev); + + /* make sure no invalid PVIDs get set */ + + if (vlan >= dev->vlans) + return -EINVAL; + + priv->pvid[port] = vlan; + return 0; +} + +static int +ar8216_sw_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct ar8216_priv *priv = to_ar8216(dev); + *vlan = priv->pvid[port]; + return 0; +} + +static int +ar8216_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + priv->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +ar8216_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + val->value.i = priv->vlan_id[val->port_vlan]; + return 0; +} + +static int +ar8216_sw_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct ar8216_priv *priv = to_ar8216(dev); + + ar8216_read_port_link(priv, port, link); + return 0; +} + +static int +ar8216_sw_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < dev->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 +ar8216_sw_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(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 < AR8X16_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + priv->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static int +ar8216_sw_hw_apply(struct switch_dev *dev) +{ + struct ar8216_priv *priv = to_ar8216(dev); + u8 portmask[AR8X16_MAX_PORTS]; + int i, j; + + mutex_lock(&priv->reg_mutex); + /* flush all vlan translation unit entries */ + priv->chip->vtu_flush(priv); + + memset(portmask, 0, sizeof(portmask)); + if (!priv->init) { + /* calculate the port destination masks and load vlans + * into the vlan translation unit */ + for (j = 0; j < AR8X16_MAX_VLANS; j++) { + u8 vp = priv->vlan_table[j]; + + if (!vp) + continue; + + for (i = 0; i < dev->ports; i++) { + u8 mask = (1 << i); + if (vp & mask) + portmask[i] |= vp & ~mask; + } + + priv->chip->vtu_load_vlan(priv, priv->vlan_id[j], + priv->vlan_table[j]); + } + } else { + /* vlan disabled: + * isolate all ports, but connect them to the cpu port */ + for (i = 0; i < dev->ports; i++) { + if (i == AR8216_PORT_CPU) + continue; + + portmask[i] = 1 << AR8216_PORT_CPU; + portmask[AR8216_PORT_CPU] |= (1 << i); + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < dev->ports; i++) { + int egress, ingress; + int pvid; + + if (priv->vlan) { + pvid = priv->vlan_id[priv->pvid[i]]; + if (priv->vlan_tagged & (1 << i)) + egress = AR8216_OUT_ADD_VLAN; + else + egress = AR8216_OUT_STRIP_VLAN; + ingress = AR8216_IN_SECURE; + } else { + pvid = i; + egress = AR8216_OUT_KEEP; + ingress = AR8216_IN_PORT_ONLY; + } + + priv->chip->setup_port(priv, i, egress, ingress, portmask[i], + pvid); + } + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static int +ar8216_sw_reset_switch(struct switch_dev *dev) +{ + struct ar8216_priv *priv = to_ar8216(dev); + int i; + + mutex_lock(&priv->reg_mutex); + memset(&priv->vlan, 0, sizeof(struct ar8216_priv) - + offsetof(struct ar8216_priv, vlan)); + + for (i = 0; i < AR8X16_MAX_VLANS; i++) + priv->vlan_id[i] = i; + + /* Configure all ports */ + for (i = 0; i < dev->ports; i++) + priv->chip->init_port(priv, i); + + priv->chip->init_globals(priv); + mutex_unlock(&priv->reg_mutex); + + return ar8216_sw_hw_apply(dev); +} + +static struct switch_attr ar8216_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = ar8216_sw_set_vlan, + .get = ar8216_sw_get_vlan, + .max = 1 + }, +}; + +static struct switch_attr ar8216_port[] = { +}; + +static struct switch_attr ar8216_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID (0-4094)", + .set = ar8216_sw_set_vid, + .get = ar8216_sw_get_vid, + .max = 4094, + }, +}; + +static const struct switch_dev_ops ar8216_sw_ops = { + .attr_global = { + .attr = ar8216_globals, + .n_attr = ARRAY_SIZE(ar8216_globals), + }, + .attr_port = { + .attr = ar8216_port, + .n_attr = ARRAY_SIZE(ar8216_port), + }, + .attr_vlan = { + .attr = ar8216_vlan, + .n_attr = ARRAY_SIZE(ar8216_vlan), + }, + .get_port_pvid = ar8216_sw_get_pvid, + .set_port_pvid = ar8216_sw_set_pvid, + .get_vlan_ports = ar8216_sw_get_ports, + .set_vlan_ports = ar8216_sw_set_ports, + .apply_config = ar8216_sw_hw_apply, + .reset_switch = ar8216_sw_reset_switch, + .get_port_link = ar8216_sw_get_port_link, +}; + +static int +ar8216_id_chip(struct ar8216_priv *priv) +{ + u32 val; + u16 id; + int i; + + val = ar8216_mii_read(priv, AR8216_REG_CTRL); + if (val == ~0) + return -ENODEV; + + id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); + for (i = 0; i < AR8X16_PROBE_RETRIES; i++) { + u16 t; + + val = ar8216_mii_read(priv, AR8216_REG_CTRL); + if (val == ~0) + return -ENODEV; + + t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); + if (t != id) + return -ENODEV; + } + + priv->chip_ver = (id & AR8216_CTRL_VERSION) >> AR8216_CTRL_VERSION_S; + priv->chip_rev = (id & AR8216_CTRL_REVISION); + + switch (priv->chip_ver) { + case AR8XXX_VER_AR8216: + priv->chip = &ar8216_chip; + break; + case AR8XXX_VER_AR8236: + priv->chip = &ar8236_chip; + break; + case AR8XXX_VER_AR8316: + priv->chip = &ar8316_chip; + break; + case AR8XXX_VER_AR8327: + priv->mii_lo_first = true; + priv->chip = &ar8327_chip; + break; + default: + printk(KERN_DEBUG + "ar8216: Unknown Atheros device [ver=%d, rev=%d, phy_id=%04x%04x]\n", + priv->chip_ver, priv->chip_rev, + mdiobus_read(priv->phy->bus, priv->phy->addr, 2), + mdiobus_read(priv->phy->bus, priv->phy->addr, 3)); + + return -ENODEV; + } + + return 0; +} + +static int +ar8216_config_init(struct phy_device *pdev) +{ + struct ar8216_priv *priv = pdev->priv; + struct net_device *dev = pdev->attached_dev; + struct switch_dev *swdev; + int ret; + + if (!priv) { + priv = kzalloc(sizeof(struct ar8216_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + } + + priv->phy = pdev; + + ret = ar8216_id_chip(priv); + if (ret) + goto err_free_priv; + + if (pdev->addr != 0) { + if (ar8xxx_has_gige(priv)) { + pdev->supported |= SUPPORTED_1000baseT_Full; + pdev->advertising |= ADVERTISED_1000baseT_Full; + } + + if (chip_is_ar8316(priv)) { + /* check if we're attaching to the switch twice */ + pdev = pdev->bus->phy_map[0]; + if (!pdev) { + kfree(priv); + return 0; + } + + /* switch device has not been initialized, reuse priv */ + if (!pdev->priv) { + priv->port4_phy = true; + pdev->priv = priv; + return 0; + } + + kfree(priv); + + /* switch device has been initialized, reinit */ + priv = pdev->priv; + priv->dev.ports = (AR8216_NUM_PORTS - 1); + priv->initialized = false; + priv->port4_phy = true; + ar8316_hw_init(priv); + return 0; + } + + kfree(priv); + return 0; + } + + if (ar8xxx_has_gige(priv)) + pdev->supported = SUPPORTED_1000baseT_Full; + else + pdev->supported = SUPPORTED_100baseT_Full; + pdev->advertising = pdev->supported; + + mutex_init(&priv->reg_mutex); + priv->read = ar8216_mii_read; + priv->write = ar8216_mii_write; + + pdev->priv = priv; + + swdev = &priv->dev; + swdev->cpu_port = AR8216_PORT_CPU; + swdev->ops = &ar8216_sw_ops; + swdev->ports = AR8216_NUM_PORTS; + + if (chip_is_ar8316(priv)) { + swdev->name = "Atheros AR8316"; + swdev->vlans = AR8X16_MAX_VLANS; + + if (priv->port4_phy) { + /* port 5 connected to the other mac, therefore unusable */ + swdev->ports = (AR8216_NUM_PORTS - 1); + } + } else if (chip_is_ar8236(priv)) { + swdev->name = "Atheros AR8236"; + swdev->vlans = AR8216_NUM_VLANS; + swdev->ports = AR8216_NUM_PORTS; + } else if (chip_is_ar8327(priv)) { + swdev->name = "Atheros AR8327"; + swdev->vlans = AR8X16_MAX_VLANS; + swdev->ports = AR8327_NUM_PORTS; + } else { + swdev->name = "Atheros AR8216"; + swdev->vlans = AR8216_NUM_VLANS; + } + + ret = register_switch(&priv->dev, pdev->attached_dev); + if (ret) + goto err_free_priv; + + printk(KERN_INFO "%s: %s switch driver attached.\n", + pdev->attached_dev->name, swdev->name); + + priv->init = true; + + ret = priv->chip->hw_init(priv); + if (ret) + goto err_free_priv; + + ret = ar8216_sw_reset_switch(&priv->dev); + if (ret) + goto err_free_priv; + + dev->phy_ptr = priv; + + /* VID fixup only needed on ar8216 */ + if (chip_is_ar8216(priv) && pdev->addr == 0) { + dev->priv_flags |= IFF_NO_IP_ALIGN; + dev->eth_mangle_rx = ar8216_mangle_rx; + dev->eth_mangle_tx = ar8216_mangle_tx; + } + + priv->init = false; + + return 0; + +err_free_priv: + kfree(priv); + return ret; +} + +static int +ar8216_read_status(struct phy_device *phydev) +{ + struct ar8216_priv *priv = phydev->priv; + struct switch_port_link link; + int ret; + + if (phydev->addr != 0) + return genphy_read_status(phydev); + + ar8216_read_port_link(priv, phydev->addr, &link); + phydev->link = !!link.link; + if (!phydev->link) + return 0; + + switch (link.speed) { + case SWITCH_PORT_SPEED_10: + phydev->speed = SPEED_10; + break; + case SWITCH_PORT_SPEED_100: + phydev->speed = SPEED_100; + break; + case SWITCH_PORT_SPEED_1000: + phydev->speed = SPEED_1000; + break; + default: + phydev->speed = 0; + } + phydev->duplex = link.duplex ? DUPLEX_FULL : DUPLEX_HALF; + + /* flush the address translation unit */ + mutex_lock(&priv->reg_mutex); + ret = priv->chip->atu_flush(priv); + mutex_unlock(&priv->reg_mutex); + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return ret; +} + +static int +ar8216_config_aneg(struct phy_device *phydev) +{ + if (phydev->addr == 0) + return 0; + + return genphy_config_aneg(phydev); +} + +static int +ar8216_probe(struct phy_device *pdev) +{ + struct ar8216_priv priv; + + priv.phy = pdev; + return ar8216_id_chip(&priv); +} + +static void +ar8216_remove(struct phy_device *pdev) +{ + struct ar8216_priv *priv = pdev->priv; + struct net_device *dev = pdev->attached_dev; + + if (!priv) + return; + + dev->priv_flags &= ~IFF_NO_IP_ALIGN; + dev->eth_mangle_rx = NULL; + dev->eth_mangle_tx = NULL; + + if (pdev->addr == 0) + unregister_switch(&priv->dev); + kfree(priv); +} + +static struct phy_driver ar8216_driver = { + .phy_id = 0x004d0000, + .name = "Atheros AR8216/AR8236/AR8316", + .phy_id_mask = 0xffff0000, + .features = PHY_BASIC_FEATURES, + .probe = ar8216_probe, + .remove = ar8216_remove, + .config_init = &ar8216_config_init, + .config_aneg = &ar8216_config_aneg, + .read_status = &ar8216_read_status, + .driver = { .owner = THIS_MODULE }, +}; + +int __init +ar8216_init(void) +{ + return phy_driver_register(&ar8216_driver); +} + +void __exit +ar8216_exit(void) +{ + phy_driver_unregister(&ar8216_driver); +} + +module_init(ar8216_init); +module_exit(ar8216_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/ar8216.h b/drivers/net/phy/ar8216.h new file mode 100644 index 0000000..8948c52 --- /dev/null +++ b/drivers/net/phy/ar8216.h @@ -0,0 +1,341 @@ +/* + * ar8216.h: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * 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. + */ + +#ifndef __AR8216_H +#define __AR8216_H + +#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s) + +#define AR8216_PORT_CPU 0 +#define AR8216_NUM_PORTS 6 +#define AR8216_NUM_VLANS 16 +#define AR8316_NUM_VLANS 4096 + +/* Atheros specific MII registers */ +#define MII_ATH_MMD_ADDR 0x0d +#define MII_ATH_MMD_DATA 0x0e +#define MII_ATH_DBG_ADDR 0x1d +#define MII_ATH_DBG_DATA 0x1e + +#define AR8216_REG_CTRL 0x0000 +#define AR8216_CTRL_REVISION BITS(0, 8) +#define AR8216_CTRL_REVISION_S 0 +#define AR8216_CTRL_VERSION BITS(8, 8) +#define AR8216_CTRL_VERSION_S 8 +#define AR8216_CTRL_RESET BIT(31) + +#define AR8216_REG_FLOOD_MASK 0x002C +#define AR8216_FM_UNI_DEST_PORTS BITS(0, 6) +#define AR8216_FM_MULTI_DEST_PORTS BITS(16, 6) + +#define AR8216_REG_GLOBAL_CTRL 0x0030 +#define AR8216_GCTRL_MTU BITS(0, 11) +#define AR8236_GCTRL_MTU BITS(0, 14) +#define AR8316_GCTRL_MTU BITS(0, 14) + +#define AR8216_REG_VTU 0x0040 +#define AR8216_VTU_OP BITS(0, 3) +#define AR8216_VTU_OP_NOOP 0x0 +#define AR8216_VTU_OP_FLUSH 0x1 +#define AR8216_VTU_OP_LOAD 0x2 +#define AR8216_VTU_OP_PURGE 0x3 +#define AR8216_VTU_OP_REMOVE_PORT 0x4 +#define AR8216_VTU_ACTIVE BIT(3) +#define AR8216_VTU_FULL BIT(4) +#define AR8216_VTU_PORT BITS(8, 4) +#define AR8216_VTU_PORT_S 8 +#define AR8216_VTU_VID BITS(16, 12) +#define AR8216_VTU_VID_S 16 +#define AR8216_VTU_PRIO BITS(28, 3) +#define AR8216_VTU_PRIO_S 28 +#define AR8216_VTU_PRIO_EN BIT(31) + +#define AR8216_REG_VTU_DATA 0x0044 +#define AR8216_VTUDATA_MEMBER BITS(0, 10) +#define AR8236_VTUDATA_MEMBER BITS(0, 7) +#define AR8216_VTUDATA_VALID BIT(11) + +#define AR8216_REG_ATU 0x0050 +#define AR8216_ATU_OP BITS(0, 3) +#define AR8216_ATU_OP_NOOP 0x0 +#define AR8216_ATU_OP_FLUSH 0x1 +#define AR8216_ATU_OP_LOAD 0x2 +#define AR8216_ATU_OP_PURGE 0x3 +#define AR8216_ATU_OP_FLUSH_LOCKED 0x4 +#define AR8216_ATU_OP_FLUSH_UNICAST 0x5 +#define AR8216_ATU_OP_GET_NEXT 0x6 +#define AR8216_ATU_ACTIVE BIT(3) +#define AR8216_ATU_PORT_NUM BITS(8, 4) +#define AR8216_ATU_FULL_VIO BIT(12) +#define AR8216_ATU_ADDR4 BITS(16, 8) +#define AR8216_ATU_ADDR5 BITS(24, 8) + +#define AR8216_REG_ATU_DATA 0x0054 +#define AR8216_ATU_ADDR3 BITS(0, 8) +#define AR8216_ATU_ADDR2 BITS(8, 8) +#define AR8216_ATU_ADDR1 BITS(16, 8) +#define AR8216_ATU_ADDR0 BITS(24, 8) + +#define AR8216_REG_ATU_CTRL 0x005C +#define AR8216_ATU_CTRL_AGE_EN BIT(17) +#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16) +#define AR8216_ATU_CTRL_AGE_TIME_S 0 + +#define AR8216_PORT_OFFSET(_i) (0x0100 * (_i + 1)) +#define AR8216_REG_PORT_STATUS(_i) (AR8216_PORT_OFFSET(_i) + 0x0000) +#define AR8216_PORT_STATUS_SPEED BITS(0,2) +#define AR8216_PORT_STATUS_SPEED_S 0 +#define AR8216_PORT_STATUS_TXMAC BIT(2) +#define AR8216_PORT_STATUS_RXMAC BIT(3) +#define AR8216_PORT_STATUS_TXFLOW BIT(4) +#define AR8216_PORT_STATUS_RXFLOW BIT(5) +#define AR8216_PORT_STATUS_DUPLEX BIT(6) +#define AR8216_PORT_STATUS_LINK_UP BIT(8) +#define AR8216_PORT_STATUS_LINK_AUTO BIT(9) +#define AR8216_PORT_STATUS_LINK_PAUSE BIT(10) + +#define AR8216_REG_PORT_CTRL(_i) (AR8216_PORT_OFFSET(_i) + 0x0004) + +/* port forwarding state */ +#define AR8216_PORT_CTRL_STATE BITS(0, 3) +#define AR8216_PORT_CTRL_STATE_S 0 + +#define AR8216_PORT_CTRL_LEARN_LOCK BIT(7) + +/* egress 802.1q mode */ +#define AR8216_PORT_CTRL_VLAN_MODE BITS(8, 2) +#define AR8216_PORT_CTRL_VLAN_MODE_S 8 + +#define AR8216_PORT_CTRL_IGMP_SNOOP BIT(10) +#define AR8216_PORT_CTRL_HEADER BIT(11) +#define AR8216_PORT_CTRL_MAC_LOOP BIT(12) +#define AR8216_PORT_CTRL_SINGLE_VLAN BIT(13) +#define AR8216_PORT_CTRL_LEARN BIT(14) +#define AR8216_PORT_CTRL_MIRROR_TX BIT(16) +#define AR8216_PORT_CTRL_MIRROR_RX BIT(17) + +#define AR8216_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET(_i) + 0x0008) + +#define AR8216_PORT_VLAN_DEFAULT_ID BITS(0, 12) +#define AR8216_PORT_VLAN_DEFAULT_ID_S 0 + +#define AR8216_PORT_VLAN_DEST_PORTS BITS(16, 9) +#define AR8216_PORT_VLAN_DEST_PORTS_S 16 + +/* bit0 added to the priority field of egress frames */ +#define AR8216_PORT_VLAN_TX_PRIO BIT(27) + +/* port default priority */ +#define AR8216_PORT_VLAN_PRIORITY BITS(28, 2) +#define AR8216_PORT_VLAN_PRIORITY_S 28 + +/* ingress 802.1q mode */ +#define AR8216_PORT_VLAN_MODE BITS(30, 2) +#define AR8216_PORT_VLAN_MODE_S 30 + +#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c) +#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010) + + +#define AR8236_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET((_i)) + 0x0008) +#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12) +#define AR8236_PORT_VLAN_DEFAULT_ID_S 16 +#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3) +#define AR8236_PORT_VLAN_PRIORITY_S 28 + +#define AR8236_REG_PORT_VLAN2(_i) (AR8216_PORT_OFFSET((_i)) + 0x000c) +#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7) +#define AR8236_PORT_VLAN2_MEMBER_S 16 +#define AR8236_PORT_VLAN2_TX_PRIO BIT(23) +#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2) +#define AR8236_PORT_VLAN2_VLAN_MODE_S 30 + +#define AR8327_NUM_PORTS 7 +#define AR8327_NUM_PHYS 5 +#define AR8327_PORTS_ALL 0x7f + +#define AR8327_REG_MASK 0x000 + +#define AR8327_REG_PAD0_MODE 0x004 +#define AR8327_REG_PAD5_MODE 0x008 +#define AR8327_REG_PAD6_MODE 0x00c +#define AR8327_PAD_MAC_MII_RXCLK_SEL BIT(0) +#define AR8327_PAD_MAC_MII_TXCLK_SEL BIT(1) +#define AR8327_PAD_MAC_MII_EN BIT(2) +#define AR8327_PAD_MAC_GMII_RXCLK_SEL BIT(4) +#define AR8327_PAD_MAC_GMII_TXCLK_SEL BIT(5) +#define AR8327_PAD_MAC_GMII_EN BIT(6) +#define AR8327_PAD_SGMII_EN BIT(7) +#define AR8327_PAD_PHY_MII_RXCLK_SEL BIT(8) +#define AR8327_PAD_PHY_MII_TXCLK_SEL BIT(9) +#define AR8327_PAD_PHY_MII_EN BIT(10) +#define AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL BIT(11) +#define AR8327_PAD_PHY_GMII_RXCLK_SEL BIT(12) +#define AR8327_PAD_PHY_GMII_TXCLK_SEL BIT(13) +#define AR8327_PAD_PHY_GMII_EN BIT(14) +#define AR8327_PAD_PHYX_GMII_EN BIT(16) +#define AR8327_PAD_PHYX_RGMII_EN BIT(17) +#define AR8327_PAD_PHYX_MII_EN BIT(18) +#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2) +#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S 20 +#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2) +#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S 22 +#define AR8327_PAD_RGMII_RXCLK_DELAY_EN BIT(24) +#define AR8327_PAD_RGMII_TXCLK_DELAY_EN BIT(25) +#define AR8327_PAD_RGMII_EN BIT(26) + +#define AR8327_REG_POWER_ON_STRIP 0x010 +#define AR8327_POWER_ON_STRIP_POWER_ON_SEL BIT(31) +#define AR8327_POWER_ON_STRIP_LED_OPEN_EN BIT(24) + +#define AR8327_REG_INT_STATUS0 0x020 +#define AR8327_INT0_VT_DONE BIT(20) + +#define AR8327_REG_INT_STATUS1 0x024 +#define AR8327_REG_INT_MASK0 0x028 +#define AR8327_REG_INT_MASK1 0x02c +#define AR8327_REG_SERVICE_TAG 0x048 +#define AR8327_REG_LED_CTRL0 0x050 +#define AR8327_REG_LED_CTRL1 0x054 +#define AR8327_REG_LED_CTRL2 0x058 +#define AR8327_REG_LED_CTRL3 0x05c +#define AR8327_REG_MAC_ADDR0 0x060 +#define AR8327_REG_MAC_ADDR1 0x064 + +#define AR8327_REG_MAX_FRAME_SIZE 0x078 +#define AR8327_MAX_FRAME_SIZE_MTU BITS(0, 14) + +#define AR8327_REG_PORT_STATUS(_i) (0x07c + (_i) * 4) + +#define AR8327_REG_HEADER_CTRL 0x098 +#define AR8327_REG_PORT_HEADER(_i) (0x09c + (_i) * 4) + +#define AR8327_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8) +#define AR8327_PORT_VLAN0_DEF_SVID BITS(0, 12) +#define AR8327_PORT_VLAN0_DEF_SVID_S 0 +#define AR8327_PORT_VLAN0_DEF_CVID BITS(16, 12) +#define AR8327_PORT_VLAN0_DEF_CVID_S 16 + +#define AR8327_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8) +#define AR8327_PORT_VLAN1_PORT_VLAN_PROP BIT(6) +#define AR8327_PORT_VLAN1_OUT_MODE BITS(12, 2) +#define AR8327_PORT_VLAN1_OUT_MODE_S 12 +#define AR8327_PORT_VLAN1_OUT_MODE_UNMOD 0 +#define AR8327_PORT_VLAN1_OUT_MODE_UNTAG 1 +#define AR8327_PORT_VLAN1_OUT_MODE_TAG 2 +#define AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH 3 + +#define AR8327_REG_ATU_DATA0 0x600 +#define AR8327_REG_ATU_DATA1 0x604 +#define AR8327_REG_ATU_DATA2 0x608 + +#define AR8327_REG_ATU_FUNC 0x60c +#define AR8327_ATU_FUNC_OP BITS(0, 4) +#define AR8327_ATU_FUNC_OP_NOOP 0x0 +#define AR8327_ATU_FUNC_OP_FLUSH 0x1 +#define AR8327_ATU_FUNC_OP_LOAD 0x2 +#define AR8327_ATU_FUNC_OP_PURGE 0x3 +#define AR8327_ATU_FUNC_OP_FLUSH_LOCKED 0x4 +#define AR8327_ATU_FUNC_OP_FLUSH_UNICAST 0x5 +#define AR8327_ATU_FUNC_OP_GET_NEXT 0x6 +#define AR8327_ATU_FUNC_OP_SEARCH_MAC 0x7 +#define AR8327_ATU_FUNC_OP_CHANGE_TRUNK 0x8 +#define AR8327_ATU_FUNC_BUSY BIT(31) + +#define AR8327_REG_VTU_FUNC0 0x0610 +#define AR8327_VTU_FUNC0_EG_MODE BITS(4, 14) +#define AR8327_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2) +#define AR8327_VTU_FUNC0_EG_MODE_KEEP 0 +#define AR8327_VTU_FUNC0_EG_MODE_UNTAG 1 +#define AR8327_VTU_FUNC0_EG_MODE_TAG 2 +#define AR8327_VTU_FUNC0_EG_MODE_NOT 3 +#define AR8327_VTU_FUNC0_IVL BIT(19) +#define AR8327_VTU_FUNC0_VALID BIT(20) + +#define AR8327_REG_VTU_FUNC1 0x0614 +#define AR8327_VTU_FUNC1_OP BITS(0, 3) +#define AR8327_VTU_FUNC1_OP_NOOP 0 +#define AR8327_VTU_FUNC1_OP_FLUSH 1 +#define AR8327_VTU_FUNC1_OP_LOAD 2 +#define AR8327_VTU_FUNC1_OP_PURGE 3 +#define AR8327_VTU_FUNC1_OP_REMOVE_PORT 4 +#define AR8327_VTU_FUNC1_OP_GET_NEXT 5 +#define AR8327_VTU_FUNC1_OP_GET_ONE 6 +#define AR8327_VTU_FUNC1_FULL BIT(4) +#define AR8327_VTU_FUNC1_PORT BIT(8, 4) +#define AR8327_VTU_FUNC1_PORT_S 8 +#define AR8327_VTU_FUNC1_VID BIT(16, 12) +#define AR8327_VTU_FUNC1_VID_S 16 +#define AR8327_VTU_FUNC1_BUSY BIT(31) + +#define AR8327_REG_FWD_CTRL0 0x620 +#define AR8327_FWD_CTRL0_CPU_PORT_EN BIT(10) +#define AR8327_FWD_CTRL0_MIRROR_PORT BITS(4, 4) +#define AR8327_FWD_CTRL0_MIRROR_PORT_S 4 + +#define AR8327_REG_FWD_CTRL1 0x624 +#define AR8327_FWD_CTRL1_UC_FLOOD BITS(0, 7) +#define AR8327_FWD_CTRL1_UC_FLOOD_S 0 +#define AR8327_FWD_CTRL1_MC_FLOOD BITS(8, 7) +#define AR8327_FWD_CTRL1_MC_FLOOD_S 8 +#define AR8327_FWD_CTRL1_BC_FLOOD BITS(16, 7) +#define AR8327_FWD_CTRL1_BC_FLOOD_S 16 +#define AR8327_FWD_CTRL1_IGMP BITS(24, 7) +#define AR8327_FWD_CTRL1_IGMP_S 24 + +#define AR8327_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc) +#define AR8327_PORT_LOOKUP_MEMBER BITS(0, 7) +#define AR8327_PORT_LOOKUP_IN_MODE BITS(8, 2) +#define AR8327_PORT_LOOKUP_IN_MODE_S 8 +#define AR8327_PORT_LOOKUP_STATE BITS(16, 3) +#define AR8327_PORT_LOOKUP_STATE_S 16 +#define AR8327_PORT_LOOKUP_LEARN BIT(20) + +#define AR8327_REG_PORT_PRIO(_i) (0x664 + (_i) * 0xc) + +/* port speed */ +enum { + AR8216_PORT_SPEED_10M = 0, + AR8216_PORT_SPEED_100M = 1, + AR8216_PORT_SPEED_1000M = 2, + AR8216_PORT_SPEED_ERR = 3, +}; + +/* ingress 802.1q mode */ +enum { + AR8216_IN_PORT_ONLY = 0, + AR8216_IN_PORT_FALLBACK = 1, + AR8216_IN_VLAN_ONLY = 2, + AR8216_IN_SECURE = 3 +}; + +/* egress 802.1q mode */ +enum { + AR8216_OUT_KEEP = 0, + AR8216_OUT_STRIP_VLAN = 1, + AR8216_OUT_ADD_VLAN = 2 +}; + +/* port forwarding state */ +enum { + AR8216_PORT_STATE_DISABLED = 0, + AR8216_PORT_STATE_BLOCK = 1, + AR8216_PORT_STATE_LISTEN = 2, + AR8216_PORT_STATE_LEARN = 3, + AR8216_PORT_STATE_FORWARD = 4 +}; + +#endif diff --git a/drivers/net/phy/ip17xx.c b/drivers/net/phy/ip17xx.c new file mode 100644 index 0000000..c82c39e --- /dev/null +++ b/drivers/net/phy/ip17xx.c @@ -0,0 +1,1410 @@ +/* + * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family + * + * Copyright (C) 2008 Patrick Horn + * Copyright (C) 2008, 2010 Martin Mares + * Copyright (C) 2009 Felix Fietkau + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_VLANS 16 +#define MAX_PORTS 9 +#undef DUMP_MII_IO + +typedef struct ip17xx_reg { + u16 p; // phy + u16 m; // mii +} reg; +typedef char bitnum; + +#define NOTSUPPORTED {-1,-1} + +#define REG_SUPP(x) (((x).m != ((u16)-1)) && ((x).p != (u16)-1)) + +struct ip17xx_state; + +/*********** CONSTANTS ***********/ +struct register_mappings { + char *NAME; + u16 MODEL_NO; // Compare to bits 4-9 of MII register 0,3. + bitnum NUM_PORTS; + bitnum CPU_PORT; + +/* The default VLAN for each port. + Default: 0x0001 for Ports 0,1,2,3 + 0x0002 for Ports 4,5 */ + reg VLAN_DEFAULT_TAG_REG[MAX_PORTS]; + +/* These ports are tagged. + Default: 0x00 */ + reg ADD_TAG_REG; + reg REMOVE_TAG_REG; + bitnum ADD_TAG_BIT[MAX_PORTS]; +/* These ports are untagged. + Default: 0x00 (i.e. do not alter any VLAN tags...) + Maybe set to 0 if user disables VLANs. */ + bitnum REMOVE_TAG_BIT[MAX_PORTS]; + +/* Port M and Port N are on the same VLAN. + Default: All ports on all VLANs. */ +// Use register {29, 19+N/2} + reg VLAN_LOOKUP_REG; +// Port 5 uses register {30, 18} but same as odd bits. + reg VLAN_LOOKUP_REG_5; // in a different register on IP175C. + bitnum VLAN_LOOKUP_EVEN_BIT[MAX_PORTS]; + bitnum VLAN_LOOKUP_ODD_BIT[MAX_PORTS]; + +/* This VLAN corresponds to which ports. + Default: 0x2f,0x30,0x3f,0x3f... */ + reg TAG_VLAN_MASK_REG; + bitnum TAG_VLAN_MASK_EVEN_BIT[MAX_PORTS]; + bitnum TAG_VLAN_MASK_ODD_BIT[MAX_PORTS]; + + int RESET_VAL; + reg RESET_REG; + + reg MODE_REG; + int MODE_VAL; + +/* General flags */ + reg ROUTER_CONTROL_REG; + reg VLAN_CONTROL_REG; + bitnum TAG_VLAN_BIT; + bitnum ROUTER_EN_BIT; + bitnum NUMLAN_GROUPS_MAX; + bitnum NUMLAN_GROUPS_BIT; + + reg MII_REGISTER_EN; + bitnum MII_REGISTER_EN_BIT; + + // set to 1 for 178C, 0 for 175C. + bitnum SIMPLE_VLAN_REGISTERS; // 175C has two vlans per register but 178C has only one. + + // Pointers to functions which manipulate hardware state + int (*update_state)(struct ip17xx_state *state); + int (*set_vlan_mode)(struct ip17xx_state *state); + int (*reset)(struct ip17xx_state *state); +}; + +static int ip175c_update_state(struct ip17xx_state *state); +static int ip175c_set_vlan_mode(struct ip17xx_state *state); +static int ip175c_reset(struct ip17xx_state *state); + +static const struct register_mappings IP178C = { + .NAME = "IP178C", + .MODEL_NO = 0x18, + .VLAN_DEFAULT_TAG_REG = { + {30,3},{30,4},{30,5},{30,6},{30,7},{30,8}, + {30,9},{30,10},{30,11}, + }, + + .ADD_TAG_REG = {30,12}, + .ADD_TAG_BIT = {0,1,2,3,4,5,6,7,8}, + .REMOVE_TAG_REG = {30,13}, + .REMOVE_TAG_BIT = {4,5,6,7,8,9,10,11,12}, + + .SIMPLE_VLAN_REGISTERS = 1, + + .VLAN_LOOKUP_REG = {31,0},// +N + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, // not used with SIMPLE_VLAN_REGISTERS + .VLAN_LOOKUP_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,5,6,7,8}, + + .TAG_VLAN_MASK_REG = {30,14}, // +N + .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, + .TAG_VLAN_MASK_ODD_BIT = {0,1,2,3,4,5,6,7,8}, + + .RESET_VAL = 0x55AA, + .RESET_REG = {30,0}, + .MODE_VAL = 0, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = {30,30}, + .ROUTER_EN_BIT = 11, + .NUMLAN_GROUPS_MAX = 8, + .NUMLAN_GROUPS_BIT = 8, // {0-2} + + .VLAN_CONTROL_REG = {30,13}, + .TAG_VLAN_BIT = 3, + + .CPU_PORT = 8, + .NUM_PORTS = 9, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + +static const struct register_mappings IP175C = { + .NAME = "IP175C", + .MODEL_NO = 0x18, + .VLAN_DEFAULT_TAG_REG = { + {29,24},{29,25},{29,26},{29,27},{29,28},{29,30}, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED + }, + + .ADD_TAG_REG = {29,23}, + .REMOVE_TAG_REG = {29,23}, + .ADD_TAG_BIT = {11,12,13,14,15,1,-1,-1,-1}, + .REMOVE_TAG_BIT = {6,7,8,9,10,0,-1,-1,-1}, + + .SIMPLE_VLAN_REGISTERS = 0, + + .VLAN_LOOKUP_REG = {29,19},// +N/2 + .VLAN_LOOKUP_REG_5 = {30,18}, + .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,15,-1,-1,-1}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,7,-1,-1,-1}, + + .TAG_VLAN_MASK_REG = {30,1}, // +N/2 + .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,-1,-1,-1}, + .TAG_VLAN_MASK_ODD_BIT = {8,9,10,11,12,13,-1,-1,-1}, + + .RESET_VAL = 0x175C, + .RESET_REG = {30,0}, + .MODE_VAL = 0x175C, + .MODE_REG = {29,31}, + + .ROUTER_CONTROL_REG = {30,9}, + .ROUTER_EN_BIT = 3, + .NUMLAN_GROUPS_MAX = 8, + .NUMLAN_GROUPS_BIT = 0, // {0-2} + + .VLAN_CONTROL_REG = {30,9}, + .TAG_VLAN_BIT = 7, + + .NUM_PORTS = 6, + .CPU_PORT = 5, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + +static const struct register_mappings IP175A = { + .NAME = "IP175A", + .MODEL_NO = 0x05, + .VLAN_DEFAULT_TAG_REG = { + {0,24},{0,25},{0,26},{0,27},{0,28},NOTSUPPORTED, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED + }, + + .ADD_TAG_REG = {0,23}, + .REMOVE_TAG_REG = {0,23}, + .ADD_TAG_BIT = {11,12,13,14,15,-1,-1,-1,-1}, + .REMOVE_TAG_BIT = {6,7,8,9,10,-1,-1,-1,-1}, + + .SIMPLE_VLAN_REGISTERS = 0, + + // Only programmable via EEPROM + .VLAN_LOOKUP_REG = NOTSUPPORTED,// +N/2 + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, + .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,-1,-1,-1,-1}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,-1,-1,-1,-1}, + + .TAG_VLAN_MASK_REG = NOTSUPPORTED, // +N/2, + .TAG_VLAN_MASK_EVEN_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, + .TAG_VLAN_MASK_ODD_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, + + .RESET_VAL = -1, + .RESET_REG = NOTSUPPORTED, + .MODE_VAL = 0, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = NOTSUPPORTED, + .VLAN_CONTROL_REG = NOTSUPPORTED, + .TAG_VLAN_BIT = -1, + .ROUTER_EN_BIT = -1, + .NUMLAN_GROUPS_MAX = -1, + .NUMLAN_GROUPS_BIT = -1, // {0-2} + + .NUM_PORTS = 5, + .CPU_PORT = 4, + + .MII_REGISTER_EN = {0, 18}, + .MII_REGISTER_EN_BIT = 7, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + + +static int ip175d_update_state(struct ip17xx_state *state); +static int ip175d_set_vlan_mode(struct ip17xx_state *state); +static int ip175d_reset(struct ip17xx_state *state); + +static const struct register_mappings IP175D = { + .NAME = "IP175D", + .MODEL_NO = 0x18, + + // The IP175D has a completely different interface, so we leave most + // of the registers undefined and switch to different code paths. + + .VLAN_DEFAULT_TAG_REG = { + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, + }, + + .ADD_TAG_REG = NOTSUPPORTED, + .REMOVE_TAG_REG = NOTSUPPORTED, + + .SIMPLE_VLAN_REGISTERS = 0, + + .VLAN_LOOKUP_REG = NOTSUPPORTED, + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, + .TAG_VLAN_MASK_REG = NOTSUPPORTED, + + .RESET_VAL = 0x175D, + .RESET_REG = {20,2}, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = NOTSUPPORTED, + .ROUTER_EN_BIT = -1, + .NUMLAN_GROUPS_BIT = -1, + + .VLAN_CONTROL_REG = NOTSUPPORTED, + .TAG_VLAN_BIT = -1, + + .NUM_PORTS = 6, + .CPU_PORT = 5, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175d_update_state, + .set_vlan_mode = ip175d_set_vlan_mode, + .reset = ip175d_reset, +}; + +struct ip17xx_state { + struct switch_dev dev; + struct mii_bus *mii_bus; + bool registered; + + int router_mode; // ROUTER_EN + int vlan_enabled; // TAG_VLAN_EN + struct port_state { + u16 pvid; + unsigned int shareports; + } ports[MAX_PORTS]; + unsigned int add_tag; + unsigned int remove_tag; + int num_vlans; + struct vlan_state { + unsigned int ports; + unsigned int tag; // VLAN tag (IP175D only) + } vlans[MAX_VLANS]; + const struct register_mappings *regs; + reg proc_mii; // phy/reg for the low level register access via swconfig + + char buf[80]; +}; + +#define get_state(_dev) container_of((_dev), struct ip17xx_state, dev) + +static int ip_phy_read(struct ip17xx_state *state, int port, int reg) +{ + int val = mdiobus_read(state->mii_bus, port, reg); + if (val < 0) + pr_warning("IP17xx: Unable to get MII register %d,%d: error %d\n", port, reg, -val); +#ifdef DUMP_MII_IO + else + pr_debug("IP17xx: Read MII(%d,%d) -> %04x\n", port, reg, val); +#endif + return val; +} + +static int ip_phy_write(struct ip17xx_state *state, int port, int reg, u16 val) +{ + int err; + +#ifdef DUMP_MII_IO + pr_debug("IP17xx: Write MII(%d,%d) <- %04x\n", port, reg, val); +#endif + err = mdiobus_write(state->mii_bus, port, reg, val); + if (err < 0) + pr_warning("IP17xx: Unable to write MII register %d,%d: error %d\n", port, reg, -err); + return err; +} + +static int ip_phy_write_masked(struct ip17xx_state *state, int port, int reg, unsigned int mask, unsigned int data) +{ + int val = ip_phy_read(state, port, reg); + if (val < 0) + return 0; + return ip_phy_write(state, port, reg, (val & ~mask) | data); +} + +static int getPhy(struct ip17xx_state *state, reg mii) +{ + if (!REG_SUPP(mii)) + return -EFAULT; + return ip_phy_read(state, mii.p, mii.m); +} + +static int setPhy(struct ip17xx_state *state, reg mii, u16 value) +{ + int err; + + if (!REG_SUPP(mii)) + return -EFAULT; + err = ip_phy_write(state, mii.p, mii.m, value); + if (err < 0) + return err; + mdelay(2); + getPhy(state, mii); + return 0; +} + + +/** + * These two macros are to simplify the mapping of logical bits to the bits in hardware. + * NOTE: these macros will return if there is an error! + */ + +#define GET_PORT_BITS(state, bits, addr, bit_lookup) \ + do { \ + int i, val = getPhy((state), (addr)); \ + if (val < 0) \ + return val; \ + (bits) = 0; \ + for (i = 0; i < MAX_PORTS; i++) { \ + if ((bit_lookup)[i] == -1) continue; \ + if (val & (1<<(bit_lookup)[i])) \ + (bits) |= (1<>i)<<(bit_lookup)[i]); \ + } \ + val = setPhy((state), (addr), val); \ + if (val < 0) \ + return val; \ + } while (0) + + +static int get_model(struct ip17xx_state *state) +{ + int id1, id2; + int oui_id, model_no, rev_no, chip_no; + + id1 = ip_phy_read(state, 0, 2); + id2 = ip_phy_read(state, 0, 3); + oui_id = (id1 << 6) | ((id2 >> 10) & 0x3f); + model_no = (id2 >> 4) & 0x3f; + rev_no = id2 & 0xf; + pr_debug("IP17xx: Identified oui=%06x model=%02x rev=%X\n", oui_id, model_no, rev_no); + + if (oui_id != 0x0090c3) // No other oui_id should have reached us anyway + return -ENODEV; + + if (model_no == IP175A.MODEL_NO) { + state->regs = &IP175A; + } else if (model_no == IP175C.MODEL_NO) { + /* + * Several models share the same model_no: + * 178C has more PHYs, so we try whether the device responds to a read from PHY5 + * 175D has a new chip ID register + * 175C has neither + */ + if (ip_phy_read(state, 5, 2) == 0x0243) { + state->regs = &IP178C; + } else { + chip_no = ip_phy_read(state, 20, 0); + pr_debug("IP17xx: Chip ID register reads %04x\n", chip_no); + if (chip_no == 0x175d) { + state->regs = &IP175D; + } else { + state->regs = &IP175C; + } + } + } else { + pr_warning("IP17xx: Found an unknown IC+ switch with model number %02x, revision %X.\n", model_no, rev_no); + return -EPERM; + } + return 0; +} + +/*** Low-level functions for the older models ***/ + +/** Only set vlan and router flags in the switch **/ +static int ip175c_set_flags(struct ip17xx_state *state) +{ + int val; + + if (!REG_SUPP(state->regs->ROUTER_CONTROL_REG)) { + return 0; + } + + val = getPhy(state, state->regs->ROUTER_CONTROL_REG); + if (val < 0) { + return val; + } + if (state->regs->ROUTER_EN_BIT >= 0) { + if (state->router_mode) { + val |= (1<regs->ROUTER_EN_BIT); + } else { + val &= (~(1<regs->ROUTER_EN_BIT)); + } + } + if (state->regs->TAG_VLAN_BIT >= 0) { + if (state->vlan_enabled) { + val |= (1<regs->TAG_VLAN_BIT); + } else { + val &= (~(1<regs->TAG_VLAN_BIT)); + } + } + if (state->regs->NUMLAN_GROUPS_BIT >= 0) { + val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<regs->NUMLAN_GROUPS_BIT)); + if (state->num_vlans > state->regs->NUMLAN_GROUPS_MAX) { + val |= state->regs->NUMLAN_GROUPS_MAX << state->regs->NUMLAN_GROUPS_BIT; + } else if (state->num_vlans >= 1) { + val |= (state->num_vlans-1) << state->regs->NUMLAN_GROUPS_BIT; + } + } + return setPhy(state, state->regs->ROUTER_CONTROL_REG, val); +} + +/** Set all VLAN and port state. Usually you should call "correct_vlan_state" first. **/ +static int ip175c_set_state(struct ip17xx_state *state) +{ + int j; + int i; + SET_PORT_BITS(state, state->add_tag, + state->regs->ADD_TAG_REG, state->regs->ADD_TAG_BIT); + SET_PORT_BITS(state, state->remove_tag, + state->regs->REMOVE_TAG_REG, state->regs->REMOVE_TAG_BIT); + + if (REG_SUPP(state->regs->VLAN_LOOKUP_REG)) { + for (j=0; jregs->NUM_PORTS; j++) { + reg addr; + const bitnum *bit_lookup = (j%2==0)? + state->regs->VLAN_LOOKUP_EVEN_BIT: + state->regs->VLAN_LOOKUP_ODD_BIT; + + addr = state->regs->VLAN_LOOKUP_REG; + if (state->regs->SIMPLE_VLAN_REGISTERS) { + addr.m += j; + } else { + switch (j) { + case 0: + case 1: + break; + case 2: + case 3: + addr.m+=1; + break; + case 4: + addr.m+=2; + break; + case 5: + addr = state->regs->VLAN_LOOKUP_REG_5; + break; + default: + addr.m = -1; // shouldn't get here, but... + break; + } + } + //printf("shareports for %d is %02X\n",j,state->ports[j].shareports); + if (REG_SUPP(addr)) { + SET_PORT_BITS(state, state->ports[j].shareports, addr, bit_lookup); + } + } + } + if (REG_SUPP(state->regs->TAG_VLAN_MASK_REG)) { + for (j=0; jregs->TAG_VLAN_MASK_REG; + const bitnum *bit_lookup = (j%2==0)? + state->regs->TAG_VLAN_MASK_EVEN_BIT: + state->regs->TAG_VLAN_MASK_ODD_BIT; + unsigned int vlan_mask; + if (state->regs->SIMPLE_VLAN_REGISTERS) { + addr.m += j; + } else { + addr.m += j/2; + } + vlan_mask = state->vlans[j].ports; + SET_PORT_BITS(state, vlan_mask, addr, bit_lookup); + } + } + + for (i=0; iregs->VLAN_DEFAULT_TAG_REG[i])) { + int err = setPhy(state, state->regs->VLAN_DEFAULT_TAG_REG[i], + state->ports[i].pvid); + if (err < 0) { + return err; + } + } + } + + return ip175c_set_flags(state); +} + +/** + * Uses only the VLAN port mask and the add tag mask to generate the other fields: + * which ports are part of the same VLAN, removing vlan tags, and VLAN tag ids. + */ +static void ip175c_correct_vlan_state(struct ip17xx_state *state) +{ + int i, j; + state->num_vlans = 0; + for (i=0; ivlans[i].ports != 0) { + state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere... + } + } + + for (i=0; iregs->NUM_PORTS; i++) { + unsigned int portmask = (1<vlan_enabled) { + // Share with everybody! + state->ports[i].shareports = (1<regs->NUM_PORTS)-1; + continue; + } + state->ports[i].shareports = portmask; + for (j=0; jvlans[j].ports & portmask) + state->ports[i].shareports |= state->vlans[j].ports; + } + } +} + +static int ip175c_update_state(struct ip17xx_state *state) +{ + ip175c_correct_vlan_state(state); + return ip175c_set_state(state); +} + +static int ip175c_set_vlan_mode(struct ip17xx_state *state) +{ + return ip175c_update_state(state); +} + +static int ip175c_reset(struct ip17xx_state *state) +{ + int err; + + if (REG_SUPP(state->regs->MODE_REG)) { + err = setPhy(state, state->regs->MODE_REG, state->regs->MODE_VAL); + if (err < 0) + return err; + err = getPhy(state, state->regs->MODE_REG); + if (err < 0) + return err; + } + + return ip175c_update_state(state); +} + +/*** Low-level functions for IP175D ***/ + +static int ip175d_update_state(struct ip17xx_state *state) +{ + unsigned int filter_mask = 0; + unsigned int ports[16], add[16], rem[16]; + int i, j; + int err = 0; + + for (i = 0; i < 16; i++) { + ports[i] = 0; + add[i] = 0; + rem[i] = 0; + if (!state->vlan_enabled) { + err |= ip_phy_write(state, 22, 14+i, i+1); // default tags + ports[i] = 0x3f; + continue; + } + if (!state->vlans[i].tag) { + // Reset the filter + err |= ip_phy_write(state, 22, 14+i, 0); // tag + continue; + } + filter_mask |= 1 << i; + err |= ip_phy_write(state, 22, 14+i, state->vlans[i].tag); + ports[i] = state->vlans[i].ports; + for (j = 0; j < 6; j++) { + if (ports[i] & (1 << j)) { + if (state->add_tag & (1 << j)) + add[i] |= 1 << j; + if (state->remove_tag & (1 << j)) + rem[i] |= 1 << j; + } + } + } + + // Port masks, tag adds and removals + for (i = 0; i < 8; i++) { + err |= ip_phy_write(state, 23, i, ports[2*i] | (ports[2*i+1] << 8)); + err |= ip_phy_write(state, 23, 8+i, add[2*i] | (add[2*i+1] << 8)); + err |= ip_phy_write(state, 23, 16+i, rem[2*i] | (rem[2*i+1] << 8)); + } + err |= ip_phy_write(state, 22, 10, filter_mask); + + // Default VLAN tag for each port + for (i = 0; i < 6; i++) + err |= ip_phy_write(state, 22, 4+i, state->vlans[state->ports[i].pvid].tag); + + return (err ? -EIO : 0); +} + +static int ip175d_set_vlan_mode(struct ip17xx_state *state) +{ + int i; + int err = 0; + + if (state->vlan_enabled) { + // VLAN classification rules: tag-based VLANs, use VID to classify, + // drop packets that cannot be classified. + err |= ip_phy_write_masked(state, 22, 0, 0x3fff, 0x003f); + + // Ingress rules: CFI=1 dropped, null VID is untagged, VID=1 passed, + // VID=0xfff discarded, admin both tagged and untagged, ingress + // filters enabled. + err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); + + // Egress rules: IGMP processing off, keep VLAN header off + err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); + } else { + // VLAN classification rules: everything off & clear table + err |= ip_phy_write_masked(state, 22, 0, 0xbfff, 0x8000); + + // Ingress and egress rules: set to defaults + err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); + err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); + } + + // Reset default VLAN for each port to 0 + for (i = 0; i < 6; i++) + state->ports[i].pvid = 0; + + err |= ip175d_update_state(state); + + return (err ? -EIO : 0); +} + +static int ip175d_reset(struct ip17xx_state *state) +{ + int err = 0; + + // Disable the special tagging mode + err |= ip_phy_write_masked(state, 21, 22, 0x0003, 0x0000); + + // Set 802.1q protocol type + err |= ip_phy_write(state, 22, 3, 0x8100); + + state->vlan_enabled = 0; + err |= ip175d_set_vlan_mode(state); + + return (err ? -EIO : 0); +} + +/*** High-level functions ***/ + +static int ip17xx_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->vlan_enabled; + return 0; +} + +static void ip17xx_reset_vlan_config(struct ip17xx_state *state) +{ + int i; + + state->remove_tag = (state->vlan_enabled ? ((1<regs->NUM_PORTS)-1) : 0x0000); + state->add_tag = 0x0000; + for (i = 0; i < MAX_VLANS; i++) { + state->vlans[i].ports = 0x0000; + state->vlans[i].tag = (i ? i : 16); + } + for (i = 0; i < MAX_PORTS; i++) + state->ports[i].pvid = 0; +} + +static int ip17xx_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int enable; + + enable = val->value.i; + if (state->vlan_enabled == enable) { + // Do not change any state. + return 0; + } + state->vlan_enabled = enable; + + // Otherwise, if we are switching state, set fields to a known default. + ip17xx_reset_vlan_config(state); + + return state->regs->set_vlan_mode(state); +} + +static int ip17xx_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int b; + int ind; + unsigned int ports; + + if (val->port_vlan >= dev->vlans || val->port_vlan < 0) + return -EINVAL; + + ports = state->vlans[val->port_vlan].ports; + b = 0; + ind = 0; + while (b < MAX_PORTS) { + if (ports&1) { + int istagged = ((state->add_tag >> b) & 1); + val->value.ports[ind].id = b; + val->value.ports[ind].flags = (istagged << SWITCH_PORT_FLAG_TAGGED); + ind++; + } + b++; + ports >>= 1; + } + val->len = ind; + + return 0; +} + +static int ip17xx_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int i; + + if (val->port_vlan >= dev->vlans || val->port_vlan < 0) + return -EINVAL; + + state->vlans[val->port_vlan].ports = 0; + for (i = 0; i < val->len; i++) { + unsigned int bitmask = (1<value.ports[i].id); + state->vlans[val->port_vlan].ports |= bitmask; + if (val->value.ports[i].flags & (1<add_tag |= bitmask; + state->remove_tag &= (~bitmask); + } else { + state->add_tag &= (~bitmask); + state->remove_tag |= bitmask; + } + } + + return state->regs->update_state(state); +} + +static int ip17xx_apply(struct switch_dev *dev) +{ + struct ip17xx_state *state = get_state(dev); + + if (REG_SUPP(state->regs->MII_REGISTER_EN)) { + int val = getPhy(state, state->regs->MII_REGISTER_EN); + if (val < 0) { + return val; + } + val |= (1<regs->MII_REGISTER_EN_BIT); + return setPhy(state, state->regs->MII_REGISTER_EN, val); + } + return 0; +} + +static int ip17xx_reset(struct switch_dev *dev) +{ + struct ip17xx_state *state = get_state(dev); + int i, err; + + if (REG_SUPP(state->regs->RESET_REG)) { + err = setPhy(state, state->regs->RESET_REG, state->regs->RESET_VAL); + if (err < 0) + return err; + err = getPhy(state, state->regs->RESET_REG); + + /* + * Data sheet specifies reset period to be 2 msec. + * (I don't see any mention of the 2ms delay in the IP178C spec, only + * in IP175C, but it can't hurt.) + */ + mdelay(2); + } + + /* reset switch ports */ + for (i = 0; i < state->regs->NUM_PORTS-1; i++) { + err = ip_phy_write(state, i, MII_BMCR, BMCR_RESET); + if (err < 0) + return err; + } + + state->router_mode = 0; + state->vlan_enabled = 0; + ip17xx_reset_vlan_config(state); + + return state->regs->reset(state); +} + +static int ip17xx_get_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + if (state->add_tag & (1<port_vlan)) { + if (state->remove_tag & (1<port_vlan)) + val->value.i = 3; // shouldn't ever happen. + else + val->value.i = 1; + } else { + if (state->remove_tag & (1<port_vlan)) + val->value.i = 0; + else + val->value.i = 2; + } + return 0; +} + +static int ip17xx_set_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + state->add_tag &= ~(1<port_vlan); + state->remove_tag &= ~(1<port_vlan); + + if (val->value.i == 0) + state->remove_tag |= (1<port_vlan); + if (val->value.i == 1) + state->add_tag |= (1<port_vlan); + + return state->regs->update_state(state); +} + +/** Get the current phy address */ +static int ip17xx_get_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->proc_mii.p; + return 0; +} + +/** Set a new phy address for low level access to registers */ +static int ip17xx_set_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int new_reg = val->value.i; + + if (new_reg < 0 || new_reg > 31) + state->proc_mii.p = (u16)-1; + else + state->proc_mii.p = (u16)new_reg; + return 0; +} + +/** Get the current register number */ +static int ip17xx_get_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->proc_mii.m; + return 0; +} + +/** Set a new register address for low level access to registers */ +static int ip17xx_set_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int new_reg = val->value.i; + + if (new_reg < 0 || new_reg > 31) + state->proc_mii.m = (u16)-1; + else + state->proc_mii.m = (u16)new_reg; + return 0; +} + +/** Get the register content of state->proc_mii */ +static int ip17xx_get_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int retval = -EINVAL; + if (REG_SUPP(state->proc_mii)) + retval = getPhy(state, state->proc_mii); + + if (retval < 0) { + return retval; + } else { + val->value.i = retval; + return 0; + } +} + +/** Write a value to the register defined by phy/reg above */ +static int ip17xx_set_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int myval, err = -EINVAL; + + myval = val->value.i; + if (myval <= 0xffff && myval >= 0 && REG_SUPP(state->proc_mii)) { + err = setPhy(state, state->proc_mii, (u16)myval); + } + return err; +} + +static int ip17xx_read_name(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + val->value.s = state->regs->NAME; // Just a const pointer, won't be freed by swconfig. + return 0; +} + +static int ip17xx_get_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int vlan = val->port_vlan; + + if (vlan < 0 || vlan >= MAX_VLANS) + return -EINVAL; + + val->value.i = state->vlans[vlan].tag; + return 0; +} + +static int ip17xx_set_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int vlan = val->port_vlan; + int tag = val->value.i; + + if (vlan < 0 || vlan >= MAX_VLANS) + return -EINVAL; + + if (tag < 0 || tag > 4095) + return -EINVAL; + + state->vlans[vlan].tag = tag; + return state->regs->update_state(state); +} + +static int ip17xx_set_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int nr = val->port_vlan; + int ctrl; + int autoneg; + int speed; + if (val->value.i == 100) { + speed = 1; + autoneg = 0; + } else if (val->value.i == 10) { + speed = 0; + autoneg = 0; + } else { + autoneg = 1; + speed = 1; + } + + /* Can't set speed for cpu port */ + if (nr == state->regs->CPU_PORT) + return -EINVAL; + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + ctrl = ip_phy_read(state, nr, 0); + if (ctrl < 0) + return -EIO; + + ctrl &= (~(1<<12)); + ctrl &= (~(1<<13)); + ctrl |= (autoneg<<12); + ctrl |= (speed<<13); + + return ip_phy_write(state, nr, 0, ctrl); +} + +static int ip17xx_get_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int nr = val->port_vlan; + int speed, status; + + if (nr == state->regs->CPU_PORT) { + val->value.i = 100; + return 0; + } + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + status = ip_phy_read(state, nr, 1); + speed = ip_phy_read(state, nr, 18); + if (status < 0 || speed < 0) + return -EIO; + + if (status & 4) + val->value.i = ((speed & (1<<11)) ? 100 : 10); + else + val->value.i = 0; + + return 0; +} + +static int ip17xx_get_port_status(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int ctrl, speed, status; + int nr = val->port_vlan; + int len; + char *buf = state->buf; // fixed-length at 80. + + if (nr == state->regs->CPU_PORT) { + sprintf(buf, "up, 100 Mbps, cpu port"); + val->value.s = buf; + return 0; + } + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + ctrl = ip_phy_read(state, nr, 0); + status = ip_phy_read(state, nr, 1); + speed = ip_phy_read(state, nr, 18); + if (ctrl < 0 || status < 0 || speed < 0) + return -EIO; + + if (status & 4) + len = sprintf(buf, "up, %d Mbps, %s duplex", + ((speed & (1<<11)) ? 100 : 10), + ((speed & (1<<10)) ? "full" : "half")); + else + len = sprintf(buf, "down"); + + if (ctrl & (1<<12)) { + len += sprintf(buf+len, ", auto-negotiate"); + if (!(status & (1<<5))) + len += sprintf(buf+len, " (in progress)"); + } else { + len += sprintf(buf+len, ", fixed speed (%d)", + ((ctrl & (1<<13)) ? 100 : 10)); + } + + buf[len] = '\0'; + val->value.s = buf; + return 0; +} + +static int ip17xx_get_pvid(struct switch_dev *dev, int port, int *val) +{ + struct ip17xx_state *state = get_state(dev); + + *val = state->ports[port].pvid; + return 0; +} + +static int ip17xx_set_pvid(struct switch_dev *dev, int port, int val) +{ + struct ip17xx_state *state = get_state(dev); + + if (val < 0 || val >= MAX_VLANS) + return -EINVAL; + + state->ports[port].pvid = val; + return state->regs->update_state(state); +} + + +enum Ports { + IP17XX_PORT_STATUS, + IP17XX_PORT_LINK, + IP17XX_PORT_TAGGED, + IP17XX_PORT_PVID, +}; + +enum Globals { + IP17XX_ENABLE_VLAN, + IP17XX_GET_NAME, + IP17XX_REGISTER_PHY, + IP17XX_REGISTER_MII, + IP17XX_REGISTER_VALUE, + IP17XX_REGISTER_ERRNO, +}; + +enum Vlans { + IP17XX_VLAN_TAG, +}; + +static const struct switch_attr ip17xx_global[] = { + [IP17XX_ENABLE_VLAN] = { + .id = IP17XX_ENABLE_VLAN, + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Flag to enable or disable VLANs and tagging", + .get = ip17xx_get_enable_vlan, + .set = ip17xx_set_enable_vlan, + }, + [IP17XX_GET_NAME] = { + .id = IP17XX_GET_NAME, + .type = SWITCH_TYPE_STRING, + .description = "Returns the type of IC+ chip.", + .name = "name", + .get = ip17xx_read_name, + .set = NULL, + }, + /* jal: added for low level debugging etc. */ + [IP17XX_REGISTER_PHY] = { + .id = IP17XX_REGISTER_PHY, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: set PHY (0-4, or 29,30,31)", + .name = "phy", + .get = ip17xx_get_phy, + .set = ip17xx_set_phy, + }, + [IP17XX_REGISTER_MII] = { + .id = IP17XX_REGISTER_MII, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: set MII register number (0-31)", + .name = "reg", + .get = ip17xx_get_reg, + .set = ip17xx_set_reg, + }, + [IP17XX_REGISTER_VALUE] = { + .id = IP17XX_REGISTER_VALUE, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: read/write to register (0-65535)", + .name = "val", + .get = ip17xx_get_val, + .set = ip17xx_set_val, + }, +}; + +static const struct switch_attr ip17xx_vlan[] = { + [IP17XX_VLAN_TAG] = { + .id = IP17XX_VLAN_TAG, + .type = SWITCH_TYPE_INT, + .description = "VLAN ID (0-4095) [IP175D only]", + .name = "vid", + .get = ip17xx_get_tag, + .set = ip17xx_set_tag, + } +}; + +static const struct switch_attr ip17xx_port[] = { + [IP17XX_PORT_STATUS] = { + .id = IP17XX_PORT_STATUS, + .type = SWITCH_TYPE_STRING, + .description = "Returns Detailed port status", + .name = "status", + .get = ip17xx_get_port_status, + .set = NULL, + }, + [IP17XX_PORT_LINK] = { + .id = IP17XX_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "Link speed. Can write 0 for auto-negotiate, or 10 or 100", + .name = "link", + .get = ip17xx_get_port_speed, + .set = ip17xx_set_port_speed, + }, + [IP17XX_PORT_TAGGED] = { + .id = IP17XX_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "0 = untag, 1 = add tags, 2 = do not alter (This value is reset if vlans are altered)", + .name = "tagged", + .get = ip17xx_get_tagged, + .set = ip17xx_set_tagged, + }, +}; + +static const struct switch_dev_ops ip17xx_ops = { + .attr_global = { + .attr = ip17xx_global, + .n_attr = ARRAY_SIZE(ip17xx_global), + }, + .attr_port = { + .attr = ip17xx_port, + .n_attr = ARRAY_SIZE(ip17xx_port), + }, + .attr_vlan = { + .attr = ip17xx_vlan, + .n_attr = ARRAY_SIZE(ip17xx_vlan), + }, + + .get_port_pvid = ip17xx_get_pvid, + .set_port_pvid = ip17xx_set_pvid, + .get_vlan_ports = ip17xx_get_ports, + .set_vlan_ports = ip17xx_set_ports, + .apply_config = ip17xx_apply, + .reset_switch = ip17xx_reset, +}; + +static int ip17xx_probe(struct phy_device *pdev) +{ + struct ip17xx_state *state; + struct switch_dev *dev; + int err; + + /* We only attach to PHY 0, but use all available PHYs */ + if (pdev->addr != 0) + return -ENODEV; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + dev = &state->dev; + + pdev->priv = state; + state->mii_bus = pdev->bus; + + err = get_model(state); + if (err < 0) + goto error; + + dev->vlans = MAX_VLANS; + dev->cpu_port = state->regs->CPU_PORT; + dev->ports = state->regs->NUM_PORTS; + dev->name = state->regs->NAME; + dev->ops = &ip17xx_ops; + + pr_info("IP17xx: Found %s at %s\n", dev->name, dev_name(&pdev->dev)); + return 0; + +error: + kfree(state); + return err; +} + +static int ip17xx_config_init(struct phy_device *pdev) +{ + struct ip17xx_state *state = pdev->priv; + struct net_device *dev = pdev->attached_dev; + int err; + + err = register_switch(&state->dev, dev); + if (err < 0) + return err; + + state->registered = true; + ip17xx_reset(&state->dev); + return 0; +} + +static void ip17xx_remove(struct phy_device *pdev) +{ + struct ip17xx_state *state = pdev->priv; + + if (state->registered) + unregister_switch(&state->dev); + kfree(state); +} + +static int ip17xx_config_aneg(struct phy_device *pdev) +{ + return 0; +} + +static int ip17xx_aneg_done(struct phy_device *pdev) +{ + return BMSR_ANEGCOMPLETE; +} + +static int ip17xx_update_link(struct phy_device *pdev) +{ + pdev->link = 1; + return 0; +} + +static int ip17xx_read_status(struct phy_device *pdev) +{ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->pause = pdev->asym_pause = 0; + pdev->link = 1; + + return 0; +} + +static struct phy_driver ip17xx_driver = { + .name = "IC+ IP17xx", + .phy_id = 0x02430c00, + .phy_id_mask = 0x0ffffc00, + .features = PHY_BASIC_FEATURES, + .probe = ip17xx_probe, + .remove = ip17xx_remove, + .config_init = ip17xx_config_init, + .config_aneg = ip17xx_config_aneg, + .aneg_done = ip17xx_aneg_done, + .update_link = ip17xx_update_link, + .read_status = ip17xx_read_status, + .driver = { .owner = THIS_MODULE }, +}; + +static struct phy_driver ip175a_driver = { + .name = "IC+ IP175A", + .phy_id = 0x02430c50, + .phy_id_mask = 0x0ffffff0, + .features = PHY_BASIC_FEATURES, + .probe = ip17xx_probe, + .remove = ip17xx_remove, + .config_init = ip17xx_config_init, + .config_aneg = ip17xx_config_aneg, + .aneg_done = ip17xx_aneg_done, + .update_link = ip17xx_update_link, + .read_status = ip17xx_read_status, + .driver = { .owner = THIS_MODULE }, +}; + + +int __init ip17xx_init(void) +{ + int ret; + + ret = phy_driver_register(&ip175a_driver); + if (ret < 0) + return ret; + + return phy_driver_register(&ip17xx_driver); +} + +void __exit ip17xx_exit(void) +{ + phy_driver_unregister(&ip17xx_driver); + phy_driver_unregister(&ip175a_driver); +} + +MODULE_AUTHOR("Patrick Horn "); +MODULE_AUTHOR("Felix Fietkau "); +MODULE_AUTHOR("Martin Mares "); +MODULE_LICENSE("GPL"); + +module_init(ip17xx_init); +module_exit(ip17xx_exit); diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 2d80e01e..1499d4a 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -1,261 +1,83 @@ /* - * drivers/net/phy/micrel.c + * Driver for Micrel/Kendin PHYs * - * Driver for Micrel PHYs + * Copyright (c) 2008-2009 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz * - * Author: David J. Choi + * 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. * - * Copyright (c) 2010 Micrel, Inc. - * - * 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. - * - * Support : ksz9021 1000/100/10 phy from Micrel - * ks8001, ks8737, ks8721, ks8041, ks8051 100/10 phy */ -#include #include +#include +#include #include -#include - -/* general Interrupt control/status reg in vendor specific block. */ -#define MII_KSZPHY_INTCS 0x1B -#define KSZPHY_INTCS_JABBER (1 << 15) -#define KSZPHY_INTCS_RECEIVE_ERR (1 << 14) -#define KSZPHY_INTCS_PAGE_RECEIVE (1 << 13) -#define KSZPHY_INTCS_PARELLEL (1 << 12) -#define KSZPHY_INTCS_LINK_PARTNER_ACK (1 << 11) -#define KSZPHY_INTCS_LINK_DOWN (1 << 10) -#define KSZPHY_INTCS_REMOTE_FAULT (1 << 9) -#define KSZPHY_INTCS_LINK_UP (1 << 8) -#define KSZPHY_INTCS_ALL (KSZPHY_INTCS_LINK_UP |\ - KSZPHY_INTCS_LINK_DOWN) - -/* general PHY control reg in vendor specific block. */ -#define MII_KSZPHY_CTRL 0x1F -/* bitmap of PHY register to set interrupt mode */ -#define KSZPHY_CTRL_INT_ACTIVE_HIGH (1 << 9) -#define KSZ9021_CTRL_INT_ACTIVE_HIGH (1 << 14) -#define KS8737_CTRL_INT_ACTIVE_HIGH (1 << 14) -#define KSZ8051_RMII_50MHZ_CLK (1 << 7) - -static int kszphy_ack_interrupt(struct phy_device *phydev) -{ - /* bit[7..0] int status, which is a read and clear register. */ - int rc; - - rc = phy_read(phydev, MII_KSZPHY_INTCS); - - return (rc < 0) ? rc : 0; -} - -static int kszphy_set_interrupt(struct phy_device *phydev) -{ - int temp; - temp = (PHY_INTERRUPT_ENABLED == phydev->interrupts) ? - KSZPHY_INTCS_ALL : 0; - return phy_write(phydev, MII_KSZPHY_INTCS, temp); -} -static int kszphy_config_intr(struct phy_device *phydev) -{ - int temp, rc; - - /* set the interrupt pin active low */ - temp = phy_read(phydev, MII_KSZPHY_CTRL); - temp &= ~KSZPHY_CTRL_INT_ACTIVE_HIGH; - phy_write(phydev, MII_KSZPHY_CTRL, temp); - rc = kszphy_set_interrupt(phydev); - return rc < 0 ? rc : 0; -} +#define KSZ_REG_INT_CTRL 0x1b -static int ksz9021_config_intr(struct phy_device *phydev) -{ - int temp, rc; +#define KSZ_INT_LU_EN (1 << 8) /* enable Link Up interrupt */ +#define KSZ_INT_RF_EN (1 << 9) /* enable Remote Fault interrupt */ +#define KSZ_INT_LD_EN (1 << 10) /* enable Link Down interrupt */ - /* set the interrupt pin active low */ - temp = phy_read(phydev, MII_KSZPHY_CTRL); - temp &= ~KSZ9021_CTRL_INT_ACTIVE_HIGH; - phy_write(phydev, MII_KSZPHY_CTRL, temp); - rc = kszphy_set_interrupt(phydev); - return rc < 0 ? rc : 0; -} +#define KSZ_INT_INIT (KSZ_INT_LU_EN | KSZ_INT_LD_EN) -static int ks8737_config_intr(struct phy_device *phydev) +static int ksz8041_ack_interrupt(struct phy_device *phydev) { - int temp, rc; + int err; - /* set the interrupt pin active low */ - temp = phy_read(phydev, MII_KSZPHY_CTRL); - temp &= ~KS8737_CTRL_INT_ACTIVE_HIGH; - phy_write(phydev, MII_KSZPHY_CTRL, temp); - rc = kszphy_set_interrupt(phydev); - return rc < 0 ? rc : 0; -} + err = phy_read(phydev, KSZ_REG_INT_CTRL); -static int kszphy_config_init(struct phy_device *phydev) -{ - return 0; + return (err < 0) ? err : 0; } -static int ks8051_config_init(struct phy_device *phydev) +static int ksz8041_config_intr(struct phy_device *phydev) { - int regval; + int err; - if (phydev->dev_flags & MICREL_PHY_50MHZ_CLK) { - regval = phy_read(phydev, MII_KSZPHY_CTRL); - regval |= KSZ8051_RMII_50MHZ_CLK; - phy_write(phydev, MII_KSZPHY_CTRL, regval); - } + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, KSZ_REG_INT_CTRL, + KSZ_INT_INIT); + else + err = phy_write(phydev, KSZ_REG_INT_CTRL, 0); - return 0; + return err; } -static struct phy_driver ks8737_driver = { - .phy_id = PHY_ID_KS8737, - .phy_id_mask = 0x00fffff0, - .name = "Micrel KS8737", - .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), - .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, - .config_init = kszphy_config_init, - .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, - .ack_interrupt = kszphy_ack_interrupt, - .config_intr = ks8737_config_intr, - .driver = { .owner = THIS_MODULE,}, -}; - -static struct phy_driver ks8041_driver = { - .phy_id = PHY_ID_KS8041, - .phy_id_mask = 0x00fffff0, - .name = "Micrel KS8041", - .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause - | SUPPORTED_Asym_Pause), - .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, - .config_init = kszphy_config_init, +static struct phy_driver ksz8041_phy_driver = { + .phy_id = 0x00221512, + .name = "Micrel KSZ8041", + .phy_id_mask = 0x001fffff, + .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, .config_aneg = genphy_config_aneg, .read_status = genphy_read_status, - .ack_interrupt = kszphy_ack_interrupt, - .config_intr = kszphy_config_intr, - .driver = { .owner = THIS_MODULE,}, + .ack_interrupt = ksz8041_ack_interrupt, + .config_intr = ksz8041_config_intr, + .driver = { + .owner = THIS_MODULE, + }, }; -static struct phy_driver ks8051_driver = { - .phy_id = PHY_ID_KS8051, - .phy_id_mask = 0x00fffff0, - .name = "Micrel KS8051", - .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause - | SUPPORTED_Asym_Pause), - .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, - .config_init = ks8051_config_init, - .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, - .ack_interrupt = kszphy_ack_interrupt, - .config_intr = kszphy_config_intr, - .driver = { .owner = THIS_MODULE,}, -}; - -static struct phy_driver ks8001_driver = { - .phy_id = PHY_ID_KS8001, - .name = "Micrel KS8001 or KS8721", - .phy_id_mask = 0x00fffff0, - .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), - .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, - .config_init = kszphy_config_init, - .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, - .ack_interrupt = kszphy_ack_interrupt, - .config_intr = kszphy_config_intr, - .driver = { .owner = THIS_MODULE,}, -}; - -static struct phy_driver ksz9021_driver = { - .phy_id = PHY_ID_KSZ9021, - .phy_id_mask = 0x000fff10, - .name = "Micrel KSZ9021 Gigabit PHY", - .features = (PHY_GBIT_FEATURES | SUPPORTED_Pause - | SUPPORTED_Asym_Pause), - .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, - .config_init = kszphy_config_init, - .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, - .ack_interrupt = kszphy_ack_interrupt, - .config_intr = ksz9021_config_intr, - .driver = { .owner = THIS_MODULE, }, -}; - -static int __init ksphy_init(void) +static int __init micrel_phy_init(void) { - int ret; - -#if 0 - ret = phy_driver_register(&ks8001_driver); - if (ret) - goto err1; - - ret = phy_driver_register(&ksz9021_driver); - if (ret) - goto err2; - - ret = phy_driver_register(&ks8737_driver); - if (ret) - goto err3; - ret = phy_driver_register(&ks8041_driver); - if (ret) - goto err4; -#endif - ret = phy_driver_register(&ks8051_driver); - if (ret) - goto err2; - - ret = phy_driver_register(&ksz9021_driver); - if (ret) - goto err1; - - return 0; - -#if 0 -err5: - phy_driver_unregister(&ks8041_driver); -err4: - phy_driver_unregister(&ks8737_driver); -err3: - phy_driver_unregister(&ksz9021_driver); -err2: - phy_driver_unregister(&ks8001_driver); -#endif -err2: - phy_driver_unregister(&ks8051_driver); -err1: - return ret; + return phy_driver_register(&ksz8041_phy_driver); } -static void __exit ksphy_exit(void) +static void __exit micrel_phy_exit(void) { - phy_driver_unregister(&ks8001_driver); - phy_driver_unregister(&ks8737_driver); - phy_driver_unregister(&ksz9021_driver); - phy_driver_unregister(&ks8041_driver); - phy_driver_unregister(&ks8051_driver); + phy_driver_unregister(&ksz8041_phy_driver); } -module_init(ksphy_init); -module_exit(ksphy_exit); - -MODULE_DESCRIPTION("Micrel PHY driver"); -MODULE_AUTHOR("David J. Choi"); -MODULE_LICENSE("GPL"); - -static struct mdio_device_id __maybe_unused micrel_tbl[] = { - { PHY_ID_KSZ9021, 0x000fff10 }, - { PHY_ID_KS8001, 0x00fffff0 }, - { PHY_ID_KS8737, 0x00fffff0 }, - { PHY_ID_KS8041, 0x00fffff0 }, - { PHY_ID_KS8051, 0x00fffff0 }, - { } -}; +#ifdef MODULE +module_init(micrel_phy_init); +module_exit(micrel_phy_exit); +#else +subsys_initcall(micrel_phy_init); +#endif -MODULE_DEVICE_TABLE(mdio, micrel_tbl); +MODULE_DESCRIPTION("Micrel/Kendin PHY driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Imre Kaloz "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mvswitch.c b/drivers/net/phy/mvswitch.c new file mode 100644 index 0000000..d754951 --- /dev/null +++ b/drivers/net/phy/mvswitch.c @@ -0,0 +1,422 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau + * + * 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "mvswitch.h" + +/* Undefine this to use trailer mode instead. + * I don't know if header mode works with all chips */ +#define HEADER_MODE 1 + +MODULE_DESCRIPTION("Marvell 88E6060 Switch driver"); +MODULE_AUTHOR("Felix Fietkau"); +MODULE_LICENSE("GPL"); + +#define MVSWITCH_MAGIC 0x88E6060 + +struct mvswitch_priv { + netdev_features_t orig_features; + u8 vlans[16]; +}; + +#define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv) + +static inline u16 +r16(struct phy_device *phydev, int addr, int reg) +{ + return phydev->bus->read(phydev->bus, addr, reg); +} + +static inline void +w16(struct phy_device *phydev, int addr, int reg, u16 val) +{ + phydev->bus->write(phydev->bus, addr, reg, val); +} + + +static struct sk_buff * +mvswitch_mangle_tx(struct net_device *dev, struct sk_buff *skb) +{ + struct mvswitch_priv *priv; + char *buf = NULL; + u16 vid; + + priv = dev->phy_ptr; + if (unlikely(!priv)) + goto error; + + if (unlikely(skb->len < 16)) + goto error; + +#ifdef HEADER_MODE + if (__vlan_hwaccel_get_tag(skb, &vid)) + goto error; + + if (skb_cloned(skb) || (skb->len <= 62) || (skb_headroom(skb) < MV_HEADER_SIZE)) { + if (pskb_expand_head(skb, MV_HEADER_SIZE, (skb->len < 62 ? 62 - skb->len : 0), GFP_ATOMIC)) + goto error_expand; + if (skb->len < 62) + skb->len = 62; + } + buf = skb_push(skb, MV_HEADER_SIZE); +#else + if (__vlan_get_tag(skb, &vid)) + goto error; + + if (unlikely((vid > 15 || !priv->vlans[vid]))) + goto error; + + if (skb->len <= 64) { + if (pskb_expand_head(skb, 0, 64 + MV_TRAILER_SIZE - skb->len, GFP_ATOMIC)) + goto error_expand; + + buf = skb->data + 64; + skb->len = 64 + MV_TRAILER_SIZE; + } else { + if (skb_cloned(skb) || unlikely(skb_tailroom(skb) < 4)) { + if (pskb_expand_head(skb, 0, 4, GFP_ATOMIC)) + goto error_expand; + } + buf = skb_put(skb, 4); + } + + /* move the ethernet header 4 bytes forward, overwriting the vlan tag */ + memmove(skb->data + 4, skb->data, 12); + skb->data += 4; + skb->len -= 4; + skb->mac_header += 4; +#endif + + if (!buf) + goto error; + + +#ifdef HEADER_MODE + /* prepend the tag */ + *((__be16 *) buf) = cpu_to_be16( + ((vid << MV_HEADER_VLAN_S) & MV_HEADER_VLAN_M) | + ((priv->vlans[vid] << MV_HEADER_PORTS_S) & MV_HEADER_PORTS_M) + ); +#else + /* append the tag */ + *((__be32 *) buf) = cpu_to_be32(( + (MV_TRAILER_OVERRIDE << MV_TRAILER_FLAGS_S) | + ((priv->vlans[vid] & MV_TRAILER_PORTS_M) << MV_TRAILER_PORTS_S) + )); +#endif + + return skb; + +error_expand: + if (net_ratelimit()) + printk("%s: failed to expand/update skb for the switch\n", dev->name); + +error: + /* any errors? drop the packet! */ + dev_kfree_skb_any(skb); + return NULL; +} + +static void +mvswitch_mangle_rx(struct net_device *dev, struct sk_buff *skb) +{ + struct mvswitch_priv *priv; + unsigned char *buf; + int vlan = -1; + int i; + + priv = dev->phy_ptr; + if (WARN_ON_ONCE(!priv)) + return; + +#ifdef HEADER_MODE + buf = skb->data; + skb_pull(skb, MV_HEADER_SIZE); +#else + buf = skb->data + skb->len - MV_TRAILER_SIZE; + if (buf[0] != 0x80) + return; +#endif + + /* look for the vlan matching the incoming port */ + for (i = 0; i < ARRAY_SIZE(priv->vlans); i++) { + if ((1 << buf[1]) & priv->vlans[i]) + vlan = i; + } + + if (vlan == -1) + return; + + __vlan_hwaccel_put_tag(skb, vlan); +} + + +static int +mvswitch_wait_mask(struct phy_device *pdev, int addr, int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = r16(pdev, addr, reg) & mask; + if (r == val) + return 0; + } while(--i > 0); + return -ETIMEDOUT; +} + +static int +mvswitch_config_init(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + struct net_device *dev = pdev->attached_dev; + u8 vlmap = 0; + int i; + + if (!dev) + return -EINVAL; + + printk("%s: Marvell 88E6060 PHY driver attached.\n", dev->name); + pdev->supported = ADVERTISED_100baseT_Full; + pdev->advertising = ADVERTISED_100baseT_Full; + dev->phy_ptr = priv; + pdev->irq = PHY_POLL; +#ifdef HEADER_MODE + dev->flags |= IFF_PROMISC; +#endif + + /* initialize default vlans */ + for (i = 0; i < MV_PORTS; i++) + priv->vlans[(i == MV_WANPORT ? 2 : 1)] |= (1 << i); + + /* before entering reset, disable all ports */ + for (i = 0; i < MV_PORTS; i++) + w16(pdev, MV_PORTREG(CONTROL, i), 0x00); + + msleep(2); /* wait for the status change to settle in */ + + /* put the ATU in reset */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET); + + i = mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET, 0); + if (i < 0) { + printk("%s: Timeout waiting for the switch to reset.\n", dev->name); + return i; + } + + /* set the ATU flags */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), + MV_ATUCTL_NO_LEARN | + MV_ATUCTL_ATU_1K | + MV_ATUCTL_AGETIME(MV_ATUCTL_AGETIME_MIN) /* minimum without disabling ageing */ + ); + + /* initialize the cpu port */ + w16(pdev, MV_PORTREG(CONTROL, MV_CPUPORT), +#ifdef HEADER_MODE + MV_PORTCTRL_HEADER | +#else + MV_PORTCTRL_RXTR | + MV_PORTCTRL_TXTR | +#endif + MV_PORTCTRL_ENABLED + ); + /* wait for the phy change to settle in */ + msleep(2); + for (i = 0; i < MV_PORTS; i++) { + u8 pvid = 0; + int j; + + vlmap = 0; + + /* look for the matching vlan */ + for (j = 0; j < ARRAY_SIZE(priv->vlans); j++) { + if (priv->vlans[j] & (1 << i)) { + vlmap = priv->vlans[j]; + pvid = j; + } + } + /* leave port unconfigured if it's not part of a vlan */ + if (!vlmap) + continue; + + /* add the cpu port to the allowed destinations list */ + vlmap |= (1 << MV_CPUPORT); + + /* take port out of its own vlan destination map */ + vlmap &= ~(1 << i); + + /* apply vlan settings */ + w16(pdev, MV_PORTREG(VLANMAP, i), + MV_PORTVLAN_PORTS(vlmap) | + MV_PORTVLAN_ID(i) + ); + + /* re-enable port */ + w16(pdev, MV_PORTREG(CONTROL, i), + MV_PORTCTRL_ENABLED + ); + } + + w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT), + MV_PORTVLAN_ID(MV_CPUPORT) + ); + + /* set the port association vector */ + for (i = 0; i <= MV_PORTS; i++) { + w16(pdev, MV_PORTREG(ASSOC, i), + MV_PORTASSOC_PORTS(1 << i) + ); + } + + /* init switch control */ + w16(pdev, MV_SWITCHREG(CTRL), + MV_SWITCHCTL_MSIZE | + MV_SWITCHCTL_DROP + ); + + dev->eth_mangle_rx = mvswitch_mangle_rx; + dev->eth_mangle_tx = mvswitch_mangle_tx; + priv->orig_features = dev->features; + +#ifdef HEADER_MODE + dev->priv_flags |= IFF_NO_IP_ALIGN; + dev->features |= NETIF_F_HW_VLAN_RX | NETIF_F_HW_VLAN_TX; +#else + dev->features |= NETIF_F_HW_VLAN_RX; +#endif + + return 0; +} + +static int +mvswitch_read_status(struct phy_device *pdev) +{ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + + /* XXX ugly workaround: we can't force the switch + * to gracefully handle hosts moving from one port to another, + * so we have to regularly clear the ATU database */ + + /* wait for the ATU to become available */ + mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); + + /* flush the ATU */ + w16(pdev, MV_SWITCHREG(ATU_OP), + MV_ATUOP_INPROGRESS | + MV_ATUOP_FLUSH_ALL + ); + + /* wait for operation to complete */ + mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); + + return 0; +} + +static int +mvswitch_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static void +mvswitch_remove(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + struct net_device *dev = pdev->attached_dev; + + dev->phy_ptr = NULL; + dev->eth_mangle_rx = NULL; + dev->eth_mangle_tx = NULL; + dev->features = priv->orig_features; + dev->priv_flags &= ~IFF_NO_IP_ALIGN; + kfree(priv); +} + +static int +mvswitch_probe(struct phy_device *pdev) +{ + struct mvswitch_priv *priv; + + priv = kzalloc(sizeof(struct mvswitch_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + pdev->priv = priv; + + return 0; +} + +static int +mvswitch_fixup(struct phy_device *dev) +{ + u16 reg; + + if (dev->addr != 0x10) + return 0; + + reg = dev->bus->read(dev->bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; + if (reg != MV_IDENT_VALUE) + return 0; + + dev->phy_id = MVSWITCH_MAGIC; + return 0; +} + + +static struct phy_driver mvswitch_driver = { + .name = "Marvell 88E6060", + .phy_id = MVSWITCH_MAGIC, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = &mvswitch_probe, + .remove = &mvswitch_remove, + .config_init = &mvswitch_config_init, + .config_aneg = &mvswitch_config_aneg, + .read_status = &mvswitch_read_status, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init +mvswitch_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup); + return phy_driver_register(&mvswitch_driver); +} + +static void __exit +mvswitch_exit(void) +{ + phy_driver_unregister(&mvswitch_driver); +} + +module_init(mvswitch_init); +module_exit(mvswitch_exit); diff --git a/drivers/net/phy/mvswitch.h b/drivers/net/phy/mvswitch.h new file mode 100644 index 0000000..1563eec --- /dev/null +++ b/drivers/net/phy/mvswitch.h @@ -0,0 +1,145 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau + * + * 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 + */ +#ifndef __MVSWITCH_H +#define __MVSWITCH_H + +#define MV_HEADER_SIZE 2 +#define MV_HEADER_PORTS_M 0x001f +#define MV_HEADER_PORTS_S 0 +#define MV_HEADER_VLAN_M 0xf000 +#define MV_HEADER_VLAN_S 12 + +#define MV_TRAILER_SIZE 4 +#define MV_TRAILER_PORTS_M 0x1f +#define MV_TRAILER_PORTS_S 16 +#define MV_TRAILER_FLAGS_S 24 +#define MV_TRAILER_OVERRIDE 0x80 + + +#define MV_PORTS 5 +#define MV_WANPORT 4 +#define MV_CPUPORT 5 + +#define MV_BASE 0x10 + +#define MV_PHYPORT_BASE (MV_BASE + 0x0) +#define MV_PHYPORT(_n) (MV_PHYPORT_BASE + (_n)) +#define MV_SWITCHPORT_BASE (MV_BASE + 0x8) +#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) +#define MV_SWITCHREGS (MV_BASE + 0xf) + +enum { + MV_PHY_CONTROL = 0x00, + MV_PHY_STATUS = 0x01, + MV_PHY_IDENT0 = 0x02, + MV_PHY_IDENT1 = 0x03, + MV_PHY_ANEG = 0x04, + MV_PHY_LINK_ABILITY = 0x05, + MV_PHY_ANEG_EXPAND = 0x06, + MV_PHY_XMIT_NEXTP = 0x07, + MV_PHY_LINK_NEXTP = 0x08, + MV_PHY_CONTROL1 = 0x10, + MV_PHY_STATUS1 = 0x11, + MV_PHY_INTR_EN = 0x12, + MV_PHY_INTR_STATUS = 0x13, + MV_PHY_INTR_PORT = 0x14, + MV_PHY_RECV_COUNTER = 0x16, + MV_PHY_LED_PARALLEL = 0x16, + MV_PHY_LED_STREAM = 0x17, + MV_PHY_LED_CTRL = 0x18, + MV_PHY_LED_OVERRIDE = 0x19, + MV_PHY_VCT_CTRL = 0x1a, + MV_PHY_VCT_STATUS = 0x1b, + MV_PHY_CONTROL2 = 0x1e +}; +#define MV_PHYREG(_type, _port) MV_PHYPORT(_port), MV_PHY_##_type + +enum { + MV_PORT_STATUS = 0x00, + MV_PORT_IDENT = 0x03, + MV_PORT_CONTROL = 0x04, + MV_PORT_VLANMAP = 0x06, + MV_PORT_ASSOC = 0x0b, + MV_PORT_RXCOUNT = 0x10, + MV_PORT_TXCOUNT = 0x11, +}; +#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type + +enum { + MV_PORTCTRL_BLOCK = (1 << 0), + MV_PORTCTRL_LEARN = (2 << 0), + MV_PORTCTRL_ENABLED = (3 << 0), + MV_PORTCTRL_VLANTUN = (1 << 7), /* Enforce VLANs on packets */ + MV_PORTCTRL_RXTR = (1 << 8), /* Enable Marvell packet trailer for ingress */ + MV_PORTCTRL_HEADER = (1 << 11), /* Enable Marvell packet header mode for port */ + MV_PORTCTRL_TXTR = (1 << 14), /* Enable Marvell packet trailer for egress */ + MV_PORTCTRL_FORCEFL = (1 << 15), /* force flow control */ +}; + +#define MV_PORTVLAN_ID(_n) (((_n) & 0xf) << 12) +#define MV_PORTVLAN_PORTS(_n) ((_n) & 0x3f) + +#define MV_PORTASSOC_PORTS(_n) ((_n) & 0x1f) +#define MV_PORTASSOC_MONITOR (1 << 15) + +enum { + MV_SWITCH_MAC0 = 0x01, + MV_SWITCH_MAC1 = 0x02, + MV_SWITCH_MAC2 = 0x03, + MV_SWITCH_CTRL = 0x04, + MV_SWITCH_ATU_CTRL = 0x0a, + MV_SWITCH_ATU_OP = 0x0b, + MV_SWITCH_ATU_DATA = 0x0c, + MV_SWITCH_ATU_MAC0 = 0x0d, + MV_SWITCH_ATU_MAC1 = 0x0e, + MV_SWITCH_ATU_MAC2 = 0x0f, +}; +#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type + +enum { + MV_SWITCHCTL_EEIE = (1 << 0), /* EEPROM interrupt enable */ + MV_SWITCHCTL_PHYIE = (1 << 1), /* PHY interrupt enable */ + MV_SWITCHCTL_ATUDONE= (1 << 2), /* ATU done interrupt enable */ + MV_SWITCHCTL_ATUIE = (1 << 3), /* ATU interrupt enable */ + MV_SWITCHCTL_CTRMODE= (1 << 8), /* statistics for rx and tx errors */ + MV_SWITCHCTL_RELOAD = (1 << 9), /* reload registers from eeprom */ + MV_SWITCHCTL_MSIZE = (1 << 10), /* increase maximum frame size */ + MV_SWITCHCTL_DROP = (1 << 13), /* discard frames with excessive collisions */ +}; + +enum { +#define MV_ATUCTL_AGETIME_MIN 16 +#define MV_ATUCTL_AGETIME_MAX 4080 +#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) + MV_ATUCTL_ATU_256 = (0 << 12), + MV_ATUCTL_ATU_512 = (1 << 12), + MV_ATUCTL_ATU_1K = (2 << 12), + MV_ATUCTL_ATUMASK = (3 << 12), + MV_ATUCTL_NO_LEARN = (1 << 14), + MV_ATUCTL_RESET = (1 << 15), +}; + +enum { +#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) + + MV_ATUOP_NOOP = (0 << 12), + MV_ATUOP_FLUSH_ALL = (1 << 12), + MV_ATUOP_FLUSH_U = (2 << 12), + MV_ATUOP_LOAD_DB = (3 << 12), + MV_ATUOP_GET_NEXT = (4 << 12), + MV_ATUOP_FLUSH_DB = (5 << 12), + MV_ATUOP_FLUSH_DB_UU= (6 << 12), + + MV_ATUOP_INPROGRESS = (1 << 15), +}; + +#define MV_IDENT_MASK 0xfff0 +#define MV_IDENT_VALUE 0x0600 + +#endif diff --git a/drivers/net/phy/psb6970.c b/drivers/net/phy/psb6970.c new file mode 100644 index 0000000..da4df23 --- /dev/null +++ b/drivers/net/phy/psb6970.c @@ -0,0 +1,438 @@ +/* + * 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 +#include +#include +#include + +#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) +{ + return phydev->bus->read(phydev->bus, PHYADDR(reg)); +} + +static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val) +{ + phydev->bus->write(phydev->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->addr == 0) + printk(KERN_INFO "%s: psb6970 switch driver attached.\n", + pdev->attached_dev->name); + + if (pdev->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->addr == 0) + unregister_switch(&priv->dev); + kfree(priv); +} + +static int psb6970_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->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, + .driver = {.owner = THIS_MODULE}, +}; + +int __init psb6970_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup); + return phy_driver_register(&psb6970_driver); +} + +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 "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/rtl8306.c b/drivers/net/phy/rtl8306.c new file mode 100644 index 0000000..15ffda4 --- /dev/null +++ b/drivers/net/phy/rtl8306.c @@ -0,0 +1,1055 @@ +/* + * rtl8306.c: RTL8306S switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG 1 + +/* Global (PHY0) */ +#define RTL8306_REG_PAGE 16 +#define RTL8306_REG_PAGE_LO (1 << 15) +#define RTL8306_REG_PAGE_HI (1 << 1) /* inverted */ + +#define RTL8306_NUM_VLANS 16 +#define RTL8306_NUM_PORTS 6 +#define RTL8306_PORT_CPU 5 +#define RTL8306_NUM_PAGES 4 +#define RTL8306_NUM_REGS 32 + +#define RTL_NAME_S "RTL8306S" +#define RTL_NAME_SD "RTL8306SD" +#define RTL_NAME_SDM "RTL8306SDM" +#define RTL_NAME_UNKNOWN "RTL8306(unknown)" + +#define RTL8306_MAGIC 0x8306 + +static LIST_HEAD(phydevs); + +struct rtl_priv { + struct list_head list; + struct switch_dev dev; + int page; + int type; + int do_cpu; + struct mii_bus *bus; + char hwname[sizeof(RTL_NAME_UNKNOWN)]; + bool fixup; +}; + +struct rtl_phyregs { + int nway; + int speed; + int duplex; +}; + +#define to_rtl(_dev) container_of(_dev, struct rtl_priv, dev) + +enum { + RTL_TYPE_S, + RTL_TYPE_SD, + RTL_TYPE_SDM, +}; + +struct rtl_reg { + int page; + int phy; + int reg; + int bits; + int shift; + int inverted; +}; + +#define RTL_VLAN_REGOFS(name) \ + (RTL_REG_VLAN1_##name - RTL_REG_VLAN0_##name) + +#define RTL_PORT_REGOFS(name) \ + (RTL_REG_PORT1_##name - RTL_REG_PORT0_##name) + +#define RTL_PORT_REG(id, reg) \ + (RTL_REG_PORT0_##reg + (id * RTL_PORT_REGOFS(reg))) + +#define RTL_VLAN_REG(id, reg) \ + (RTL_REG_VLAN0_##reg + (id * RTL_VLAN_REGOFS(reg))) + +#define RTL_GLOBAL_REGATTR(reg) \ + .id = RTL_REG_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = 0, \ + .set = rtl_attr_set_int, \ + .get = rtl_attr_get_int + +#define RTL_PORT_REGATTR(reg) \ + .id = RTL_REG_PORT0_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = RTL_PORT_REGOFS(reg), \ + .set = rtl_attr_set_port_int, \ + .get = rtl_attr_get_port_int + +#define RTL_VLAN_REGATTR(reg) \ + .id = RTL_REG_VLAN0_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = RTL_VLAN_REGOFS(reg), \ + .set = rtl_attr_set_vlan_int, \ + .get = rtl_attr_get_vlan_int + +enum rtl_regidx { + RTL_REG_CHIPID, + RTL_REG_CHIPVER, + RTL_REG_CHIPTYPE, + RTL_REG_CPUPORT, + + RTL_REG_EN_CPUPORT, + RTL_REG_EN_TAG_OUT, + RTL_REG_EN_TAG_CLR, + RTL_REG_EN_TAG_IN, + RTL_REG_TRAP_CPU, + RTL_REG_TRUNK_PORTSEL, + RTL_REG_EN_TRUNK, + RTL_REG_RESET, + + RTL_REG_VLAN_ENABLE, + RTL_REG_VLAN_FILTER, + RTL_REG_VLAN_TAG_ONLY, + RTL_REG_VLAN_TAG_AWARE, +#define RTL_VLAN_ENUM(id) \ + RTL_REG_VLAN##id##_VID, \ + RTL_REG_VLAN##id##_PORTMASK + RTL_VLAN_ENUM(0), + RTL_VLAN_ENUM(1), + RTL_VLAN_ENUM(2), + RTL_VLAN_ENUM(3), + RTL_VLAN_ENUM(4), + RTL_VLAN_ENUM(5), + RTL_VLAN_ENUM(6), + RTL_VLAN_ENUM(7), + RTL_VLAN_ENUM(8), + RTL_VLAN_ENUM(9), + RTL_VLAN_ENUM(10), + RTL_VLAN_ENUM(11), + RTL_VLAN_ENUM(12), + RTL_VLAN_ENUM(13), + RTL_VLAN_ENUM(14), + RTL_VLAN_ENUM(15), +#define RTL_PORT_ENUM(id) \ + RTL_REG_PORT##id##_PVID, \ + RTL_REG_PORT##id##_NULL_VID_REPLACE, \ + RTL_REG_PORT##id##_NON_PVID_DISCARD, \ + RTL_REG_PORT##id##_VID_INSERT, \ + RTL_REG_PORT##id##_TAG_INSERT, \ + RTL_REG_PORT##id##_LINK, \ + RTL_REG_PORT##id##_SPEED, \ + RTL_REG_PORT##id##_NWAY, \ + RTL_REG_PORT##id##_NRESTART, \ + RTL_REG_PORT##id##_DUPLEX, \ + RTL_REG_PORT##id##_RXEN, \ + RTL_REG_PORT##id##_TXEN + RTL_PORT_ENUM(0), + RTL_PORT_ENUM(1), + RTL_PORT_ENUM(2), + RTL_PORT_ENUM(3), + RTL_PORT_ENUM(4), + RTL_PORT_ENUM(5), +}; + +static const struct rtl_reg rtl_regs[] = { + [RTL_REG_CHIPID] = { 0, 4, 30, 16, 0, 0 }, + [RTL_REG_CHIPVER] = { 0, 4, 31, 8, 0, 0 }, + [RTL_REG_CHIPTYPE] = { 0, 4, 31, 2, 8, 0 }, + + /* CPU port number */ + [RTL_REG_CPUPORT] = { 2, 4, 21, 3, 0, 0 }, + /* Enable CPU port function */ + [RTL_REG_EN_CPUPORT] = { 3, 2, 21, 1, 15, 1 }, + /* Enable CPU port tag insertion */ + [RTL_REG_EN_TAG_OUT] = { 3, 2, 21, 1, 12, 0 }, + /* Enable CPU port tag removal */ + [RTL_REG_EN_TAG_CLR] = { 3, 2, 21, 1, 11, 0 }, + /* Enable CPU port tag checking */ + [RTL_REG_EN_TAG_IN] = { 0, 4, 21, 1, 7, 0 }, + [RTL_REG_EN_TRUNK] = { 0, 0, 19, 1, 11, 1 }, + [RTL_REG_TRUNK_PORTSEL] = { 0, 0, 16, 1, 6, 1 }, + [RTL_REG_RESET] = { 0, 0, 16, 1, 12, 0 }, + + [RTL_REG_TRAP_CPU] = { 3, 2, 22, 1, 6, 0 }, + + [RTL_REG_VLAN_TAG_ONLY] = { 0, 0, 16, 1, 8, 1 }, + [RTL_REG_VLAN_FILTER] = { 0, 0, 16, 1, 9, 1 }, + [RTL_REG_VLAN_TAG_AWARE] = { 0, 0, 16, 1, 10, 1 }, + [RTL_REG_VLAN_ENABLE] = { 0, 0, 18, 1, 8, 1 }, + +#define RTL_VLAN_REGS(id, phy, page, regofs) \ + [RTL_REG_VLAN##id##_VID] = { page, phy, 25 + regofs, 12, 0, 0 }, \ + [RTL_REG_VLAN##id##_PORTMASK] = { page, phy, 24 + regofs, 6, 0, 0 } + RTL_VLAN_REGS( 0, 0, 0, 0), + RTL_VLAN_REGS( 1, 1, 0, 0), + RTL_VLAN_REGS( 2, 2, 0, 0), + RTL_VLAN_REGS( 3, 3, 0, 0), + RTL_VLAN_REGS( 4, 4, 0, 0), + RTL_VLAN_REGS( 5, 0, 1, 2), + RTL_VLAN_REGS( 6, 1, 1, 2), + RTL_VLAN_REGS( 7, 2, 1, 2), + RTL_VLAN_REGS( 8, 3, 1, 2), + RTL_VLAN_REGS( 9, 4, 1, 2), + RTL_VLAN_REGS(10, 0, 1, 4), + RTL_VLAN_REGS(11, 1, 1, 4), + RTL_VLAN_REGS(12, 2, 1, 4), + RTL_VLAN_REGS(13, 3, 1, 4), + RTL_VLAN_REGS(14, 4, 1, 4), + RTL_VLAN_REGS(15, 0, 1, 6), + +#define REG_PORT_SETTING(port, phy) \ + [RTL_REG_PORT##port##_SPEED] = { 0, phy, 0, 1, 13, 0 }, \ + [RTL_REG_PORT##port##_NWAY] = { 0, phy, 0, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NRESTART] = { 0, phy, 0, 1, 9, 0 }, \ + [RTL_REG_PORT##port##_DUPLEX] = { 0, phy, 0, 1, 8, 0 }, \ + [RTL_REG_PORT##port##_TXEN] = { 0, phy, 24, 1, 11, 0 }, \ + [RTL_REG_PORT##port##_RXEN] = { 0, phy, 24, 1, 10, 0 }, \ + [RTL_REG_PORT##port##_LINK] = { 0, phy, 1, 1, 2, 0 }, \ + [RTL_REG_PORT##port##_NULL_VID_REPLACE] = { 0, phy, 22, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NON_PVID_DISCARD] = { 0, phy, 22, 1, 11, 0 }, \ + [RTL_REG_PORT##port##_VID_INSERT] = { 0, phy, 22, 2, 9, 0 }, \ + [RTL_REG_PORT##port##_TAG_INSERT] = { 0, phy, 22, 2, 0, 0 } + + REG_PORT_SETTING(0, 0), + REG_PORT_SETTING(1, 1), + REG_PORT_SETTING(2, 2), + REG_PORT_SETTING(3, 3), + REG_PORT_SETTING(4, 4), + REG_PORT_SETTING(5, 6), + +#define REG_PORT_PVID(phy, page, regofs) \ + { page, phy, 24 + regofs, 4, 12, 0 } + [RTL_REG_PORT0_PVID] = REG_PORT_PVID(0, 0, 0), + [RTL_REG_PORT1_PVID] = REG_PORT_PVID(1, 0, 0), + [RTL_REG_PORT2_PVID] = REG_PORT_PVID(2, 0, 0), + [RTL_REG_PORT3_PVID] = REG_PORT_PVID(3, 0, 0), + [RTL_REG_PORT4_PVID] = REG_PORT_PVID(4, 0, 0), + [RTL_REG_PORT5_PVID] = REG_PORT_PVID(0, 1, 2), +}; + + +static inline void +rtl_set_page(struct rtl_priv *priv, unsigned int page) +{ + struct mii_bus *bus = priv->bus; + u16 pgsel; + + if (priv->fixup) + return; + + if (priv->page == page) + return; + + BUG_ON(page > RTL8306_NUM_PAGES); + pgsel = bus->read(bus, 0, RTL8306_REG_PAGE); + pgsel &= ~(RTL8306_REG_PAGE_LO | RTL8306_REG_PAGE_HI); + if (page & (1 << 0)) + pgsel |= RTL8306_REG_PAGE_LO; + if (!(page & (1 << 1))) /* bit is inverted */ + pgsel |= RTL8306_REG_PAGE_HI; + bus->write(bus, 0, RTL8306_REG_PAGE, pgsel); +} + +static inline int +rtl_w16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + + rtl_set_page(priv, page); + bus->write(bus, phy, reg, val); + bus->read(bus, phy, reg); /* flush */ + return 0; +} + +static inline int +rtl_r16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + + rtl_set_page(priv, page); + return bus->read(bus, phy, reg); +} + +static inline u16 +rtl_rmw(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 mask, u16 val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + u16 r; + + rtl_set_page(priv, page); + r = bus->read(bus, phy, reg); + r &= ~mask; + r |= val; + bus->write(bus, phy, reg, r); + return bus->read(bus, phy, reg); /* flush */ +} + + +static inline int +rtl_get(struct switch_dev *dev, enum rtl_regidx s) +{ + const struct rtl_reg *r = &rtl_regs[s]; + u16 val; + + BUG_ON(s >= ARRAY_SIZE(rtl_regs)); + if (r->bits == 0) /* unimplemented */ + return 0; + + val = rtl_r16(dev, r->page, r->phy, r->reg); + + if (r->shift > 0) + val >>= r->shift; + + if (r->inverted) + val = ~val; + + val &= (1 << r->bits) - 1; + + return val; +} + +static int +rtl_set(struct switch_dev *dev, enum rtl_regidx s, unsigned int val) +{ + const struct rtl_reg *r = &rtl_regs[s]; + u16 mask = 0xffff; + + BUG_ON(s >= ARRAY_SIZE(rtl_regs)); + + if (r->bits == 0) /* unimplemented */ + return 0; + + if (r->shift > 0) + val <<= r->shift; + + if (r->inverted) + val = ~val; + + if (r->bits != 16) { + mask = (1 << r->bits) - 1; + mask <<= r->shift; + } + val &= mask; + return rtl_rmw(dev, r->page, r->phy, r->reg, mask, val); +} + +static void +rtl_phy_save(struct switch_dev *dev, int port, struct rtl_phyregs *regs) +{ + regs->nway = rtl_get(dev, RTL_PORT_REG(port, NWAY)); + regs->speed = rtl_get(dev, RTL_PORT_REG(port, SPEED)); + regs->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); +} + +static void +rtl_phy_restore(struct switch_dev *dev, int port, struct rtl_phyregs *regs) +{ + rtl_set(dev, RTL_PORT_REG(port, NWAY), regs->nway); + rtl_set(dev, RTL_PORT_REG(port, SPEED), regs->speed); + rtl_set(dev, RTL_PORT_REG(port, DUPLEX), regs->duplex); +} + +static void +rtl_port_set_enable(struct switch_dev *dev, int port, int enabled) +{ + rtl_set(dev, RTL_PORT_REG(port, RXEN), enabled); + rtl_set(dev, RTL_PORT_REG(port, TXEN), enabled); + + if ((port >= 5) || !enabled) + return; + + /* restart autonegotiation if enabled */ + rtl_set(dev, RTL_PORT_REG(port, NRESTART), 1); +} + +static int +rtl_hw_apply(struct switch_dev *dev) +{ + int i; + int trunk_en, trunk_psel; + struct rtl_phyregs port5; + + rtl_phy_save(dev, 5, &port5); + + /* disable rx/tx from PHYs */ + for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { + rtl_port_set_enable(dev, i, 0); + } + + /* save trunking status */ + trunk_en = rtl_get(dev, RTL_REG_EN_TRUNK); + trunk_psel = rtl_get(dev, RTL_REG_TRUNK_PORTSEL); + + /* trunk port 3 and 4 + * XXX: Big WTF, but RealTek seems to do it */ + rtl_set(dev, RTL_REG_EN_TRUNK, 1); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 1); + + /* execute the software reset */ + rtl_set(dev, RTL_REG_RESET, 1); + + /* wait for the reset to complete, + * but don't wait for too long */ + for (i = 0; i < 10; i++) { + if (rtl_get(dev, RTL_REG_RESET) == 0) + break; + + msleep(1); + } + + /* enable rx/tx from PHYs */ + for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { + rtl_port_set_enable(dev, i, 1); + } + + /* restore trunking settings */ + rtl_set(dev, RTL_REG_EN_TRUNK, trunk_en); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, trunk_psel); + rtl_phy_restore(dev, 5, &port5); + + return 0; +} + +static void +rtl_hw_init(struct switch_dev *dev) +{ + struct rtl_priv *priv = to_rtl(dev); + int cpu_mask = 1 << dev->cpu_port; + int i; + + rtl_set(dev, RTL_REG_VLAN_ENABLE, 0); + rtl_set(dev, RTL_REG_VLAN_FILTER, 0); + rtl_set(dev, RTL_REG_EN_TRUNK, 0); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 0); + + /* initialize cpu port settings */ + if (priv->do_cpu) { + rtl_set(dev, RTL_REG_CPUPORT, dev->cpu_port); + rtl_set(dev, RTL_REG_EN_CPUPORT, 1); + } else { + rtl_set(dev, RTL_REG_CPUPORT, 7); + rtl_set(dev, RTL_REG_EN_CPUPORT, 0); + } + rtl_set(dev, RTL_REG_EN_TAG_OUT, 0); + rtl_set(dev, RTL_REG_EN_TAG_IN, 0); + rtl_set(dev, RTL_REG_EN_TAG_CLR, 0); + + /* reset all vlans */ + for (i = 0; i < RTL8306_NUM_VLANS; i++) { + rtl_set(dev, RTL_VLAN_REG(i, VID), i); + rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), 0); + } + + /* default to port isolation */ + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + unsigned long mask; + + if ((1 << i) == cpu_mask) + mask = ((1 << RTL8306_NUM_PORTS) - 1) & ~cpu_mask; /* all bits set */ + else + mask = cpu_mask | (1 << i); + + rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), mask); + rtl_set(dev, RTL_PORT_REG(i, PVID), i); + rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); + rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), 1); + rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), 3); + } + rtl_hw_apply(dev); +} + +#ifdef DEBUG +static int +rtl_set_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + priv->do_cpu = val->value.i; + rtl_hw_init(dev); + return 0; +} + +static int +rtl_get_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + val->value.i = priv->do_cpu; + return 0; +} + +static int +rtl_set_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + dev->cpu_port = val->value.i; + rtl_hw_init(dev); + return 0; +} + +static int +rtl_get_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + val->value.i = dev->cpu_port; + return 0; +} +#endif + +static int +rtl_reset(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + rtl_hw_init(dev); + return 0; +} + +static int +rtl_attr_set_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int idx = attr->id + (val->port_vlan * attr->ofs); + struct rtl_phyregs port; + + if (attr->id >= ARRAY_SIZE(rtl_regs)) + return -EINVAL; + + if ((attr->max > 0) && (val->value.i > attr->max)) + return -EINVAL; + + /* access to phy register 22 on port 4/5 + * needs phy status save/restore */ + if ((val->port_vlan > 3) && + (rtl_regs[idx].reg == 22) && + (rtl_regs[idx].page == 0)) { + + rtl_phy_save(dev, val->port_vlan, &port); + rtl_set(dev, idx, val->value.i); + rtl_phy_restore(dev, val->port_vlan, &port); + } else { + rtl_set(dev, idx, val->value.i); + } + + return 0; +} + +static int +rtl_attr_get_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int idx = attr->id + (val->port_vlan * attr->ofs); + + if (idx >= ARRAY_SIZE(rtl_regs)) + return -EINVAL; + + val->value.i = rtl_get(dev, idx); + return 0; +} + +static int +rtl_attr_set_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= RTL8306_NUM_PORTS) + return -EINVAL; + + return rtl_attr_set_int(dev, attr, val); +} + +static int +rtl_attr_get_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= RTL8306_NUM_PORTS) + return -EINVAL; + return rtl_attr_get_int(dev, attr, val); +} + +static int +rtl_attr_set_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + return rtl_attr_set_int(dev, attr, val); +} + +static int +rtl_attr_get_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + return rtl_attr_get_int(dev, attr, val); +} + +static int +rtl_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + unsigned int i, mask; + + mask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + struct switch_port *port; + + if (!(mask & (1 << i))) + continue; + + port = &val->value.ports[val->len]; + port->id = i; + port->flags = 0; + val->len++; + } + + return 0; +} + +static int +rtl_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct rtl_phyregs port; + int en = val->value.i; + int i; + + rtl_set(dev, RTL_REG_EN_TAG_OUT, en && priv->do_cpu); + rtl_set(dev, RTL_REG_EN_TAG_IN, en && priv->do_cpu); + rtl_set(dev, RTL_REG_EN_TAG_CLR, en && priv->do_cpu); + rtl_set(dev, RTL_REG_VLAN_TAG_AWARE, en); + if (en) + rtl_set(dev, RTL_REG_VLAN_FILTER, en); + + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + if (i > 3) + rtl_phy_save(dev, val->port_vlan, &port); + rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); + rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), (en ? (i == dev->cpu_port ? 0 : 1) : 1)); + rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), (en ? (i == dev->cpu_port ? 2 : 1) : 3)); + if (i > 3) + rtl_phy_restore(dev, val->port_vlan, &port); + } + rtl_set(dev, RTL_REG_VLAN_ENABLE, en); + + return 0; +} + +static int +rtl_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + return rtl_get(dev, RTL_REG_VLAN_ENABLE); +} + +static int +rtl_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + unsigned int mask = 0; + unsigned int oldmask; + int i; + + for(i = 0; i < val->len; i++) + { + struct switch_port *port = &val->value.ports[i]; + bool tagged = false; + + mask |= (1 << port->id); + + if (port->id == dev->cpu_port) + continue; + + if ((i == dev->cpu_port) || + (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED))) + tagged = true; + + /* fix up PVIDs for added ports */ + if (!tagged) + rtl_set(dev, RTL_PORT_REG(port->id, PVID), val->port_vlan); + + rtl_set(dev, RTL_PORT_REG(port->id, NON_PVID_DISCARD), (tagged ? 0 : 1)); + rtl_set(dev, RTL_PORT_REG(port->id, VID_INSERT), (tagged ? 0 : 1)); + rtl_set(dev, RTL_PORT_REG(port->id, TAG_INSERT), (tagged ? 2 : 1)); + } + + oldmask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); + rtl_set(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK), mask); + + /* fix up PVIDs for removed ports, default to last vlan */ + oldmask &= ~mask; + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + if (!(oldmask & (1 << i))) + continue; + + if (i == dev->cpu_port) + continue; + + if (rtl_get(dev, RTL_PORT_REG(i, PVID)) == val->port_vlan) + rtl_set(dev, RTL_PORT_REG(i, PVID), dev->vlans - 1); + } + + return 0; +} + +static struct switch_attr rtl_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "reset", + .description = "Reset the switch", + .set = rtl_reset, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .max = 1, + .set = rtl_set_vlan, + .get = rtl_get_vlan, + }, + { + RTL_GLOBAL_REGATTR(EN_TRUNK), + .name = "trunk", + .description = "Enable port trunking", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(TRUNK_PORTSEL), + .name = "trunk_sel", + .description = "Select ports for trunking (0: 0,1 - 1: 3,4)", + .max = 1, + }, +#ifdef DEBUG + { + RTL_GLOBAL_REGATTR(VLAN_FILTER), + .name = "vlan_filter", + .description = "Filter incoming packets for allowed VLANS", + .max = 1, + }, + { + .type = SWITCH_TYPE_INT, + .name = "cpuport", + .description = "CPU Port", + .set = rtl_set_cpuport, + .get = rtl_get_cpuport, + .max = RTL8306_NUM_PORTS, + }, + { + .type = SWITCH_TYPE_INT, + .name = "use_cpuport", + .description = "CPU Port handling flag", + .set = rtl_set_use_cpuport, + .get = rtl_get_use_cpuport, + .max = RTL8306_NUM_PORTS, + }, + { + RTL_GLOBAL_REGATTR(TRAP_CPU), + .name = "trap_cpu", + .description = "VLAN trap to CPU", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(VLAN_TAG_AWARE), + .name = "vlan_tag_aware", + .description = "Enable VLAN tag awareness", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(VLAN_TAG_ONLY), + .name = "tag_only", + .description = "Only accept tagged packets", + .max = 1, + }, +#endif +}; +static struct switch_attr rtl_port[] = { + { + RTL_PORT_REGATTR(PVID), + .name = "pvid", + .description = "Port VLAN ID", + .max = RTL8306_NUM_VLANS - 1, + }, + { + RTL_PORT_REGATTR(LINK), + .name = "link", + .description = "get the current link state", + .max = 1, + .set = NULL, + }, +#ifdef DEBUG + { + RTL_PORT_REGATTR(NULL_VID_REPLACE), + .name = "null_vid", + .description = "NULL VID gets replaced by port default vid", + .max = 1, + }, + { + RTL_PORT_REGATTR(NON_PVID_DISCARD), + .name = "non_pvid_discard", + .description = "discard packets with VID != PVID", + .max = 1, + }, + { + RTL_PORT_REGATTR(VID_INSERT), + .name = "vid_insert_remove", + .description = "how should the switch insert and remove vids ?", + .max = 3, + }, + { + RTL_PORT_REGATTR(TAG_INSERT), + .name = "tag_insert", + .description = "tag insertion handling", + .max = 3, + }, +#endif + { + RTL_PORT_REGATTR(SPEED), + .name = "speed", + .description = "current link speed", + .max = 1, + }, + { + RTL_PORT_REGATTR(NWAY), + .name = "nway", + .description = "enable autonegotiation", + .max = 1, + }, +}; + +static struct switch_attr rtl_vlan[] = { + { + RTL_VLAN_REGATTR(VID), + .name = "vid", + .description = "VLAN ID (1-4095)", + .max = 4095, + }, +}; + +static const struct switch_dev_ops rtl8306_ops = { + .attr_global = { + .attr = rtl_globals, + .n_attr = ARRAY_SIZE(rtl_globals), + }, + .attr_port = { + .attr = rtl_port, + .n_attr = ARRAY_SIZE(rtl_port), + }, + .attr_vlan = { + .attr = rtl_vlan, + .n_attr = ARRAY_SIZE(rtl_vlan), + }, + + .get_vlan_ports = rtl_get_ports, + .set_vlan_ports = rtl_set_ports, + .apply_config = rtl_hw_apply, +}; + +static int +rtl8306_config_init(struct phy_device *pdev) +{ + struct net_device *netdev = pdev->attached_dev; + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + struct switch_val val; + unsigned int chipid, chipver, chiptype; + int err; + + /* Only init the switch for the primary PHY */ + if (pdev->addr != 0) + return 0; + + val.value.i = 1; + priv->dev.cpu_port = RTL8306_PORT_CPU; + priv->dev.ports = RTL8306_NUM_PORTS; + priv->dev.vlans = RTL8306_NUM_VLANS; + priv->dev.ops = &rtl8306_ops; + priv->do_cpu = 0; + priv->page = -1; + priv->bus = pdev->bus; + + chipid = rtl_get(dev, RTL_REG_CHIPID); + chipver = rtl_get(dev, RTL_REG_CHIPVER); + chiptype = rtl_get(dev, RTL_REG_CHIPTYPE); + switch(chiptype) { + case 0: + case 2: + strncpy(priv->hwname, RTL_NAME_S, sizeof(priv->hwname)); + priv->type = RTL_TYPE_S; + break; + case 1: + strncpy(priv->hwname, RTL_NAME_SD, sizeof(priv->hwname)); + priv->type = RTL_TYPE_SD; + break; + case 3: + strncpy(priv->hwname, RTL_NAME_SDM, sizeof(priv->hwname)); + priv->type = RTL_TYPE_SDM; + break; + default: + strncpy(priv->hwname, RTL_NAME_UNKNOWN, sizeof(priv->hwname)); + break; + } + + dev->name = priv->hwname; + rtl_hw_init(dev); + + printk(KERN_INFO "Registering %s switch with Chip ID: 0x%04x, version: 0x%04x\n", priv->hwname, chipid, chipver); + + err = register_switch(dev, netdev); + if (err < 0) { + kfree(priv); + return err; + } + + return 0; +} + + +static int +rtl8306_fixup(struct phy_device *pdev) +{ + struct rtl_priv priv; + u16 chipid; + + /* Attach to primary LAN port and WAN port */ + if (pdev->addr != 0 && pdev->addr != 4) + return 0; + + memset(&priv, 0, sizeof(priv)); + priv.fixup = true; + priv.page = -1; + priv.bus = pdev->bus; + chipid = rtl_get(&priv.dev, RTL_REG_CHIPID); + if (chipid == 0x5988) + pdev->phy_id = RTL8306_MAGIC; + + return 0; +} + +static int +rtl8306_probe(struct phy_device *pdev) +{ + struct rtl_priv *priv; + + list_for_each_entry(priv, &phydevs, list) { + /* + * share one rtl_priv instance between virtual phy + * devices on the same bus + */ + if (priv->bus == pdev->bus) + goto found; + } + priv = kzalloc(sizeof(struct rtl_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->bus = pdev->bus; + +found: + pdev->priv = priv; + return 0; +} + +static void +rtl8306_remove(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + unregister_switch(&priv->dev); + kfree(priv); +} + +static int +rtl8306_config_aneg(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + + /* Only for WAN */ + if (pdev->addr == 0) + return 0; + + /* Restart autonegotiation */ + rtl_set(&priv->dev, RTL_PORT_REG(4, NWAY), 1); + rtl_set(&priv->dev, RTL_PORT_REG(4, NRESTART), 1); + + return 0; +} + +static int +rtl8306_read_status(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + + if (pdev->addr == 4) { + /* WAN */ + pdev->speed = rtl_get(dev, RTL_PORT_REG(4, SPEED)) ? SPEED_100 : SPEED_10; + pdev->duplex = rtl_get(dev, RTL_PORT_REG(4, DUPLEX)) ? DUPLEX_FULL : DUPLEX_HALF; + pdev->link = !!rtl_get(dev, RTL_PORT_REG(4, LINK)); + } else { + /* LAN */ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + } + + /* + * Bypass generic PHY status read, + * it doesn't work with this switch + */ + if (pdev->link) { + pdev->state = PHY_RUNNING; + netif_carrier_on(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } else { + pdev->state = PHY_NOLINK; + netif_carrier_off(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } + + return 0; +} + + +static struct phy_driver rtl8306_driver = { + .name = "Realtek RTL8306S", + .flags = PHY_HAS_MAGICANEG, + .phy_id = RTL8306_MAGIC, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = &rtl8306_probe, + .remove = &rtl8306_remove, + .config_init = &rtl8306_config_init, + .config_aneg = &rtl8306_config_aneg, + .read_status = &rtl8306_read_status, + .driver = { .owner = THIS_MODULE,}, +}; + + +static int __init +rtl_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, rtl8306_fixup); + return phy_driver_register(&rtl8306_driver); +} + +static void __exit +rtl_exit(void) +{ + phy_driver_unregister(&rtl8306_driver); +} + +module_init(rtl_init); +module_exit(rtl_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/rtl8366_smi.c b/drivers/net/phy/rtl8366_smi.c new file mode 100644 index 0000000..ca8f788 --- /dev/null +++ b/drivers/net/phy/rtl8366_smi.c @@ -0,0 +1,1350 @@ +/* + * Realtek RTL8366 SMI interface driver + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +#include +#endif + +#include "rtl8366_smi.h" + +#define RTL8366_SMI_ACK_RETRY_COUNT 5 + +static inline void rtl8366_smi_clk_delay(struct rtl8366_smi *smi) +{ + ndelay(smi->clk_delay); +} + +static void rtl8366_smi_start(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + /* + * Set GPIO pins to output mode, with initial state: + * SCK = 0, SDA = 1 + */ + gpio_direction_output(sck, 0); + gpio_direction_output(sda, 1); + rtl8366_smi_clk_delay(smi); + + /* CLK 1: 0 -> 1, 1 -> 0 */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + + /* CLK 2: */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); +} + +static void rtl8366_smi_stop(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* add a click */ + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* set GPIO pins to input mode */ + gpio_direction_input(sda); + gpio_direction_input(sck); +} + +static void rtl8366_smi_write_bits(struct rtl8366_smi *smi, u32 data, u32 len) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + for (; len > 0; len--) { + rtl8366_smi_clk_delay(smi); + + /* prepare data */ + gpio_set_value(sda, !!(data & ( 1 << (len - 1)))); + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + } +} + +static void rtl8366_smi_read_bits(struct rtl8366_smi *smi, u32 len, u32 *data) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + gpio_direction_input(sda); + + for (*data = 0; len > 0; len--) { + u32 u; + + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + u = !!gpio_get_value(sda); + gpio_set_value(sck, 0); + + *data |= (u << (len - 1)); + } + + gpio_direction_output(sda, 0); +} + +static int rtl8366_smi_wait_for_ack(struct rtl8366_smi *smi) +{ + int retry_cnt; + + retry_cnt = 0; + do { + u32 ack; + + rtl8366_smi_read_bits(smi, 1, &ack); + if (ack == 0) + break; + + if (++retry_cnt > RTL8366_SMI_ACK_RETRY_COUNT) { + dev_err(smi->parent, "ACK timeout\n"); + return -ETIMEDOUT; + } + } while (1); + + return 0; +} + +static int rtl8366_smi_write_byte(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return rtl8366_smi_wait_for_ack(smi); +} + +static int rtl8366_smi_write_byte_noack(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return 0; +} + +static int rtl8366_smi_read_byte0(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x00, 1); + + return 0; +} + +static int rtl8366_smi_read_byte1(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x01, 1); + + return 0; +} + +int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + unsigned long flags; + u8 lo = 0; + u8 hi = 0; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send READ command */ + ret = rtl8366_smi_write_byte(smi, smi->cmd_read); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* read DATA[7:0] */ + rtl8366_smi_read_byte0(smi, &lo); + /* read DATA[15:8] */ + rtl8366_smi_read_byte1(smi, &hi); + + *data = ((u32) lo) | (((u32) hi) << 8); + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_read_reg); + +static int __rtl8366_smi_write_reg(struct rtl8366_smi *smi, + u32 addr, u32 data, bool ack) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send WRITE command */ + ret = rtl8366_smi_write_byte(smi, smi->cmd_write); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* write DATA[7:0] */ + ret = rtl8366_smi_write_byte(smi, data & 0xff); + if (ret) + goto out; + + /* write DATA[15:8] */ + if (ack) + ret = rtl8366_smi_write_byte(smi, data >> 8); + else + ret = rtl8366_smi_write_byte_noack(smi, data >> 8); + if (ret) + goto out; + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} + +int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + return __rtl8366_smi_write_reg(smi, addr, data, true); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg); + +int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + return __rtl8366_smi_write_reg(smi, addr, data, false); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg_noack); + +int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data) +{ + u32 t; + int err; + + err = rtl8366_smi_read_reg(smi, addr, &t); + if (err) + return err; + + err = rtl8366_smi_write_reg(smi, addr, (t & ~mask) | data); + return err; + +} +EXPORT_SYMBOL_GPL(rtl8366_smi_rmwr); + +static int rtl8366_mc_is_used(struct rtl8366_smi *smi, int mc_index, int *used) +{ + int err; + int i; + + *used = 0; + for (i = 0; i < smi->num_ports; i++) { + int index = 0; + + err = smi->ops->get_mc_index(smi, i, &index); + if (err) + return err; + + if (mc_index == index) { + *used = 1; + break; + } + } + + return 0; +} + +static int rtl8366_set_vlan(struct rtl8366_smi *smi, int vid, u32 member, + u32 untag, u32 fid) +{ + struct rtl8366_vlan_4k vlan4k; + int err; + int i; + + /* Update the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlan4k.member = member; + vlan4k.untag = untag; + vlan4k.fid = fid; + err = smi->ops->set_vlan_4k(smi, &vlan4k); + if (err) + return err; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + struct rtl8366_vlan_mc vlanmc; + + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vid == vlanmc.vid) { + /* update the MC entry */ + vlanmc.member = member; + vlanmc.untag = untag; + vlanmc.fid = fid; + + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + break; + } + } + + return err; +} + +static int rtl8366_get_pvid(struct rtl8366_smi *smi, int port, int *val) +{ + struct rtl8366_vlan_mc vlanmc; + int err; + int index; + + err = smi->ops->get_mc_index(smi, port, &index); + if (err) + return err; + + err = smi->ops->get_vlan_mc(smi, index, &vlanmc); + if (err) + return err; + + *val = vlanmc.vid; + return 0; +} + +static int rtl8366_set_pvid(struct rtl8366_smi *smi, unsigned port, + unsigned vid) +{ + struct rtl8366_vlan_mc vlanmc; + struct rtl8366_vlan_4k vlan4k; + int err; + int i; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vid == vlanmc.vid) { + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + /* We have no MC entry for this VID, try to find an empty one */ + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vlanmc.vid == 0 && vlanmc.member == 0) { + /* Update the entry from the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + /* MC table is full, try to find an unused entry and replace it */ + for (i = 0; i < smi->num_vlan_mc; i++) { + int used; + + err = rtl8366_mc_is_used(smi, i, &used); + if (err) + return err; + + if (!used) { + /* Update the entry from the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + dev_err(smi->parent, + "all VLAN member configurations are in use\n"); + + return -ENOSPC; +} + +int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + int err; + + err = smi->ops->enable_vlan(smi, enable); + if (err) + return err; + + smi->vlan_enabled = enable; + + if (!enable) { + smi->vlan4k_enabled = 0; + err = smi->ops->enable_vlan4k(smi, enable); + } + + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_vlan); + +static int rtl8366_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + int err; + + if (enable) { + err = smi->ops->enable_vlan(smi, enable); + if (err) + return err; + + smi->vlan_enabled = enable; + } + + err = smi->ops->enable_vlan4k(smi, enable); + if (err) + return err; + + smi->vlan4k_enabled = enable; + return 0; +} + +int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable) +{ + int port; + int err; + + for (port = 0; port < smi->num_ports; port++) { + err = smi->ops->enable_port(smi, port, enable); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_all_ports); + +int rtl8366_reset_vlan(struct rtl8366_smi *smi) +{ + struct rtl8366_vlan_mc vlanmc; + int err; + int i; + + rtl8366_enable_vlan(smi, 0); + rtl8366_enable_vlan4k(smi, 0); + + /* clear VLAN member configurations */ + vlanmc.vid = 0; + vlanmc.priority = 0; + vlanmc.member = 0; + vlanmc.untag = 0; + vlanmc.fid = 0; + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_reset_vlan); + +static int rtl8366_init_vlan(struct rtl8366_smi *smi) +{ + int port; + int err; + + err = rtl8366_reset_vlan(smi); + if (err) + return err; + + for (port = 0; port < smi->num_ports; port++) { + u32 mask; + + if (port == smi->cpu_port) + mask = (1 << smi->num_ports) - 1; + else + mask = (1 << port) | (1 << smi->cpu_port); + + err = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0); + if (err) + return err; + + err = rtl8366_set_pvid(smi, port, (port + 1)); + if (err) + return err; + } + + return rtl8366_enable_vlan(smi, 1); +} + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +int rtl8366_debugfs_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_debugfs_open); + +static ssize_t rtl8366_read_debugfs_vlan_mc(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + int i, len = 0; + char *buf = smi->buf; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%2s %6s %4s %6s %6s %3s\n", + "id", "vid","prio", "member", "untag", "fid"); + + for (i = 0; i < smi->num_vlan_mc; ++i) { + struct rtl8366_vlan_mc vlanmc; + + smi->ops->get_vlan_mc(smi, i, &vlanmc); + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%2d %6d %4d 0x%04x 0x%04x %3d\n", + i, vlanmc.vid, vlanmc.priority, + vlanmc.member, vlanmc.untag, vlanmc.fid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +#define RTL8366_VLAN4K_PAGE_SIZE 64 +#define RTL8366_VLAN4K_NUM_PAGES (4096 / RTL8366_VLAN4K_PAGE_SIZE) + +static ssize_t rtl8366_read_debugfs_vlan_4k(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + int i, len = 0; + int offset; + char *buf = smi->buf; + + if (smi->dbg_vlan_4k_page >= RTL8366_VLAN4K_NUM_PAGES) { + len += snprintf(buf + len, sizeof(smi->buf) - len, + "invalid page: %u\n", smi->dbg_vlan_4k_page); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + } + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4s %6s %6s %3s\n", + "vid", "member", "untag", "fid"); + + offset = RTL8366_VLAN4K_PAGE_SIZE * smi->dbg_vlan_4k_page; + for (i = 0; i < RTL8366_VLAN4K_PAGE_SIZE; i++) { + struct rtl8366_vlan_4k vlan4k; + + smi->ops->get_vlan_4k(smi, offset + i, &vlan4k); + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d 0x%04x 0x%04x %3d\n", + vlan4k.vid, vlan4k.member, + vlan4k.untag, vlan4k.fid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_read_debugfs_pvid(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + char *buf = smi->buf; + int len = 0; + int i; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%4s %4s\n", + "port", "pvid"); + + for (i = 0; i < smi->num_ports; i++) { + int pvid; + int err; + + err = rtl8366_get_pvid(smi, i, &pvid); + if (err) + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d error\n", i); + else + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d %4d\n", i, pvid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_read_debugfs_reg(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + u32 t, reg = smi->dbg_reg; + int err, len = 0; + char *buf = smi->buf; + + memset(buf, '\0', sizeof(smi->buf)); + + err = rtl8366_smi_read_reg(smi, reg, &t); + if (err) { + len += snprintf(buf, sizeof(smi->buf), + "Read failed (reg: 0x%04x)\n", reg); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + } + + len += snprintf(buf, sizeof(smi->buf), "reg = 0x%04x, val = 0x%04x\n", + reg, t); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_write_debugfs_reg(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + unsigned long data; + u32 reg = smi->dbg_reg; + int err; + size_t len; + char *buf = smi->buf; + + len = min(count, sizeof(smi->buf) - 1); + if (copy_from_user(buf, user_buf, len)) { + dev_err(smi->parent, "copy from user failed\n"); + return -EFAULT; + } + + buf[len] = '\0'; + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + + if (strict_strtoul(buf, 16, &data)) { + dev_err(smi->parent, "Invalid reg value %s\n", buf); + } else { + err = rtl8366_smi_write_reg(smi, reg, data); + if (err) { + dev_err(smi->parent, + "writing reg 0x%04x val 0x%04lx failed\n", + reg, data); + } + } + + return count; +} + +static ssize_t rtl8366_read_debugfs_mibs(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = file->private_data; + int i, j, len = 0; + char *buf = smi->buf; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s", + "Counter"); + + for (i = 0; i < smi->num_ports; i++) { + char port_buf[10]; + + snprintf(port_buf, sizeof(port_buf), "Port %d", i); + len += snprintf(buf + len, sizeof(smi->buf) - len, " %12s", + port_buf); + } + len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); + + for (i = 0; i < smi->num_mib_counters; i++) { + len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s ", + smi->mib_counters[i].name); + for (j = 0; j < smi->num_ports; j++) { + unsigned long long counter = 0; + + if (!smi->ops->get_mib_counter(smi, i, j, &counter)) + len += snprintf(buf + len, + sizeof(smi->buf) - len, + "%12llu ", counter); + else + len += snprintf(buf + len, + sizeof(smi->buf) - len, + "%12s ", "error"); + } + len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_rtl8366_regs = { + .read = rtl8366_read_debugfs_reg, + .write = rtl8366_write_debugfs_reg, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_vlan_mc = { + .read = rtl8366_read_debugfs_vlan_mc, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_vlan_4k = { + .read = rtl8366_read_debugfs_vlan_4k, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_pvid = { + .read = rtl8366_read_debugfs_pvid, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_mibs = { + .read = rtl8366_read_debugfs_mibs, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static void rtl8366_debugfs_init(struct rtl8366_smi *smi) +{ + struct dentry *node; + struct dentry *root; + + if (!smi->debugfs_root) + smi->debugfs_root = debugfs_create_dir(dev_name(smi->parent), + NULL); + + if (!smi->debugfs_root) { + dev_err(smi->parent, "Unable to create debugfs dir\n"); + return; + } + root = smi->debugfs_root; + + node = debugfs_create_x16("reg", S_IRUGO | S_IWUSR, root, + &smi->dbg_reg); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "reg"); + return; + } + + node = debugfs_create_file("val", S_IRUGO | S_IWUSR, root, smi, + &fops_rtl8366_regs); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "val"); + return; + } + + node = debugfs_create_file("vlan_mc", S_IRUSR, root, smi, + &fops_rtl8366_vlan_mc); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_mc"); + return; + } + + node = debugfs_create_u8("vlan_4k_page", S_IRUGO | S_IWUSR, root, + &smi->dbg_vlan_4k_page); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_4k_page"); + return; + } + + node = debugfs_create_file("vlan_4k", S_IRUSR, root, smi, + &fops_rtl8366_vlan_4k); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_4k"); + return; + } + + node = debugfs_create_file("pvid", S_IRUSR, root, smi, + &fops_rtl8366_pvid); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "pvid"); + return; + } + + node = debugfs_create_file("mibs", S_IRUSR, smi->debugfs_root, smi, + &fops_rtl8366_mibs); + if (!node) + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "mibs"); +} + +static void rtl8366_debugfs_remove(struct rtl8366_smi *smi) +{ + if (smi->debugfs_root) { + debugfs_remove_recursive(smi->debugfs_root); + smi->debugfs_root = NULL; + } +} +#else +static inline void rtl8366_debugfs_init(struct rtl8366_smi *smi) {} +static inline void rtl8366_debugfs_remove(struct rtl8366_smi *smi) {} +#endif /* CONFIG_RTL8366_SMI_DEBUG_FS */ + +static int rtl8366_smi_mii_init(struct rtl8366_smi *smi) +{ + int ret; + int i; + + smi->mii_bus = mdiobus_alloc(); + if (smi->mii_bus == NULL) { + ret = -ENOMEM; + goto err; + } + + smi->mii_bus->priv = (void *) smi; + smi->mii_bus->name = dev_name(smi->parent); + smi->mii_bus->read = smi->ops->mii_read; + smi->mii_bus->write = smi->ops->mii_write; + snprintf(smi->mii_bus->id, MII_BUS_ID_SIZE, "%s", + dev_name(smi->parent)); + smi->mii_bus->parent = smi->parent; + smi->mii_bus->phy_mask = ~(0x1f); + smi->mii_bus->irq = smi->mii_irq; + for (i = 0; i < PHY_MAX_ADDR; i++) + smi->mii_irq[i] = PHY_POLL; + + ret = mdiobus_register(smi->mii_bus); + if (ret) + goto err_free; + + return 0; + + err_free: + mdiobus_free(smi->mii_bus); + err: + return ret; +} + +static void rtl8366_smi_mii_cleanup(struct rtl8366_smi *smi) +{ + mdiobus_unregister(smi->mii_bus); + mdiobus_free(smi->mii_bus); +} + +int rtl8366_sw_reset_switch(struct switch_dev *dev) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + err = smi->ops->reset_chip(smi); + if (err) + return err; + + err = smi->ops->setup(smi); + if (err) + return err; + + err = rtl8366_reset_vlan(smi); + if (err) + return err; + + err = rtl8366_enable_vlan(smi, 1); + if (err) + return err; + + return rtl8366_enable_all_ports(smi, 1); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_reset_switch); + +int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366_get_pvid(smi, port, val); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_pvid); + +int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366_set_pvid(smi, port, val); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_port_pvid); + +int rtl8366_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int i, len = 0; + unsigned long long counter = 0; + char *buf = smi->buf; + + if (val->port_vlan >= smi->num_ports) + return -EINVAL; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "Port %d MIB counters\n", + val->port_vlan); + + for (i = 0; i < smi->num_mib_counters; ++i) { + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%-36s: ", smi->mib_counters[i].name); + if (!smi->ops->get_mib_counter(smi, i, val->port_vlan, + &counter)) + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%llu\n", counter); + else + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%s\n", "error"); + } + + val->value.s = buf; + val->len = len; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_mib); + +int rtl8366_sw_get_vlan_info(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int i; + u32 len = 0; + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + char *buf = smi->buf; + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + memset(buf, '\0', sizeof(smi->buf)); + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "VLAN %d: Ports: '", vlan4k.vid); + + for (i = 0; i < smi->num_ports; i++) { + if (!(vlan4k.member & (1 << i))) + continue; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%d%s", i, + (vlan4k.untag & (1 << i)) ? "" : "t"); + } + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "', members=%04x, untag=%04x, fid=%u", + vlan4k.member, vlan4k.untag, vlan4k.fid); + + val->value.s = buf; + val->len = len; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_info); + +int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + struct switch_port *port; + struct rtl8366_vlan_4k vlan4k; + int i; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + + port = &val->value.ports[0]; + val->len = 0; + for (i = 0; i < smi->num_ports; i++) { + if (!(vlan4k.member & BIT(i))) + continue; + + port->id = i; + port->flags = (vlan4k.untag & BIT(i)) ? + 0 : BIT(SWITCH_PORT_FLAG_TAGGED); + val->len++; + port++; + } + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_ports); + +int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + struct switch_port *port; + u32 member = 0; + u32 untag = 0; + int err; + int i; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + port = &val->value.ports[0]; + for (i = 0; i < val->len; i++, port++) { + member |= BIT(port->id); + + if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) + untag |= BIT(port->id); + + /* + * To ensure that we have a valid MC entry for this VLAN, + * initialize the port VLAN ID here. + */ + err = rtl8366_set_pvid(smi, port->id, val->port_vlan); + if (err < 0) + return err; + } + + return rtl8366_set_vlan(smi, val->port_vlan, member, untag, 0); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_ports); + +int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + val->value.i = vlan4k.fid; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_fid); + +int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + if (val->value.i < 0 || val->value.i > attr->max) + return -EINVAL; + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + return rtl8366_set_vlan(smi, val->port_vlan, + vlan4k.member, + vlan4k.untag, + val->value.i); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_fid); + +int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (attr->ofs > 2) + return -EINVAL; + + if (attr->ofs == 1) + val->value.i = smi->vlan_enabled; + else + val->value.i = smi->vlan4k_enabled; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_enable); + +int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (attr->ofs > 2) + return -EINVAL; + + if (attr->ofs == 1) + err = rtl8366_enable_vlan(smi, val->value.i); + else + err = rtl8366_enable_vlan4k(smi, val->value.i); + + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_enable); + +struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent) +{ + struct rtl8366_smi *smi; + + BUG_ON(!parent); + + smi = kzalloc(sizeof(*smi), GFP_KERNEL); + if (!smi) { + dev_err(parent, "no memory for private data\n"); + return NULL; + } + + smi->parent = parent; + return smi; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_alloc); + +static int __rtl8366_smi_init(struct rtl8366_smi *smi, const char *name) +{ + int err; + + err = gpio_request(smi->gpio_sda, name); + if (err) { + printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", + smi->gpio_sda, err); + goto err_out; + } + + err = gpio_request(smi->gpio_sck, name); + if (err) { + printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", + smi->gpio_sck, err); + goto err_free_sda; + } + + spin_lock_init(&smi->lock); + return 0; + + err_free_sda: + gpio_free(smi->gpio_sda); + err_out: + return err; +} + +static void __rtl8366_smi_cleanup(struct rtl8366_smi *smi) +{ + gpio_free(smi->gpio_sck); + gpio_free(smi->gpio_sda); +} + +enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata) +{ + static struct rtl8366_smi smi; + enum rtl8366_type type = RTL8366_TYPE_UNKNOWN; + u32 reg = 0; + + memset(&smi, 0, sizeof(smi)); + smi.gpio_sda = pdata->gpio_sda; + smi.gpio_sck = pdata->gpio_sck; + smi.clk_delay = 10; + smi.cmd_read = 0xa9; + smi.cmd_write = 0xa8; + + if (__rtl8366_smi_init(&smi, "rtl8366")) + goto out; + + if (rtl8366_smi_read_reg(&smi, 0x5c, ®)) + goto cleanup; + + switch(reg) { + case 0x6027: + printk("Found an RTL8366S switch\n"); + type = RTL8366_TYPE_S; + break; + case 0x5937: + printk("Found an RTL8366RB switch\n"); + type = RTL8366_TYPE_RB; + break; + default: + printk("Found an Unknown RTL8366 switch (id=0x%04x)\n", reg); + break; + } + +cleanup: + __rtl8366_smi_cleanup(&smi); +out: + return type; +} + +int rtl8366_smi_init(struct rtl8366_smi *smi) +{ + int err; + + if (!smi->ops) + return -EINVAL; + + err = __rtl8366_smi_init(smi, dev_name(smi->parent)); + if (err) + goto err_out; + + dev_info(smi->parent, "using GPIO pins %u (SDA) and %u (SCK)\n", + smi->gpio_sda, smi->gpio_sck); + + err = smi->ops->detect(smi); + if (err) { + dev_err(smi->parent, "chip detection failed, err=%d\n", err); + goto err_free_sck; + } + + err = smi->ops->reset_chip(smi); + if (err) + goto err_free_sck; + + err = smi->ops->setup(smi); + if (err) { + dev_err(smi->parent, "chip setup failed, err=%d\n", err); + goto err_free_sck; + } + + err = rtl8366_init_vlan(smi); + if (err) { + dev_err(smi->parent, "VLAN initialization failed, err=%d\n", + err); + goto err_free_sck; + } + + err = rtl8366_enable_all_ports(smi, 1); + if (err) + goto err_free_sck; + + err = rtl8366_smi_mii_init(smi); + if (err) + goto err_free_sck; + + rtl8366_debugfs_init(smi); + + return 0; + + err_free_sck: + __rtl8366_smi_cleanup(smi); + err_out: + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_init); + +void rtl8366_smi_cleanup(struct rtl8366_smi *smi) +{ + rtl8366_debugfs_remove(smi); + rtl8366_smi_mii_cleanup(smi); + gpio_free(smi->gpio_sck); + gpio_free(smi->gpio_sda); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_cleanup); + +MODULE_DESCRIPTION("Realtek RTL8366 SMI interface driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/rtl8366_smi.h b/drivers/net/phy/rtl8366_smi.h new file mode 100644 index 0000000..ea5e6db --- /dev/null +++ b/drivers/net/phy/rtl8366_smi.h @@ -0,0 +1,148 @@ +/* + * Realtek RTL8366 SMI interface driver defines + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _RTL8366_SMI_H +#define _RTL8366_SMI_H + +#include +#include + +struct rtl8366_smi_ops; +struct rtl8366_vlan_ops; +struct mii_bus; +struct dentry; +struct inode; +struct file; + +struct rtl8366_mib_counter { + unsigned base; + unsigned offset; + unsigned length; + const char *name; +}; + +struct rtl8366_smi { + struct device *parent; + unsigned int gpio_sda; + unsigned int gpio_sck; + unsigned int clk_delay; /* ns */ + u8 cmd_read; + u8 cmd_write; + spinlock_t lock; + struct mii_bus *mii_bus; + int mii_irq[PHY_MAX_ADDR]; + struct switch_dev sw_dev; + + unsigned int cpu_port; + unsigned int num_ports; + unsigned int num_vlan_mc; + unsigned int num_mib_counters; + struct rtl8366_mib_counter *mib_counters; + + struct rtl8366_smi_ops *ops; + + int vlan_enabled; + int vlan4k_enabled; + + char buf[4096]; +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS + struct dentry *debugfs_root; + u16 dbg_reg; + u8 dbg_vlan_4k_page; +#endif +}; + +struct rtl8366_vlan_mc { + u16 vid; + u16 untag; + u16 member; + u8 fid; + u8 priority; +}; + +struct rtl8366_vlan_4k { + u16 vid; + u16 untag; + u16 member; + u8 fid; +}; + +struct rtl8366_smi_ops { + int (*detect)(struct rtl8366_smi *smi); + int (*reset_chip)(struct rtl8366_smi *smi); + int (*setup)(struct rtl8366_smi *smi); + + int (*mii_read)(struct mii_bus *bus, int addr, int reg); + int (*mii_write)(struct mii_bus *bus, int addr, int reg, u16 val); + + int (*get_vlan_mc)(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc); + int (*set_vlan_mc)(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc); + int (*get_vlan_4k)(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k); + int (*set_vlan_4k)(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k); + int (*get_mc_index)(struct rtl8366_smi *smi, int port, int *val); + int (*set_mc_index)(struct rtl8366_smi *smi, int port, int index); + int (*get_mib_counter)(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val); + int (*is_vlan_valid)(struct rtl8366_smi *smi, unsigned vlan); + int (*enable_vlan)(struct rtl8366_smi *smi, int enable); + int (*enable_vlan4k)(struct rtl8366_smi *smi, int enable); + int (*enable_port)(struct rtl8366_smi *smi, int port, int enable); +}; + +struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent); +int rtl8366_smi_init(struct rtl8366_smi *smi); +void rtl8366_smi_cleanup(struct rtl8366_smi *smi); +int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data); +int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data); +int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data); +int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data); + +int rtl8366_reset_vlan(struct rtl8366_smi *smi); +int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable); +int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable); + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +int rtl8366_debugfs_open(struct inode *inode, struct file *file); +#endif + +static inline struct rtl8366_smi *sw_to_rtl8366_smi(struct switch_dev *sw) +{ + return container_of(sw, struct rtl8366_smi, sw_dev); +} + +int rtl8366_sw_reset_switch(struct switch_dev *dev); +int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val); +int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val); +int rtl8366_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_info(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val); +int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val); +int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); + +#endif /* _RTL8366_SMI_H */ diff --git a/drivers/net/phy/rtl8366rb.c b/drivers/net/phy/rtl8366rb.c new file mode 100644 index 0000000..5d48b23 --- /dev/null +++ b/drivers/net/phy/rtl8366rb.c @@ -0,0 +1,1269 @@ +/* + * Platform driver for the Realtek RTL8366RB ethernet switch + * + * Copyright (C) 2009-2010 Gabor Juhos + * Copyright (C) 2010 Antti Seppälä + * Copyright (C) 2010 Roman Yeryomin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8366RB_DRIVER_DESC "Realtek RTL8366RB ethernet switch driver" +#define RTL8366RB_DRIVER_VER "0.2.3" + +#define RTL8366RB_PHY_NO_MAX 4 +#define RTL8366RB_PHY_PAGE_MAX 7 +#define RTL8366RB_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366RB_SGCR 0x0000 +#define RTL8366RB_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366RB_SGCR_MAX_LENGTH(_x) (_x << 4) +#define RTL8366RB_SGCR_MAX_LENGTH_MASK RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_MAX_LENGTH_1522 RTL8366RB_SGCR_MAX_LENGTH(0x0) +#define RTL8366RB_SGCR_MAX_LENGTH_1536 RTL8366RB_SGCR_MAX_LENGTH(0x1) +#define RTL8366RB_SGCR_MAX_LENGTH_1552 RTL8366RB_SGCR_MAX_LENGTH(0x2) +#define RTL8366RB_SGCR_MAX_LENGTH_9216 RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_EN_VLAN BIT(13) +#define RTL8366RB_SGCR_EN_VLAN_4KTB BIT(14) + +/* Port Enable Control register */ +#define RTL8366RB_PECR 0x0001 + +/* Switch Security Control registers */ +#define RTL8366RB_SSCR0 0x0002 +#define RTL8366RB_SSCR1 0x0003 +#define RTL8366RB_SSCR2 0x0004 +#define RTL8366RB_SSCR2_DROP_UNKNOWN_DA BIT(0) + +#define RTL8366RB_RESET_CTRL_REG 0x0100 +#define RTL8366RB_CHIP_CTRL_RESET_HW 1 +#define RTL8366RB_CHIP_CTRL_RESET_SW (1 << 1) + +#define RTL8366RB_CHIP_VERSION_CTRL_REG 0x050A +#define RTL8366RB_CHIP_VERSION_MASK 0xf +#define RTL8366RB_CHIP_ID_REG 0x0509 +#define RTL8366RB_CHIP_ID_8366 0x5937 + +/* PHY registers control */ +#define RTL8366RB_PHY_ACCESS_CTRL_REG 0x8000 +#define RTL8366RB_PHY_ACCESS_DATA_REG 0x8002 + +#define RTL8366RB_PHY_CTRL_READ 1 +#define RTL8366RB_PHY_CTRL_WRITE 0 + +#define RTL8366RB_PHY_REG_MASK 0x1f +#define RTL8366RB_PHY_PAGE_OFFSET 5 +#define RTL8366RB_PHY_PAGE_MASK (0xf << 5) +#define RTL8366RB_PHY_NO_OFFSET 9 +#define RTL8366RB_PHY_NO_MASK (0x1f << 9) + +#define RTL8366RB_VLAN_INGRESS_CTRL2_REG 0x037f + +/* LED control registers */ +#define RTL8366RB_LED_BLINKRATE_REG 0x0430 +#define RTL8366RB_LED_BLINKRATE_BIT 0 +#define RTL8366RB_LED_BLINKRATE_MASK 0x0007 + +#define RTL8366RB_LED_CTRL_REG 0x0431 +#define RTL8366RB_LED_0_1_CTRL_REG 0x0432 +#define RTL8366RB_LED_2_3_CTRL_REG 0x0433 + +#define RTL8366RB_MIB_COUNT 33 +#define RTL8366RB_GLOBAL_MIB_COUNT 1 +#define RTL8366RB_MIB_COUNTER_PORT_OFFSET 0x0050 +#define RTL8366RB_MIB_COUNTER_BASE 0x1000 +#define RTL8366RB_MIB_CTRL_REG 0x13F0 +#define RTL8366RB_MIB_CTRL_USER_MASK 0x0FFC +#define RTL8366RB_MIB_CTRL_BUSY_MASK BIT(0) +#define RTL8366RB_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8366RB_MIB_CTRL_PORT_RESET(_p) BIT(2 + (_p)) +#define RTL8366RB_MIB_CTRL_GLOBAL_RESET BIT(11) + +#define RTL8366RB_PORT_VLAN_CTRL_BASE 0x0063 +#define RTL8366RB_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366RB_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366RB_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366RB_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + + +#define RTL8366RB_VLAN_TABLE_READ_BASE 0x018C +#define RTL8366RB_VLAN_TABLE_WRITE_BASE 0x0185 + + +#define RTL8366RB_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366RB_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366RB_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366RB_VLAN_MC_BASE(_x) (0x0020 + (_x) * 3) + + +#define RTL8366RB_PORT_LINK_STATUS_BASE 0x0014 +#define RTL8366RB_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366RB_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366RB_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366RB_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366RB_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366RB_PORT_STATUS_AN_MASK 0x0080 + + +#define RTL8366RB_PORT_NUM_CPU 5 +#define RTL8366RB_NUM_PORTS 6 +#define RTL8366RB_NUM_VLANS 16 +#define RTL8366RB_NUM_LEDGROUPS 4 +#define RTL8366RB_NUM_VIDS 4096 +#define RTL8366RB_PRIORITYMAX 7 +#define RTL8366RB_FIDMAX 7 + + +#define RTL8366RB_PORT_1 (1 << 0) /* In userspace port 0 */ +#define RTL8366RB_PORT_2 (1 << 1) /* In userspace port 1 */ +#define RTL8366RB_PORT_3 (1 << 2) /* In userspace port 2 */ +#define RTL8366RB_PORT_4 (1 << 3) /* In userspace port 3 */ +#define RTL8366RB_PORT_5 (1 << 4) /* In userspace port 4 */ + +#define RTL8366RB_PORT_CPU (1 << 5) /* CPU port */ + +#define RTL8366RB_PORT_ALL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5 | \ + RTL8366RB_PORT_CPU) + +#define RTL8366RB_PORT_ALL_BUT_CPU (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5) + +#define RTL8366RB_PORT_ALL_EXTERNAL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4) + +#define RTL8366RB_PORT_ALL_INTERNAL RTL8366RB_PORT_CPU + +#define RTL8366RB_VLAN_VID_MASK 0xfff +#define RTL8366RB_VLAN_PRIORITY_SHIFT 12 +#define RTL8366RB_VLAN_PRIORITY_MASK 0x7 +#define RTL8366RB_VLAN_UNTAG_SHIFT 8 +#define RTL8366RB_VLAN_UNTAG_MASK 0xff +#define RTL8366RB_VLAN_MEMBER_MASK 0xff +#define RTL8366RB_VLAN_FID_MASK 0x7 + + +/* Port ingress bandwidth control */ +#define RTL8366RB_IB_BASE 0x0200 +#define RTL8366RB_IB_REG(pnum) (RTL8366RB_IB_BASE + pnum) +#define RTL8366RB_IB_BDTH_MASK 0x3fff +#define RTL8366RB_IB_PREIFG_OFFSET 14 +#define RTL8366RB_IB_PREIFG_MASK (1 << RTL8366RB_IB_PREIFG_OFFSET) + +/* Port egress bandwidth control */ +#define RTL8366RB_EB_BASE 0x02d1 +#define RTL8366RB_EB_REG(pnum) (RTL8366RB_EB_BASE + pnum) +#define RTL8366RB_EB_BDTH_MASK 0x3fff +#define RTL8366RB_EB_PREIFG_REG 0x02f8 +#define RTL8366RB_EB_PREIFG_OFFSET 9 +#define RTL8366RB_EB_PREIFG_MASK (1 << RTL8366RB_EB_PREIFG_OFFSET) + +#define RTL8366RB_BDTH_SW_MAX 1048512 +#define RTL8366RB_BDTH_UNIT 64 +#define RTL8366RB_BDTH_REG_DEFAULT 16383 + +/* QOS */ +#define RTL8366RB_QOS_BIT 15 +#define RTL8366RB_QOS_MASK (1 << RTL8366RB_QOS_BIT) +/* Include/Exclude Preamble and IFG (20 bytes). 0:Exclude, 1:Include. */ +#define RTL8366RB_QOS_DEFAULT_PREIFG 1 + + +static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, +}; + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static int rtl8366rb_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + u32 data; + + rtl8366_smi_write_reg_noack(smi, RTL8366RB_RESET_CTRL_REG, + RTL8366RB_CHIP_CTRL_RESET_HW); + do { + msleep(1); + if (rtl8366_smi_read_reg(smi, RTL8366RB_RESET_CTRL_REG, &data)) + return -EIO; + + if (!(data & RTL8366RB_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + printk("Timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366rb_setup(struct rtl8366_smi *smi) +{ + int err; + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_MAX_LENGTH_MASK, + RTL8366RB_SGCR_MAX_LENGTH_1536); + + /* enable learning for all ports */ + REG_WR(smi, RTL8366RB_SSCR0, 0); + + /* enable auto ageing for all ports */ + REG_WR(smi, RTL8366RB_SSCR1, 0); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8366RB_VLAN_INGRESS_CTRL2_REG, RTL8366RB_PORT_ALL); + + /* don't drop packets whose DA has not been learned */ + REG_RMW(smi, RTL8366RB_SSCR2, RTL8366RB_SSCR2_DROP_UNKNOWN_DA, 0); + + return 0; +} + +static int rtl8366rb_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366RB_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366RB_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | + ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | + (addr & RTL8366RB_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366RB_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366RB_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | + ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | + (addr & RTL8366RB_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8366RB_NUM_PORTS || counter >= RTL8366RB_MIB_COUNT) + return -EINVAL; + + addr = RTL8366RB_MIB_COUNTER_BASE + + RTL8366RB_MIB_COUNTER_PORT_OFFSET * (port) + + rtl8366rb_mib_counters[counter].offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + data = 0; /* writing data will be discard by ASIC */ + err = rtl8366_smi_write_reg(smi, addr, data); + if (err) + return err; + + /* read MIB control register */ + err = rtl8366_smi_read_reg(smi, RTL8366RB_MIB_CTRL_REG, &data); + if (err) + return err; + + if (data & RTL8366RB_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8366RB_MIB_CTRL_RESET_MASK) + return -EIO; + + mibvalue = 0; + for (i = rtl8366rb_mib_counters[counter].length; i > 0; i--) { + err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); + if (err) + return err; + + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8366rb_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366RB_NUM_VIDS) + return -EINVAL; + + /* write VID */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_VLAN_TABLE_WRITE_BASE, + vid & RTL8366RB_VLAN_VID_MASK); + if (err) + return err; + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_READ_CTRL); + if (err) + return err; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366RB_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (err) + return err; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlan4k->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int err; + int i; + + if (vlan4k->vid >= RTL8366RB_NUM_VIDS || + vlan4k->member > RTL8366RB_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366RB_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlan4k->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366RB_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (err) + return err; + } + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_WRITE_CTRL); + + return err; +} + +static int rtl8366rb_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366RB_VLAN_MC_BASE(index) + i, + &data[i]); + if (err) + return err; + } + + vlanmc->vid = data[0] & RTL8366RB_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366RB_VLAN_PRIORITY_SHIFT) & + RTL8366RB_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlanmc->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int err; + int i; + + if (index >= RTL8366RB_NUM_VLANS || + vlanmc->vid >= RTL8366RB_NUM_VIDS || + vlanmc->priority > RTL8366RB_PRIORITYMAX || + vlanmc->member > RTL8366RB_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366RB_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366RB_VLAN_PRIORITY_MASK) << + RTL8366RB_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlanmc->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366RB_VLAN_MC_BASE(index) + i, + data[i]); + if (err) + return err; + } + + return 0; +} + +static int rtl8366rb_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + err = rtl8366_smi_read_reg(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), + &data); + if (err) + return err; + + *val = (data >> RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366RB_PORT_VLAN_CTRL_MASK; + + return 0; + +} + +static int rtl8366rb_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8366RB_NUM_PORTS || index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), + RTL8366RB_PORT_VLAN_CTRL_MASK << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366RB_PORT_VLAN_CTRL_MASK) << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)); +} + +static int rtl8366rb_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8366RB_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366RB_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8366rb_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_EN_VLAN, + (enable) ? RTL8366RB_SGCR_EN_VLAN : 0); +} + +static int rtl8366rb_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, + RTL8366RB_SGCR_EN_VLAN_4KTB, + (enable) ? RTL8366RB_SGCR_EN_VLAN_4KTB : 0); +} + +static int rtl8366rb_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, (1 << port), + (enable) ? 0 : (1 << port)); +} + +static int rtl8366rb_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, + RTL8366RB_MIB_CTRL_GLOBAL_RESET); +} + +static int rtl8366rb_sw_get_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_LED_BLINKRATE_REG, &data); + + val->value.i = (data & (RTL8366RB_LED_BLINKRATE_MASK)); + + return 0; +} + +static int rtl8366rb_sw_set_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->value.i >= 6) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_LED_BLINKRATE_REG, + RTL8366RB_LED_BLINKRATE_MASK, + val->value.i); +} + +static int rtl8366rb_sw_get_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_SSCR0, &data); + val->value.i = !data; + + return 0; +} + + +static int rtl8366rb_sw_set_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 portmask = 0; + int err = 0; + + if (!val->value.i) + portmask = RTL8366RB_PORT_ALL; + + /* set learning for all ports */ + REG_WR(smi, RTL8366RB_SSCR0, portmask); + + /* set auto ageing for all ports */ + REG_WR(smi, RTL8366RB_SSCR1, portmask); + + return 0; +} + +static int rtl8366rb_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_PORT_LINK_STATUS_BASE + (port / 2), + &data); + + if (port % 2) + data = data >> 8; + + link->link = !!(data & RTL8366RB_PORT_STATUS_LINK_MASK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8366RB_PORT_STATUS_DUPLEX_MASK); + link->rx_flow = !!(data & RTL8366RB_PORT_STATUS_RXPAUSE_MASK); + link->tx_flow = !!(data & RTL8366RB_PORT_STATUS_TXPAUSE_MASK); + link->aneg = !!(data & RTL8366RB_PORT_STATUS_AN_MASK); + + speed = (data & RTL8366RB_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8366rb_sw_set_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + u32 mask; + u32 reg; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + if (val->port_vlan == RTL8366RB_PORT_NUM_CPU) { + reg = RTL8366RB_LED_BLINKRATE_REG; + mask = 0xF << 4; + data = val->value.i << 4; + } else { + reg = RTL8366RB_LED_CTRL_REG; + mask = 0xF << (val->port_vlan * 4), + data = val->value.i << (val->port_vlan * 4); + } + + return rtl8366_smi_rmwr(smi, reg, mask, data); +} + +static int rtl8366rb_sw_get_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + + if (val->port_vlan >= RTL8366RB_NUM_LEDGROUPS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_LED_CTRL_REG, &data); + val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; + + return 0; +} + +static int rtl8366rb_sw_set_port_disable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 mask, data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + mask = 1 << val->port_vlan ; + if (val->value.i) + data = mask; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, mask, data); +} + +static int rtl8366rb_sw_get_port_disable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_PECR, &data); + if (data & (1 << val->port_vlan)) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_port_rate_in(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) + val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; + else + val->value.i = RTL8366RB_BDTH_REG_DEFAULT; + + return rtl8366_smi_rmwr(smi, RTL8366RB_IB_REG(val->port_vlan), + RTL8366RB_IB_BDTH_MASK | RTL8366RB_IB_PREIFG_MASK, + val->value.i | + (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_IB_PREIFG_OFFSET)); + +} + +static int rtl8366rb_sw_get_port_rate_in(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_IB_REG(val->port_vlan), &data); + data &= RTL8366RB_IB_BDTH_MASK; + if (data < RTL8366RB_IB_BDTH_MASK) + data += 1; + + val->value.i = (int)data * RTL8366RB_BDTH_UNIT; + + return 0; +} + +static int rtl8366rb_sw_set_port_rate_out(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_rmwr(smi, RTL8366RB_EB_PREIFG_REG, + RTL8366RB_EB_PREIFG_MASK, + (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_EB_PREIFG_OFFSET)); + + if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) + val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; + else + val->value.i = RTL8366RB_BDTH_REG_DEFAULT; + + return rtl8366_smi_rmwr(smi, RTL8366RB_EB_REG(val->port_vlan), + RTL8366RB_EB_BDTH_MASK, val->value.i ); + +} + +static int rtl8366rb_sw_get_port_rate_out(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_EB_REG(val->port_vlan), &data); + data &= RTL8366RB_EB_BDTH_MASK; + if (data < RTL8366RB_EB_BDTH_MASK) + data += 1; + + val->value.i = (int)data * RTL8366RB_BDTH_UNIT; + + return 0; +} + +static int rtl8366rb_sw_set_qos_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_QOS_MASK; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_QOS_MASK, data); +} + +static int rtl8366rb_sw_get_qos_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_SGCR, &data); + if (data & RTL8366RB_QOS_MASK) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, + RTL8366RB_MIB_CTRL_PORT_RESET(val->port_vlan)); +} + +static struct switch_attr rtl8366rb_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_learning", + .description = "Enable learning, enable aging", + .set = rtl8366rb_sw_set_learning_enable, + .get = rtl8366rb_sw_get_learning_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8366rb_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "blinkrate", + .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," + " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", + .set = rtl8366rb_sw_set_blinkrate, + .get = rtl8366rb_sw_get_blinkrate, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_qos", + .description = "Enable QOS", + .set = rtl8366rb_sw_set_qos_enable, + .get = rtl8366rb_sw_get_qos_enable, + .max = 1 + }, +}; + +static struct switch_attr rtl8366rb_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8366rb_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, { + .type = SWITCH_TYPE_INT, + .name = "led", + .description = "Get/Set port group (0 - 3) led mode (0 - 15)", + .max = 15, + .set = rtl8366rb_sw_set_port_led, + .get = rtl8366rb_sw_get_port_led, + }, { + .type = SWITCH_TYPE_INT, + .name = "disable", + .description = "Get/Set port state (enabled or disabled)", + .max = 1, + .set = rtl8366rb_sw_set_port_disable, + .get = rtl8366rb_sw_get_port_disable, + }, { + .type = SWITCH_TYPE_INT, + .name = "rate_in", + .description = "Get/Set port ingress (incoming) bandwidth limit in kbps", + .max = RTL8366RB_BDTH_SW_MAX, + .set = rtl8366rb_sw_set_port_rate_in, + .get = rtl8366rb_sw_get_port_rate_in, + }, { + .type = SWITCH_TYPE_INT, + .name = "rate_out", + .description = "Get/Set port egress (outgoing) bandwidth limit in kbps", + .max = RTL8366RB_BDTH_SW_MAX, + .set = rtl8366rb_sw_set_port_rate_out, + .get = rtl8366rb_sw_get_port_rate_out, + }, +}; + +static struct switch_attr rtl8366rb_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8366RB_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8366_ops = { + .attr_global = { + .attr = rtl8366rb_globals, + .n_attr = ARRAY_SIZE(rtl8366rb_globals), + }, + .attr_port = { + .attr = rtl8366rb_port, + .n_attr = ARRAY_SIZE(rtl8366rb_port), + }, + .attr_vlan = { + .attr = rtl8366rb_vlan, + .n_attr = ARRAY_SIZE(rtl8366rb_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8366rb_sw_get_port_link, +}; + +static int rtl8366rb_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8366RB"; + dev->cpu_port = RTL8366RB_PORT_NUM_CPU; + dev->ports = RTL8366RB_NUM_PORTS; + dev->vlans = RTL8366RB_NUM_VIDS; + dev->ops = &rtl8366_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8366rb_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8366rb_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366rb_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366rb_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366rb_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366rb_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366rb_detect(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->parent, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366RB_CHIP_ID_8366: + break; + default: + dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366RB_CHIP_VERSION_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8366rb_smi_ops = { + .detect = rtl8366rb_detect, + .reset_chip = rtl8366rb_reset_chip, + .setup = rtl8366rb_setup, + + .mii_read = rtl8366rb_mii_read, + .mii_write = rtl8366rb_mii_write, + + .get_vlan_mc = rtl8366rb_get_vlan_mc, + .set_vlan_mc = rtl8366rb_set_vlan_mc, + .get_vlan_4k = rtl8366rb_get_vlan_4k, + .set_vlan_4k = rtl8366rb_set_vlan_4k, + .get_mc_index = rtl8366rb_get_mc_index, + .set_mc_index = rtl8366rb_set_mc_index, + .get_mib_counter = rtl8366rb_get_mib_counter, + .is_vlan_valid = rtl8366rb_is_vlan_valid, + .enable_vlan = rtl8366rb_enable_vlan, + .enable_vlan4k = rtl8366rb_enable_vlan4k, + .enable_port = rtl8366rb_enable_port, +}; + +static int __devinit rtl8366rb_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_platform_data *pdata; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366RB_DRIVER_DESC + " version " RTL8366RB_DRIVER_VER"\n"); + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data specified\n"); + err = -EINVAL; + goto err_out; + } + + smi = rtl8366_smi_alloc(&pdev->dev); + if (!smi) { + err = -ENOMEM; + goto err_out; + } + + smi->gpio_sda = pdata->gpio_sda; + smi->gpio_sck = pdata->gpio_sck; + smi->clk_delay = 10; + smi->cmd_read = 0xa9; + smi->cmd_write = 0xa8; + smi->ops = &rtl8366rb_smi_ops; + smi->cpu_port = RTL8366RB_PORT_NUM_CPU; + smi->num_ports = RTL8366RB_NUM_PORTS; + smi->num_vlan_mc = RTL8366RB_NUM_VLANS; + smi->mib_counters = rtl8366rb_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366rb_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8366rb_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + err_out: + return err; +} + +static int __devexit rtl8366rb_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8366rb_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static struct platform_driver rtl8366rb_driver = { + .driver = { + .name = RTL8366RB_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = rtl8366rb_probe, + .remove = __devexit_p(rtl8366rb_remove), +}; + +static int __init rtl8366rb_module_init(void) +{ + return platform_driver_register(&rtl8366rb_driver); +} +module_init(rtl8366rb_module_init); + +static void __exit rtl8366rb_module_exit(void) +{ + platform_driver_unregister(&rtl8366rb_driver); +} +module_exit(rtl8366rb_module_exit); + +MODULE_DESCRIPTION(RTL8366RB_DRIVER_DESC); +MODULE_VERSION(RTL8366RB_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Antti Seppälä "); +MODULE_AUTHOR("Roman Yeryomin "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME); diff --git a/drivers/net/phy/rtl8366s.c b/drivers/net/phy/rtl8366s.c new file mode 100644 index 0000000..21f7439 --- /dev/null +++ b/drivers/net/phy/rtl8366s.c @@ -0,0 +1,1148 @@ +/* + * Platform driver for the Realtek RTL8366S ethernet switch + * + * Copyright (C) 2009-2010 Gabor Juhos + * Copyright (C) 2010 Antti Seppälä + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8366S_DRIVER_DESC "Realtek RTL8366S ethernet switch driver" +#define RTL8366S_DRIVER_VER "0.2.2" + +#define RTL8366S_PHY_NO_MAX 4 +#define RTL8366S_PHY_PAGE_MAX 7 +#define RTL8366S_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366S_SGCR 0x0000 +#define RTL8366S_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366S_SGCR_MAX_LENGTH(_x) (_x << 4) +#define RTL8366S_SGCR_MAX_LENGTH_MASK RTL8366S_SGCR_MAX_LENGTH(0x3) +#define RTL8366S_SGCR_MAX_LENGTH_1522 RTL8366S_SGCR_MAX_LENGTH(0x0) +#define RTL8366S_SGCR_MAX_LENGTH_1536 RTL8366S_SGCR_MAX_LENGTH(0x1) +#define RTL8366S_SGCR_MAX_LENGTH_1552 RTL8366S_SGCR_MAX_LENGTH(0x2) +#define RTL8366S_SGCR_MAX_LENGTH_16000 RTL8366S_SGCR_MAX_LENGTH(0x3) +#define RTL8366S_SGCR_EN_VLAN BIT(13) + +/* Port Enable Control register */ +#define RTL8366S_PECR 0x0001 + +/* Switch Security Control registers */ +#define RTL8366S_SSCR0 0x0002 +#define RTL8366S_SSCR1 0x0003 +#define RTL8366S_SSCR2 0x0004 +#define RTL8366S_SSCR2_DROP_UNKNOWN_DA BIT(0) + +#define RTL8366S_RESET_CTRL_REG 0x0100 +#define RTL8366S_CHIP_CTRL_RESET_HW 1 +#define RTL8366S_CHIP_CTRL_RESET_SW (1 << 1) + +#define RTL8366S_CHIP_VERSION_CTRL_REG 0x0104 +#define RTL8366S_CHIP_VERSION_MASK 0xf +#define RTL8366S_CHIP_ID_REG 0x0105 +#define RTL8366S_CHIP_ID_8366 0x8366 + +/* PHY registers control */ +#define RTL8366S_PHY_ACCESS_CTRL_REG 0x8028 +#define RTL8366S_PHY_ACCESS_DATA_REG 0x8029 + +#define RTL8366S_PHY_CTRL_READ 1 +#define RTL8366S_PHY_CTRL_WRITE 0 + +#define RTL8366S_PHY_REG_MASK 0x1f +#define RTL8366S_PHY_PAGE_OFFSET 5 +#define RTL8366S_PHY_PAGE_MASK (0x7 << 5) +#define RTL8366S_PHY_NO_OFFSET 9 +#define RTL8366S_PHY_NO_MASK (0x1f << 9) + +/* LED control registers */ +#define RTL8366S_LED_BLINKRATE_REG 0x0420 +#define RTL8366S_LED_BLINKRATE_BIT 0 +#define RTL8366S_LED_BLINKRATE_MASK 0x0007 + +#define RTL8366S_LED_CTRL_REG 0x0421 +#define RTL8366S_LED_0_1_CTRL_REG 0x0422 +#define RTL8366S_LED_2_3_CTRL_REG 0x0423 + +#define RTL8366S_MIB_COUNT 33 +#define RTL8366S_GLOBAL_MIB_COUNT 1 +#define RTL8366S_MIB_COUNTER_PORT_OFFSET 0x0040 +#define RTL8366S_MIB_COUNTER_BASE 0x1000 +#define RTL8366S_MIB_COUNTER_PORT_OFFSET2 0x0008 +#define RTL8366S_MIB_COUNTER_BASE2 0x1180 +#define RTL8366S_MIB_CTRL_REG 0x11F0 +#define RTL8366S_MIB_CTRL_USER_MASK 0x01FF +#define RTL8366S_MIB_CTRL_BUSY_MASK 0x0001 +#define RTL8366S_MIB_CTRL_RESET_MASK 0x0002 + +#define RTL8366S_MIB_CTRL_GLOBAL_RESET_MASK 0x0004 +#define RTL8366S_MIB_CTRL_PORT_RESET_BIT 0x0003 +#define RTL8366S_MIB_CTRL_PORT_RESET_MASK 0x01FC + + +#define RTL8366S_PORT_VLAN_CTRL_BASE 0x0058 +#define RTL8366S_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366S_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366S_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366S_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + + +#define RTL8366S_VLAN_TABLE_READ_BASE 0x018B +#define RTL8366S_VLAN_TABLE_WRITE_BASE 0x0185 + +#define RTL8366S_VLAN_TB_CTRL_REG 0x010F + +#define RTL8366S_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366S_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366S_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366S_VLAN_MC_BASE(_x) (0x0016 + (_x) * 2) + +#define RTL8366S_VLAN_MEMBERINGRESS_REG 0x0379 + +#define RTL8366S_PORT_LINK_STATUS_BASE 0x0060 +#define RTL8366S_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366S_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366S_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366S_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366S_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366S_PORT_STATUS_AN_MASK 0x0080 + + +#define RTL8366S_PORT_NUM_CPU 5 +#define RTL8366S_NUM_PORTS 6 +#define RTL8366S_NUM_VLANS 16 +#define RTL8366S_NUM_LEDGROUPS 4 +#define RTL8366S_NUM_VIDS 4096 +#define RTL8366S_PRIORITYMAX 7 +#define RTL8366S_FIDMAX 7 + + +#define RTL8366S_PORT_1 (1 << 0) /* In userspace port 0 */ +#define RTL8366S_PORT_2 (1 << 1) /* In userspace port 1 */ +#define RTL8366S_PORT_3 (1 << 2) /* In userspace port 2 */ +#define RTL8366S_PORT_4 (1 << 3) /* In userspace port 3 */ + +#define RTL8366S_PORT_UNKNOWN (1 << 4) /* No known connection */ +#define RTL8366S_PORT_CPU (1 << 5) /* CPU port */ + +#define RTL8366S_PORT_ALL (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4 | \ + RTL8366S_PORT_UNKNOWN | \ + RTL8366S_PORT_CPU) + +#define RTL8366S_PORT_ALL_BUT_CPU (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4 | \ + RTL8366S_PORT_UNKNOWN) + +#define RTL8366S_PORT_ALL_EXTERNAL (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4) + +#define RTL8366S_PORT_ALL_INTERNAL (RTL8366S_PORT_UNKNOWN | \ + RTL8366S_PORT_CPU) + +#define RTL8366S_VLAN_VID_MASK 0xfff +#define RTL8366S_VLAN_PRIORITY_SHIFT 12 +#define RTL8366S_VLAN_PRIORITY_MASK 0x7 +#define RTL8366S_VLAN_MEMBER_MASK 0x3f +#define RTL8366S_VLAN_UNTAG_SHIFT 6 +#define RTL8366S_VLAN_UNTAG_MASK 0x3f +#define RTL8366S_VLAN_FID_SHIFT 12 +#define RTL8366S_VLAN_FID_MASK 0x7 + +static struct rtl8366_mib_counter rtl8366s_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + + /* + * The following counters are accessible at a different + * base address. + */ + { 1, 0, 2, "Dot1dTpPortInDiscards" }, + { 1, 2, 2, "IfOutUcastPkts" }, + { 1, 4, 2, "IfOutMulticastPkts" }, + { 1, 6, 2, "IfOutBroadcastPkts" }, +}; + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static int rtl8366s_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + u32 data; + + rtl8366_smi_write_reg_noack(smi, RTL8366S_RESET_CTRL_REG, + RTL8366S_CHIP_CTRL_RESET_HW); + do { + msleep(1); + if (rtl8366_smi_read_reg(smi, RTL8366S_RESET_CTRL_REG, &data)) + return -EIO; + + if (!(data & RTL8366S_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + printk("Timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366s_setup(struct rtl8366_smi *smi) +{ + struct rtl8366_platform_data *pdata; + int err; + + pdata = smi->parent->platform_data; + if (pdata->num_initvals && pdata->initvals) { + unsigned i; + + dev_info(smi->parent, "applying initvals\n"); + for (i = 0; i < pdata->num_initvals; i++) + REG_WR(smi, pdata->initvals[i].reg, + pdata->initvals[i].val); + } + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8366S_SGCR, RTL8366S_SGCR_MAX_LENGTH_MASK, + RTL8366S_SGCR_MAX_LENGTH_1536); + + /* enable learning for all ports */ + REG_WR(smi, RTL8366S_SSCR0, 0); + + /* enable auto ageing for all ports */ + REG_WR(smi, RTL8366S_SSCR1, 0); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8366S_VLAN_MEMBERINGRESS_REG, RTL8366S_PORT_ALL); + + /* don't drop packets whose DA has not been learned */ + REG_RMW(smi, RTL8366S_SSCR2, RTL8366S_SSCR2_DROP_UNKNOWN_DA, 0); + + return 0; +} + +static int rtl8366s_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366s_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8366S_NUM_PORTS || counter >= RTL8366S_MIB_COUNT) + return -EINVAL; + + switch (rtl8366s_mib_counters[counter].base) { + case 0: + addr = RTL8366S_MIB_COUNTER_BASE + + RTL8366S_MIB_COUNTER_PORT_OFFSET * port; + break; + + case 1: + addr = RTL8366S_MIB_COUNTER_BASE2 + + RTL8366S_MIB_COUNTER_PORT_OFFSET2 * port; + break; + + default: + return -EINVAL; + } + + addr += rtl8366s_mib_counters[counter].offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + data = 0; /* writing data will be discard by ASIC */ + err = rtl8366_smi_write_reg(smi, addr, data); + if (err) + return err; + + /* read MIB control register */ + err = rtl8366_smi_read_reg(smi, RTL8366S_MIB_CTRL_REG, &data); + if (err) + return err; + + if (data & RTL8366S_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8366S_MIB_CTRL_RESET_MASK) + return -EIO; + + mibvalue = 0; + for (i = rtl8366s_mib_counters[counter].length; i > 0; i--) { + err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); + if (err) + return err; + + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8366s_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[2]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366S_NUM_VIDS) + return -EINVAL; + + /* write VID */ + err = rtl8366_smi_write_reg(smi, RTL8366S_VLAN_TABLE_WRITE_BASE, + vid & RTL8366S_VLAN_VID_MASK); + if (err) + return err; + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, + RTL8366S_TABLE_VLAN_READ_CTRL); + if (err) + return err; + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366S_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (err) + return err; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & + RTL8366S_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; + vlan4k->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & + RTL8366S_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366s_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[2]; + int err; + int i; + + if (vlan4k->vid >= RTL8366S_NUM_VIDS || + vlan4k->member > RTL8366S_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366S_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366S_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366S_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366S_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366S_VLAN_UNTAG_MASK) << + RTL8366S_VLAN_UNTAG_SHIFT) | + ((vlan4k->fid & RTL8366S_VLAN_FID_MASK) << + RTL8366S_VLAN_FID_SHIFT); + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366S_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (err) + return err; + } + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, + RTL8366S_TABLE_VLAN_WRITE_CTRL); + + return err; +} + +static int rtl8366s_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[2]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366S_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366S_VLAN_MC_BASE(index) + i, + &data[i]); + if (err) + return err; + } + + vlanmc->vid = data[0] & RTL8366S_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366S_VLAN_PRIORITY_SHIFT) & + RTL8366S_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & + RTL8366S_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & + RTL8366S_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366s_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[2]; + int err; + int i; + + if (index >= RTL8366S_NUM_VLANS || + vlanmc->vid >= RTL8366S_NUM_VIDS || + vlanmc->priority > RTL8366S_PRIORITYMAX || + vlanmc->member > RTL8366S_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366S_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366S_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366S_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366S_VLAN_PRIORITY_MASK) << + RTL8366S_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366S_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366S_VLAN_UNTAG_MASK) << + RTL8366S_VLAN_UNTAG_SHIFT) | + ((vlanmc->fid & RTL8366S_VLAN_FID_MASK) << + RTL8366S_VLAN_FID_SHIFT); + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366S_VLAN_MC_BASE(index) + i, + data[i]); + if (err) + return err; + } + + return 0; +} + +static int rtl8366s_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366_smi_read_reg(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), + &data); + if (err) + return err; + + *val = (data >> RTL8366S_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366S_PORT_VLAN_CTRL_MASK; + + return 0; +} + +static int rtl8366s_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8366S_NUM_PORTS || index >= RTL8366S_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), + RTL8366S_PORT_VLAN_CTRL_MASK << + RTL8366S_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366S_PORT_VLAN_CTRL_MASK) << + RTL8366S_PORT_VLAN_CTRL_SHIFT(port)); +} + +static int rtl8366s_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, RTL8366S_SGCR_EN_VLAN, + (enable) ? RTL8366S_SGCR_EN_VLAN : 0); +} + +static int rtl8366s_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_VLAN_TB_CTRL_REG, + 1, (enable) ? 1 : 0); +} + +static int rtl8366s_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8366S_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366S_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8366s_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_PECR, (1 << port), + (enable) ? 0 : (1 << port)); +} + +static int rtl8366s_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, 0, (1 << 2)); +} + +static int rtl8366s_sw_get_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366S_LED_BLINKRATE_REG, &data); + + val->value.i = (data & (RTL8366S_LED_BLINKRATE_MASK)); + + return 0; +} + +static int rtl8366s_sw_set_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->value.i >= 6) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366S_LED_BLINKRATE_REG, + RTL8366S_LED_BLINKRATE_MASK, + val->value.i); +} + +static int rtl8366s_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366S_SGCR, &data); + + val->value.i = ((data & (RTL8366S_SGCR_MAX_LENGTH_MASK)) >> 4); + + return 0; +} + +static int rtl8366s_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + char length_code; + + switch (val->value.i) { + case 0: + length_code = RTL8366S_SGCR_MAX_LENGTH_1522; + break; + case 1: + length_code = RTL8366S_SGCR_MAX_LENGTH_1536; + break; + case 2: + length_code = RTL8366S_SGCR_MAX_LENGTH_1552; + break; + case 3: + length_code = RTL8366S_SGCR_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, + RTL8366S_SGCR_MAX_LENGTH_MASK, + length_code); +} + +static int rtl8366s_sw_get_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi,RTL8366S_SSCR0, &data); + val->value.i = !data; + + return 0; +} + + +static int rtl8366s_sw_set_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 portmask = 0; + int err = 0; + + if (!val->value.i) + portmask = RTL8366S_PORT_ALL; + + /* set learning for all ports */ + REG_WR(smi, RTL8366S_SSCR0, portmask); + + /* set auto ageing for all ports */ + REG_WR(smi, RTL8366S_SSCR1, portmask); + + return 0; +} + +static int rtl8366s_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366S_PORT_LINK_STATUS_BASE + (port / 2), + &data); + + if (port % 2) + data = data >> 8; + + link->link = !!(data & RTL8366S_PORT_STATUS_LINK_MASK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8366S_PORT_STATUS_DUPLEX_MASK); + link->rx_flow = !!(data & RTL8366S_PORT_STATUS_RXPAUSE_MASK); + link->tx_flow = !!(data & RTL8366S_PORT_STATUS_TXPAUSE_MASK); + link->aneg = !!(data & RTL8366S_PORT_STATUS_AN_MASK); + + speed = (data & RTL8366S_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8366s_sw_set_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + u32 mask; + u32 reg; + + if (val->port_vlan >= RTL8366S_NUM_PORTS || + (1 << val->port_vlan) == RTL8366S_PORT_UNKNOWN) + return -EINVAL; + + if (val->port_vlan == RTL8366S_PORT_NUM_CPU) { + reg = RTL8366S_LED_BLINKRATE_REG; + mask = 0xF << 4; + data = val->value.i << 4; + } else { + reg = RTL8366S_LED_CTRL_REG; + mask = 0xF << (val->port_vlan * 4), + data = val->value.i << (val->port_vlan * 4); + } + + return rtl8366_smi_rmwr(smi, reg, mask, data); +} + +static int rtl8366s_sw_get_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + + if (val->port_vlan >= RTL8366S_NUM_LEDGROUPS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366S_LED_CTRL_REG, &data); + val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; + + return 0; +} + +static int rtl8366s_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366S_NUM_PORTS) + return -EINVAL; + + + return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, + 0, (1 << (val->port_vlan + 3))); +} + +static struct switch_attr rtl8366s_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_learning", + .description = "Enable learning, enable aging", + .set = rtl8366s_sw_set_learning_enable, + .get = rtl8366s_sw_get_learning_enable, + .max = 1, + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8366s_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "blinkrate", + .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," + " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", + .set = rtl8366s_sw_set_blinkrate, + .get = rtl8366s_sw_get_blinkrate, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + " (0 = 1522, 1 = 1536, 2 = 1552, 3 = 16000 (9216?))", + .set = rtl8366s_sw_set_max_length, + .get = rtl8366s_sw_get_max_length, + .max = 3, + }, +}; + +static struct switch_attr rtl8366s_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8366s_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, { + .type = SWITCH_TYPE_INT, + .name = "led", + .description = "Get/Set port group (0 - 3) led mode (0 - 15)", + .max = 15, + .set = rtl8366s_sw_set_port_led, + .get = rtl8366s_sw_get_port_led, + }, +}; + +static struct switch_attr rtl8366s_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8366S_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8366_ops = { + .attr_global = { + .attr = rtl8366s_globals, + .n_attr = ARRAY_SIZE(rtl8366s_globals), + }, + .attr_port = { + .attr = rtl8366s_port, + .n_attr = ARRAY_SIZE(rtl8366s_port), + }, + .attr_vlan = { + .attr = rtl8366s_vlan, + .n_attr = ARRAY_SIZE(rtl8366s_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8366s_sw_get_port_link, +}; + +static int rtl8366s_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8366S"; + dev->cpu_port = RTL8366S_PORT_NUM_CPU; + dev->ports = RTL8366S_NUM_PORTS; + dev->vlans = RTL8366S_NUM_VIDS; + dev->ops = &rtl8366_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8366s_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8366s_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366s_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366s_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366s_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366s_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366s_detect(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->parent, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366S_CHIP_ID_8366: + break; + default: + dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366S_CHIP_VERSION_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8366s_smi_ops = { + .detect = rtl8366s_detect, + .reset_chip = rtl8366s_reset_chip, + .setup = rtl8366s_setup, + + .mii_read = rtl8366s_mii_read, + .mii_write = rtl8366s_mii_write, + + .get_vlan_mc = rtl8366s_get_vlan_mc, + .set_vlan_mc = rtl8366s_set_vlan_mc, + .get_vlan_4k = rtl8366s_get_vlan_4k, + .set_vlan_4k = rtl8366s_set_vlan_4k, + .get_mc_index = rtl8366s_get_mc_index, + .set_mc_index = rtl8366s_set_mc_index, + .get_mib_counter = rtl8366_get_mib_counter, + .is_vlan_valid = rtl8366s_is_vlan_valid, + .enable_vlan = rtl8366s_enable_vlan, + .enable_vlan4k = rtl8366s_enable_vlan4k, + .enable_port = rtl8366s_enable_port, +}; + +static int __devinit rtl8366s_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_platform_data *pdata; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366S_DRIVER_DESC + " version " RTL8366S_DRIVER_VER"\n"); + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data specified\n"); + err = -EINVAL; + goto err_out; + } + + smi = rtl8366_smi_alloc(&pdev->dev); + if (!smi) { + err = -ENOMEM; + goto err_out; + } + + smi->gpio_sda = pdata->gpio_sda; + smi->gpio_sck = pdata->gpio_sck; + smi->clk_delay = 10; + smi->cmd_read = 0xa9; + smi->cmd_write = 0xa8; + smi->ops = &rtl8366s_smi_ops; + smi->cpu_port = RTL8366S_PORT_NUM_CPU; + smi->num_ports = RTL8366S_NUM_PORTS; + smi->num_vlan_mc = RTL8366S_NUM_VLANS; + smi->mib_counters = rtl8366s_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366s_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8366s_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + err_out: + return err; +} + +static int __devexit rtl8366s_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8366s_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static struct platform_driver rtl8366s_driver = { + .driver = { + .name = RTL8366S_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = rtl8366s_probe, + .remove = __devexit_p(rtl8366s_remove), +}; + +static int __init rtl8366s_module_init(void) +{ + return platform_driver_register(&rtl8366s_driver); +} +module_init(rtl8366s_module_init); + +static void __exit rtl8366s_module_exit(void) +{ + platform_driver_unregister(&rtl8366s_driver); +} +module_exit(rtl8366s_module_exit); + +MODULE_DESCRIPTION(RTL8366S_DRIVER_DESC); +MODULE_VERSION(RTL8366S_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Antti Seppälä "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME); diff --git a/drivers/net/phy/rtl8367.c b/drivers/net/phy/rtl8367.c new file mode 100644 index 0000000..4f2ba63 --- /dev/null +++ b/drivers/net/phy/rtl8367.c @@ -0,0 +1,1773 @@ +/* + * Platform driver for the Realtek RTL8367R/M ethernet switches + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8367_RESET_DELAY 1000 /* msecs*/ + +#define RTL8367_PHY_ADDR_MAX 8 +#define RTL8367_PHY_REG_MAX 31 + +#define RTL8367_VID_MASK 0xffff +#define RTL8367_FID_MASK 0xfff +#define RTL8367_UNTAG_MASK 0xffff +#define RTL8367_MEMBER_MASK 0xffff + +#define RTL8367_PORT_CFG_REG(_p) (0x000e + 0x20 * (_p)) +#define RTL8367_PORT_CFG_EGRESS_MODE_SHIFT 4 +#define RTL8367_PORT_CFG_EGRESS_MODE_MASK 0x3 +#define RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL 0 +#define RTL8367_PORT_CFG_EGRESS_MODE_KEEP 1 +#define RTL8367_PORT_CFG_EGRESS_MODE_PRI 2 +#define RTL8367_PORT_CFG_EGRESS_MODE_REAL 3 + +#define RTL8367_BYPASS_LINE_RATE_REG 0x03f7 + +#define RTL8367_TA_CTRL_REG 0x0500 +#define RTL8367_TA_CTRL_STATUS BIT(12) +#define RTL8367_TA_CTRL_METHOD BIT(5) +#define RTL8367_TA_CTRL_CMD_SHIFT 4 +#define RTL8367_TA_CTRL_CMD_READ 0 +#define RTL8367_TA_CTRL_CMD_WRITE 1 +#define RTL8367_TA_CTRL_TABLE_SHIFT 0 +#define RTL8367_TA_CTRL_TABLE_ACLRULE 1 +#define RTL8367_TA_CTRL_TABLE_ACLACT 2 +#define RTL8367_TA_CTRL_TABLE_CVLAN 3 +#define RTL8367_TA_CTRL_TABLE_L2 4 +#define RTL8367_TA_CTRL_CVLAN_READ \ + ((RTL8367_TA_CTRL_CMD_READ << RTL8367_TA_CTRL_CMD_SHIFT) | \ + RTL8367_TA_CTRL_TABLE_CVLAN) +#define RTL8367_TA_CTRL_CVLAN_WRITE \ + ((RTL8367_TA_CTRL_CMD_WRITE << RTL8367_TA_CTRL_CMD_SHIFT) | \ + RTL8367_TA_CTRL_TABLE_CVLAN) + +#define RTL8367_TA_ADDR_REG 0x0501 +#define RTL8367_TA_ADDR_MASK 0x3fff + +#define RTL8367_TA_DATA_REG(_x) (0x0503 + (_x)) +#define RTL8367_TA_VLAN_DATA_SIZE 4 +#define RTL8367_TA_VLAN_VID_MASK RTL8367_VID_MASK +#define RTL8367_TA_VLAN_MEMBER_SHIFT 0 +#define RTL8367_TA_VLAN_MEMBER_MASK RTL8367_MEMBER_MASK +#define RTL8367_TA_VLAN_FID_SHIFT 0 +#define RTL8367_TA_VLAN_FID_MASK RTL8367_FID_MASK +#define RTL8367_TA_VLAN_UNTAG1_SHIFT 14 +#define RTL8367_TA_VLAN_UNTAG1_MASK 0x3 +#define RTL8367_TA_VLAN_UNTAG2_SHIFT 0 +#define RTL8367_TA_VLAN_UNTAG2_MASK 0x3fff + +#define RTL8367_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) +#define RTL8367_VLAN_PVID_CTRL_MASK 0x1f +#define RTL8367_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) + +#define RTL8367_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) +#define RTL8367_VLAN_MC_DATA_SIZE 4 +#define RTL8367_VLAN_MC_MEMBER_SHIFT 0 +#define RTL8367_VLAN_MC_MEMBER_MASK RTL8367_MEMBER_MASK +#define RTL8367_VLAN_MC_FID_SHIFT 0 +#define RTL8367_VLAN_MC_FID_MASK RTL8367_FID_MASK +#define RTL8367_VLAN_MC_EVID_SHIFT 0 +#define RTL8367_VLAN_MC_EVID_MASK RTL8367_VID_MASK + +#define RTL8367_VLAN_CTRL_REG 0x07a8 +#define RTL8367_VLAN_CTRL_ENABLE BIT(0) + +#define RTL8367_VLAN_INGRESS_REG 0x07a9 + +#define RTL8367_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) + +#define RTL8367_MIB_COUNTER_REG(_x) (0x1000 + (_x)) + +#define RTL8367_MIB_ADDRESS_REG 0x1004 + +#define RTL8367_MIB_CTRL_REG(_x) (0x1005 + (_x)) +#define RTL8367_MIB_CTRL_GLOBAL_RESET_MASK BIT(11) +#define RTL8367_MIB_CTRL_QM_RESET_MASK BIT(10) +#define RTL8367_MIB_CTRL_PORT_RESET_MASK(_p) BIT(2 + (_p)) +#define RTL8367_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8367_MIB_CTRL_BUSY_MASK BIT(0) + +#define RTL8367_MIB_COUNT 36 +#define RTL8367_MIB_COUNTER_PORT_OFFSET 0x0050 + +#define RTL8367_SWC0_REG 0x1200 +#define RTL8367_SWC0_MAX_LENGTH_SHIFT 13 +#define RTL8367_SWC0_MAX_LENGTH(_x) ((_x) << 13) +#define RTL8367_SWC0_MAX_LENGTH_MASK RTL8367_SWC0_MAX_LENGTH(0x3) +#define RTL8367_SWC0_MAX_LENGTH_1522 RTL8367_SWC0_MAX_LENGTH(0) +#define RTL8367_SWC0_MAX_LENGTH_1536 RTL8367_SWC0_MAX_LENGTH(1) +#define RTL8367_SWC0_MAX_LENGTH_1552 RTL8367_SWC0_MAX_LENGTH(2) +#define RTL8367_SWC0_MAX_LENGTH_16000 RTL8367_SWC0_MAX_LENGTH(3) + +#define RTL8367_CHIP_NUMBER_REG 0x1300 + +#define RTL8367_CHIP_VER_REG 0x1301 +#define RTL8367_CHIP_VER_RLVID_SHIFT 12 +#define RTL8367_CHIP_VER_RLVID_MASK 0xf +#define RTL8367_CHIP_VER_MCID_SHIFT 8 +#define RTL8367_CHIP_VER_MCID_MASK 0xf +#define RTL8367_CHIP_VER_BOID_SHIFT 4 +#define RTL8367_CHIP_VER_BOID_MASK 0xf + +#define RTL8367_CHIP_MODE_REG 0x1302 +#define RTL8367_CHIP_MODE_MASK 0x7 + +#define RTL8367_CHIP_DEBUG0_REG 0x1303 +#define RTL8367_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) + +#define RTL8367_CHIP_DEBUG1_REG 0x1304 + +#define RTL8367_DIS_REG 0x1305 +#define RTL8367_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) +#define RTL8367_DIS_RGMII_SHIFT(_x) (4 * (_x)) +#define RTL8367_DIS_RGMII_MASK 0x7 + +#define RTL8367_EXT_RGMXF_REG(_x) (0x1306 + (_x)) +#define RTL8367_EXT_RGMXF_DUMMY0_SHIFT 5 +#define RTL8367_EXT_RGMXF_DUMMY0_MASK 0x7ff +#define RTL8367_EXT_RGMXF_TXDELAY_SHIFT 3 +#define RTL8367_EXT_RGMXF_TXDELAY_MASK 1 +#define RTL8367_EXT_RGMXF_RXDELAY_MASK 0x7 + +#define RTL8367_DI_FORCE_REG(_x) (0x1310 + (_x)) +#define RTL8367_DI_FORCE_MODE BIT(12) +#define RTL8367_DI_FORCE_NWAY BIT(7) +#define RTL8367_DI_FORCE_TXPAUSE BIT(6) +#define RTL8367_DI_FORCE_RXPAUSE BIT(5) +#define RTL8367_DI_FORCE_LINK BIT(4) +#define RTL8367_DI_FORCE_DUPLEX BIT(2) +#define RTL8367_DI_FORCE_SPEED_MASK 3 +#define RTL8367_DI_FORCE_SPEED_10 0 +#define RTL8367_DI_FORCE_SPEED_100 1 +#define RTL8367_DI_FORCE_SPEED_1000 2 + +#define RTL8367_MAC_FORCE_REG(_x) (0x1312 + (_x)) + +#define RTL8367_CHIP_RESET_REG 0x1322 +#define RTL8367_CHIP_RESET_SW BIT(1) +#define RTL8367_CHIP_RESET_HW BIT(0) + +#define RTL8367_PORT_STATUS_REG(_p) (0x1352 + (_p)) +#define RTL8367_PORT_STATUS_NWAY BIT(7) +#define RTL8367_PORT_STATUS_TXPAUSE BIT(6) +#define RTL8367_PORT_STATUS_RXPAUSE BIT(5) +#define RTL8367_PORT_STATUS_LINK BIT(4) +#define RTL8367_PORT_STATUS_DUPLEX BIT(2) +#define RTL8367_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8367_PORT_STATUS_SPEED_10 0 +#define RTL8367_PORT_STATUS_SPEED_100 1 +#define RTL8367_PORT_STATUS_SPEED_1000 2 + +#define RTL8367_RTL_NO_REG 0x13c0 +#define RTL8367_RTL_NO_8367R 0x3670 +#define RTL8367_RTL_NO_8367M 0x3671 + +#define RTL8367_RTL_VER_REG 0x13c1 +#define RTL8367_RTL_VER_MASK 0xf + +#define RTL8367_RTL_MAGIC_ID_REG 0x13c2 +#define RTL8367_RTL_MAGIC_ID_VAL 0x0249 + +#define RTL8367_LED_SYS_CONFIG_REG 0x1b00 +#define RTL8367_LED_MODE_REG 0x1b02 +#define RTL8367_LED_MODE_RATE_M 0x7 +#define RTL8367_LED_MODE_RATE_S 1 + +#define RTL8367_LED_CONFIG_REG 0x1b03 +#define RTL8367_LED_CONFIG_DATA_S 12 +#define RTL8367_LED_CONFIG_DATA_M 0x3 +#define RTL8367_LED_CONFIG_SEL BIT(14) +#define RTL8367_LED_CONFIG_LED_CFG_M 0xf + +#define RTL8367_PARA_LED_IO_EN1_REG 0x1b24 +#define RTL8367_PARA_LED_IO_EN2_REG 0x1b25 +#define RTL8367_PARA_LED_IO_EN_PMASK 0xff + +#define RTL8367_IA_CTRL_REG 0x1f00 +#define RTL8367_IA_CTRL_RW(_x) ((_x) << 1) +#define RTL8367_IA_CTRL_RW_READ RTL8367_IA_CTRL_RW(0) +#define RTL8367_IA_CTRL_RW_WRITE RTL8367_IA_CTRL_RW(1) +#define RTL8367_IA_CTRL_CMD_MASK BIT(0) + +#define RTL8367_IA_STATUS_REG 0x1f01 +#define RTL8367_IA_STATUS_PHY_BUSY BIT(2) +#define RTL8367_IA_STATUS_SDS_BUSY BIT(1) +#define RTL8367_IA_STATUS_MDX_BUSY BIT(0) + +#define RTL8367_IA_ADDRESS_REG 0x1f02 + +#define RTL8367_IA_WRITE_DATA_REG 0x1f03 +#define RTL8367_IA_READ_DATA_REG 0x1f04 + +#define RTL8367_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) + +#define RTL8367_CPU_PORT_NUM 9 +#define RTL8367_NUM_PORTS 10 +#define RTL8367_NUM_VLANS 32 +#define RTL8367_NUM_LEDGROUPS 4 +#define RTL8367_NUM_VIDS 4096 +#define RTL8367_PRIORITYMAX 7 +#define RTL8367_FIDMAX 7 + +#define RTL8367_PORT_0 BIT(0) +#define RTL8367_PORT_1 BIT(1) +#define RTL8367_PORT_2 BIT(2) +#define RTL8367_PORT_3 BIT(3) +#define RTL8367_PORT_4 BIT(4) +#define RTL8367_PORT_5 BIT(5) +#define RTL8367_PORT_6 BIT(6) +#define RTL8367_PORT_7 BIT(7) +#define RTL8367_PORT_E1 BIT(8) /* external port 1 */ +#define RTL8367_PORT_E0 BIT(9) /* external port 0 */ + +#define RTL8367_PORTS_ALL \ + (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ + RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ + RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1 | \ + RTL8367_PORT_E0) + +#define RTL8367_PORTS_ALL_BUT_CPU \ + (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ + RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ + RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1) + +struct rtl8367_initval { + u16 reg; + u16 val; +}; + +static struct rtl8366_mib_counter rtl8367_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 2, "Dot3StatsFCSErrors" }, + { 0, 6, 2, "Dot3StatsSymbolErrors" }, + { 0, 8, 2, "Dot3InPauseFrames" }, + { 0, 10, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 12, 2, "EtherStatsFragments" }, + { 0, 14, 2, "EtherStatsJabbers" }, + { 0, 16, 2, "IfInUcastPkts" }, + { 0, 18, 2, "EtherStatsDropEvents" }, + { 0, 20, 4, "EtherStatsOctets" }, + + { 0, 24, 2, "EtherStatsUnderSizePkts" }, + { 0, 26, 2, "EtherOversizeStats" }, + { 0, 28, 2, "EtherStatsPkts64Octets" }, + { 0, 30, 2, "EtherStatsPkts65to127Octets" }, + { 0, 32, 2, "EtherStatsPkts128to255Octets" }, + { 0, 34, 2, "EtherStatsPkts256to511Octets" }, + { 0, 36, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 38, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 40, 2, "EtherStatsMulticastPkts" }, + { 0, 42, 2, "EtherStatsBroadcastPkts" }, + + { 0, 44, 4, "IfOutOctets" }, + + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, + { 0, 72, 2, "OutOampduPkts" }, + { 0, 74, 2, "InOampduPkts" }, + { 0, 76, 2, "PktgenPkts" }, +}; + +#define REG_RD(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_read_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static const struct rtl8367_initval rtl8367_initvals_0_0[] = { + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, + {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, + {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, + {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, + {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, + {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, + {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, + {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, + {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, + {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, + {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, + {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, + {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, + {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, + {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, + {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, + {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, + {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, + {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, + {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, + {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, + {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1006}, {0x121e, 0x03e8}, + {0x121f, 0x02b3}, {0x1220, 0x028f}, {0x1221, 0x029b}, {0x1222, 0x0277}, + {0x1223, 0x02b3}, {0x1224, 0x028f}, {0x1225, 0x029b}, {0x1226, 0x0277}, + {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, {0x1230, 0x00b4}, + {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, + {0x0219, 0x0032}, {0x0200, 0x03e8}, {0x0201, 0x03e8}, {0x0202, 0x03e8}, + {0x0203, 0x03e8}, {0x0204, 0x03e8}, {0x0205, 0x03e8}, {0x0206, 0x03e8}, + {0x0207, 0x03e8}, {0x0218, 0x0032}, {0x0208, 0x029b}, {0x0209, 0x029b}, + {0x020a, 0x029b}, {0x020b, 0x029b}, {0x020c, 0x029b}, {0x020d, 0x029b}, + {0x020e, 0x029b}, {0x020f, 0x029b}, {0x0210, 0x029b}, {0x0211, 0x029b}, + {0x0212, 0x029b}, {0x0213, 0x029b}, {0x0214, 0x029b}, {0x0215, 0x029b}, + {0x0216, 0x029b}, {0x0217, 0x029b}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, + {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x1700, 0x014C}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, + {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, + {0x133f, 0x0010}, {0x20A0, 0x1940}, {0x20C0, 0x1940}, {0x20E0, 0x1940}, +}; + +static const struct rtl8367_initval rtl8367_initvals_0_1[] = { + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, + {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, + {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, + {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, + {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, + {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, + {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, + {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, + {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, + {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, + {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, + {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, + {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, + {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, + {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, + {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, + {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, + {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, + {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, + {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, + {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, + {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1b06}, {0x121e, 0x07f0}, + {0x121f, 0x0438}, {0x1220, 0x040f}, {0x1221, 0x040f}, {0x1222, 0x03eb}, + {0x1223, 0x0438}, {0x1224, 0x040f}, {0x1225, 0x040f}, {0x1226, 0x03eb}, + {0x1227, 0x0144}, {0x1228, 0x0138}, {0x122f, 0x0144}, {0x1230, 0x0138}, + {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, + {0x0219, 0x0032}, {0x0200, 0x07d0}, {0x0201, 0x07d0}, {0x0202, 0x07d0}, + {0x0203, 0x07d0}, {0x0204, 0x07d0}, {0x0205, 0x07d0}, {0x0206, 0x07d0}, + {0x0207, 0x07d0}, {0x0218, 0x0032}, {0x0208, 0x0190}, {0x0209, 0x0190}, + {0x020a, 0x0190}, {0x020b, 0x0190}, {0x020c, 0x0190}, {0x020d, 0x0190}, + {0x020e, 0x0190}, {0x020f, 0x0190}, {0x0210, 0x0190}, {0x0211, 0x0190}, + {0x0212, 0x0190}, {0x0213, 0x0190}, {0x0214, 0x0190}, {0x0215, 0x0190}, + {0x0216, 0x0190}, {0x0217, 0x0190}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, + {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x1700, 0x0125}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, + {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, + {0x133f, 0x0010}, +}; + +static const struct rtl8367_initval rtl8367_initvals_1_0[] = { + {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, + {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, + {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, + {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, + {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, + {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, + {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, + {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, + {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, + {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, + {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, + {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, + {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, + {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, + {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, + {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, + {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, + {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, + {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, + {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, + {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, + {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, + {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, + {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, + {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, + {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, + {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, + {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, + {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, + {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, + {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, + {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, + {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, + {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, + {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, + {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, + {0x121D, 0x7D16}, {0x121E, 0x03E8}, {0x121F, 0x024E}, {0x1220, 0x0230}, + {0x1221, 0x0244}, {0x1222, 0x0226}, {0x1223, 0x024E}, {0x1224, 0x0230}, + {0x1225, 0x0244}, {0x1226, 0x0226}, {0x1227, 0x00C0}, {0x1228, 0x00B4}, + {0x122F, 0x00C0}, {0x1230, 0x00B4}, {0x0208, 0x03E8}, {0x0209, 0x03E8}, + {0x020A, 0x03E8}, {0x020B, 0x03E8}, {0x020C, 0x03E8}, {0x020D, 0x03E8}, + {0x020E, 0x03E8}, {0x020F, 0x03E8}, {0x0210, 0x03E8}, {0x0211, 0x03E8}, + {0x0212, 0x03E8}, {0x0213, 0x03E8}, {0x0214, 0x03E8}, {0x0215, 0x03E8}, + {0x0216, 0x03E8}, {0x0217, 0x03E8}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087B, 0x0000}, + {0x087C, 0xFF00}, {0x087D, 0x0000}, {0x087E, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, + {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, + {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, {0x2200, 0x1340}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x20A0, 0x1940}, + {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, +}; + +static const struct rtl8367_initval rtl8367_initvals_1_1[] = { + {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, + {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, + {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, + {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, + {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, + {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, + {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, + {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, + {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, + {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, + {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, + {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, + {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, + {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, + {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, + {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, + {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, + {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, + {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, + {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, + {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, + {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, + {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, + {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, + {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, + {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, + {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, + {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, + {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, + {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, + {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, + {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, + {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, + {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, + {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, + {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, + {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, + {0x0865, 0x3210}, {0x087B, 0x0000}, {0x087C, 0xFF00}, {0x087D, 0x0000}, + {0x087E, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, + {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, + {0x0A25, 0x2040}, {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, + {0x0A29, 0x2040}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, + {0x2200, 0x1340}, {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, + {0x1B03, 0x0876}, +}; + +static const struct rtl8367_initval rtl8367_initvals_2_0[] = { + {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, + {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, + {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, + {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, + {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, + {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, + {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, + {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, + {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, + {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, + {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, + {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, + {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, + {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, + {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, + {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, + {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, + {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, + {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, + {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, + {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, + {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, + {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, + {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, + {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, + {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, + {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, + {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, + {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, + {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, + {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, + {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, + {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, + {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, + {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, + {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x7D16}, + {0x121e, 0x03e8}, {0x121f, 0x024e}, {0x1220, 0x0230}, {0x1221, 0x0244}, + {0x1222, 0x0226}, {0x1223, 0x024e}, {0x1224, 0x0230}, {0x1225, 0x0244}, + {0x1226, 0x0226}, {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, + {0x1230, 0x00b4}, {0x0208, 0x03e8}, {0x0209, 0x03e8}, {0x020a, 0x03e8}, + {0x020b, 0x03e8}, {0x020c, 0x03e8}, {0x020d, 0x03e8}, {0x020e, 0x03e8}, + {0x020f, 0x03e8}, {0x0210, 0x03e8}, {0x0211, 0x03e8}, {0x0212, 0x03e8}, + {0x0213, 0x03e8}, {0x0214, 0x03e8}, {0x0215, 0x03e8}, {0x0216, 0x03e8}, + {0x0217, 0x03e8}, {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, + {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, {0x087c, 0xff00}, + {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, + {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, + {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, {0x20A0, 0x1940}, + {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, +}; + +static const struct rtl8367_initval rtl8367_initvals_2_1[] = { + {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, + {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, + {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, + {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, + {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, + {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, + {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, + {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, + {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, + {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, + {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, + {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, + {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, + {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, + {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, + {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, + {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, + {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, + {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, + {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, + {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, + {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, + {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, + {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, + {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, + {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, + {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, + {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, + {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, + {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, + {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, + {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, + {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, + {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, + {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, + {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x0900, 0x0000}, + {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, + {0x087b, 0x0000}, {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, + {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, + {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A25, 0x2040}, + {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, + {0x130c, 0x0050}, +}; + +static int rtl8367_write_initvals(struct rtl8366_smi *smi, + const struct rtl8367_initval *initvals, + int count) +{ + int err; + int i; + + for (i = 0; i < count; i++) + REG_WR(smi, initvals[i].reg, initvals[i].val); + + return 0; +} + +static int rtl8367_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 *val) +{ + int timeout; + u32 data; + int err; + + if (phy_addr > RTL8367_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if (data & RTL8367_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* prepare address */ + REG_WR(smi, RTL8367_IA_ADDRESS_REG, + RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send read command */ + REG_WR(smi, RTL8367_IA_CTRL_REG, + RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_READ); + + timeout = 5; + do { + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy read timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + /* read data */ + REG_RD(smi, RTL8367_IA_READ_DATA_REG, val); + + dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, *val); + return 0; +} + +static int rtl8367_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 val) +{ + int timeout; + u32 data; + int err; + + dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, val); + + if (phy_addr > RTL8367_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if (data & RTL8367_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* preapre data */ + REG_WR(smi, RTL8367_IA_WRITE_DATA_REG, val); + + /* prepare address */ + REG_WR(smi, RTL8367_IA_ADDRESS_REG, + RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send write command */ + REG_WR(smi, RTL8367_IA_CTRL_REG, + RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_WRITE); + + timeout = 5; + do { + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy write timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + return 0; +} + +static int rtl8367_init_regs0(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + int err; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_0_0; + count = ARRAY_SIZE(rtl8367_initvals_0_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_0_1; + count = ARRAY_SIZE(rtl8367_initvals_0_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + err = rtl8367_write_initvals(smi, initvals, count); + if (err) + return err; + + /* TODO: complete this */ + + return 0; +} + +static int rtl8367_init_regs1(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_1_0; + count = ARRAY_SIZE(rtl8367_initvals_1_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_1_1; + count = ARRAY_SIZE(rtl8367_initvals_1_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + return rtl8367_write_initvals(smi, initvals, count); +} + +static int rtl8367_init_regs2(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_2_0; + count = ARRAY_SIZE(rtl8367_initvals_2_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_2_1; + count = ARRAY_SIZE(rtl8367_initvals_2_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + return rtl8367_write_initvals(smi, initvals, count); +} + +static int rtl8367_init_regs(struct rtl8366_smi *smi) +{ + u32 data; + u32 rlvid; + u32 mode; + int err; + + REG_WR(smi, RTL8367_RTL_MAGIC_ID_REG, RTL8367_RTL_MAGIC_ID_VAL); + + REG_RD(smi, RTL8367_CHIP_VER_REG, &data); + rlvid = (data >> RTL8367_CHIP_VER_RLVID_SHIFT) & + RTL8367_CHIP_VER_RLVID_MASK; + + REG_RD(smi, RTL8367_CHIP_MODE_REG, &data); + mode = data & RTL8367_CHIP_MODE_MASK; + + switch (rlvid) { + case 0: + err = rtl8367_init_regs0(smi, mode); + break; + + case 1: + err = rtl8367_write_phy_reg(smi, 0, 31, 5); + if (err) + break; + + err = rtl8367_write_phy_reg(smi, 0, 5, 0x3ffe); + if (err) + break; + + err = rtl8367_read_phy_reg(smi, 0, 6, &data); + if (err) + break; + + if (data == 0x94eb) { + err = rtl8367_init_regs1(smi, mode); + } else if (data == 0x2104) { + err = rtl8367_init_regs2(smi, mode); + } else { + dev_err(smi->parent, "unknow phy data %04x\n", data); + return -ENODEV; + } + + break; + + default: + dev_err(smi->parent, "unknow rlvid %u\n", rlvid); + err = -ENODEV; + break; + } + + return err; +} + +static int rtl8367_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + int err; + u32 data; + + REG_WR(smi, RTL8367_CHIP_RESET_REG, RTL8367_CHIP_RESET_HW); + msleep(RTL8367_RESET_DELAY); + + do { + REG_RD(smi, RTL8367_CHIP_RESET_REG, &data); + if (!(data & RTL8367_CHIP_RESET_HW)) + break; + + msleep(1); + } while (--timeout); + + if (!timeout) { + dev_err(smi->parent, "chip reset timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int rtl8367_extif_set_mode(struct rtl8366_smi *smi, int id, + enum rtl8367_extif_mode mode) +{ + int err; + + /* set port mode */ + switch (mode) { + case RTL8367_EXTIF_MODE_RGMII: + case RTL8367_EXTIF_MODE_RGMII_33V: + REG_WR(smi, RTL8367_CHIP_DEBUG0_REG, 0x0367); + REG_WR(smi, RTL8367_CHIP_DEBUG1_REG, 0x7777); + break; + + case RTL8367_EXTIF_MODE_TMII_MAC: + case RTL8367_EXTIF_MODE_TMII_PHY: + REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), BIT((id + 1) % 2)); + break; + + case RTL8367_EXTIF_MODE_GMII: + REG_RMW(smi, RTL8367_CHIP_DEBUG0_REG, + RTL8367_CHIP_DEBUG0_DUMMY0(id), + RTL8367_CHIP_DEBUG0_DUMMY0(id)); + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), BIT(6)); + break; + + case RTL8367_EXTIF_MODE_MII_MAC: + case RTL8367_EXTIF_MODE_MII_PHY: + case RTL8367_EXTIF_MODE_DISABLED: + REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), 0); + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), 0); + break; + + default: + dev_err(smi->parent, + "invalid mode for external interface %d\n", id); + return -EINVAL; + } + + REG_RMW(smi, RTL8367_DIS_REG, + RTL8367_DIS_RGMII_MASK << RTL8367_DIS_RGMII_SHIFT(id), + mode << RTL8367_DIS_RGMII_SHIFT(id)); + + return 0; +} + +static int rtl8367_extif_set_force(struct rtl8366_smi *smi, int id, + struct rtl8367_port_ability *pa) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367_DI_FORCE_MODE | + RTL8367_DI_FORCE_NWAY | + RTL8367_DI_FORCE_TXPAUSE | + RTL8367_DI_FORCE_RXPAUSE | + RTL8367_DI_FORCE_LINK | + RTL8367_DI_FORCE_DUPLEX | + RTL8367_DI_FORCE_SPEED_MASK); + + val = pa->speed; + val |= pa->force_mode ? RTL8367_DI_FORCE_MODE : 0; + val |= pa->nway ? RTL8367_DI_FORCE_NWAY : 0; + val |= pa->txpause ? RTL8367_DI_FORCE_TXPAUSE : 0; + val |= pa->rxpause ? RTL8367_DI_FORCE_RXPAUSE : 0; + val |= pa->link ? RTL8367_DI_FORCE_LINK : 0; + val |= pa->duplex ? RTL8367_DI_FORCE_DUPLEX : 0; + + REG_RMW(smi, RTL8367_DI_FORCE_REG(id), mask, val); + + return 0; +} + +static int rtl8367_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, + unsigned txdelay, unsigned rxdelay) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367_EXT_RGMXF_RXDELAY_MASK | + (RTL8367_EXT_RGMXF_TXDELAY_MASK << + RTL8367_EXT_RGMXF_TXDELAY_SHIFT)); + + val = rxdelay; + val |= txdelay << RTL8367_EXT_RGMXF_TXDELAY_SHIFT; + + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), mask, val); + + return 0; +} + +static int rtl8367_extif_init(struct rtl8366_smi *smi, int id, + struct rtl8367_extif_config *cfg) +{ + enum rtl8367_extif_mode mode; + int err; + + mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; + + err = rtl8367_extif_set_mode(smi, id, mode); + if (err) + return err; + + if (mode != RTL8367_EXTIF_MODE_DISABLED) { + err = rtl8367_extif_set_force(smi, id, &cfg->ability); + if (err) + return err; + + err = rtl8367_extif_set_rgmii_delay(smi, id, cfg->txdelay, + cfg->rxdelay); + if (err) + return err; + } + + return 0; +} + +static int rtl8367_led_group_set_ports(struct rtl8366_smi *smi, + unsigned int group, u16 port_mask) +{ + u32 reg; + u32 s; + int err; + + port_mask &= RTL8367_PARA_LED_IO_EN_PMASK; + s = (group % 2) * 8; + reg = RTL8367_PARA_LED_IO_EN1_REG + (group / 2); + + REG_RMW(smi, reg, (RTL8367_PARA_LED_IO_EN_PMASK << s), port_mask << s); + + return 0; +} + +static int rtl8367_led_group_set_mode(struct rtl8366_smi *smi, + unsigned int mode) +{ + u16 mask; + u16 set; + int err; + + mode &= RTL8367_LED_CONFIG_DATA_M; + + mask = (RTL8367_LED_CONFIG_DATA_M << RTL8367_LED_CONFIG_DATA_S) | + RTL8367_LED_CONFIG_SEL; + set = (mode << RTL8367_LED_CONFIG_DATA_S) | RTL8367_LED_CONFIG_SEL; + + REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); + + return 0; +} + +static int rtl8367_led_group_set_config(struct rtl8366_smi *smi, + unsigned int led, unsigned int cfg) +{ + u16 mask; + u16 set; + int err; + + mask = (RTL8367_LED_CONFIG_LED_CFG_M << (led * 4)) | + RTL8367_LED_CONFIG_SEL; + set = (cfg & RTL8367_LED_CONFIG_LED_CFG_M) << (led * 4); + + REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); + return 0; +} + +static int rtl8367_led_op_select_parallel(struct rtl8366_smi *smi) +{ + int err; + + REG_WR(smi, RTL8367_LED_SYS_CONFIG_REG, 0x1472); + return 0; +} + +static int rtl8367_led_blinkrate_set(struct rtl8366_smi *smi, unsigned int rate) +{ + u16 mask; + u16 set; + int err; + + mask = RTL8367_LED_MODE_RATE_M << RTL8367_LED_MODE_RATE_S; + set = (rate & RTL8367_LED_MODE_RATE_M) << RTL8367_LED_MODE_RATE_S; + REG_RMW(smi, RTL8367_LED_MODE_REG, mask, set); + + return 0; +} + +static int rtl8367_setup(struct rtl8366_smi *smi) +{ + struct rtl8367_platform_data *pdata; + int err; + int i; + + pdata = smi->parent->platform_data; + + err = rtl8367_init_regs(smi); + if (err) + return err; + + /* initialize external interfaces */ + err = rtl8367_extif_init(smi, 0, pdata->extif0_cfg); + if (err) + return err; + + err = rtl8367_extif_init(smi, 1, pdata->extif1_cfg); + if (err) + return err; + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8367_SWC0_REG, RTL8367_SWC0_MAX_LENGTH_MASK, + RTL8367_SWC0_MAX_LENGTH_1536); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8367_VLAN_INGRESS_REG, RTL8367_PORTS_ALL); + + /* + * Setup egress tag mode for each port. + */ + for (i = 0; i < RTL8367_NUM_PORTS; i++) + REG_RMW(smi, + RTL8367_PORT_CFG_REG(i), + RTL8367_PORT_CFG_EGRESS_MODE_MASK << + RTL8367_PORT_CFG_EGRESS_MODE_SHIFT, + RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL << + RTL8367_PORT_CFG_EGRESS_MODE_SHIFT); + + /* setup LEDs */ + err = rtl8367_led_group_set_ports(smi, 0, RTL8367_PORTS_ALL); + if (err) + return err; + + err = rtl8367_led_group_set_mode(smi, 0); + if (err) + return err; + + err = rtl8367_led_op_select_parallel(smi); + if (err) + return err; + + err = rtl8367_led_blinkrate_set(smi, 1); + if (err) + return err; + + err = rtl8367_led_group_set_config(smi, 0, 2); + if (err) + return err; + + return 0; +} + +static int rtl8367_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + struct rtl8366_mib_counter *mib; + int offset; + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8367_NUM_PORTS || counter >= RTL8367_MIB_COUNT) + return -EINVAL; + + mib = &rtl8367_mib_counters[counter]; + addr = RTL8367_MIB_COUNTER_PORT_OFFSET * port + mib->offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + REG_WR(smi, RTL8367_MIB_ADDRESS_REG, addr >> 2); + + /* read MIB control register */ + REG_RD(smi, RTL8367_MIB_CTRL_REG(0), &data); + + if (data & RTL8367_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8367_MIB_CTRL_RESET_MASK) + return -EIO; + + if (mib->length == 4) + offset = 3; + else + offset = (mib->offset + 1) % 4; + + mibvalue = 0; + for (i = 0; i < mib->length; i++) { + REG_RD(smi, RTL8367_MIB_COUNTER_REG(offset - i), &data); + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8367_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367_TA_VLAN_DATA_SIZE]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8367_NUM_VIDS) + return -EINVAL; + + /* write VID */ + REG_WR(smi, RTL8367_TA_ADDR_REG, vid); + + /* write table access control word */ + REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_READ); + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367_TA_DATA_REG(i), &data[i]); + + vlan4k->vid = vid; + vlan4k->member = (data[0] >> RTL8367_TA_VLAN_MEMBER_SHIFT) & + RTL8367_TA_VLAN_MEMBER_MASK; + vlan4k->fid = (data[1] >> RTL8367_TA_VLAN_FID_SHIFT) & + RTL8367_TA_VLAN_FID_MASK; + vlan4k->untag = (data[2] >> RTL8367_TA_VLAN_UNTAG1_SHIFT) & + RTL8367_TA_VLAN_UNTAG1_MASK; + vlan4k->untag |= ((data[3] >> RTL8367_TA_VLAN_UNTAG2_SHIFT) & + RTL8367_TA_VLAN_UNTAG2_MASK) << 2; + + return 0; +} + +static int rtl8367_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367_TA_VLAN_DATA_SIZE]; + int err; + int i; + + if (vlan4k->vid >= RTL8367_NUM_VIDS || + vlan4k->member > RTL8367_TA_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8367_UNTAG_MASK || + vlan4k->fid > RTL8367_FIDMAX) + return -EINVAL; + + data[0] = (vlan4k->member & RTL8367_TA_VLAN_MEMBER_MASK) << + RTL8367_TA_VLAN_MEMBER_SHIFT; + data[1] = (vlan4k->fid & RTL8367_TA_VLAN_FID_MASK) << + RTL8367_TA_VLAN_FID_SHIFT; + data[2] = (vlan4k->untag & RTL8367_TA_VLAN_UNTAG1_MASK) << + RTL8367_TA_VLAN_UNTAG1_SHIFT; + data[3] = ((vlan4k->untag >> 2) & RTL8367_TA_VLAN_UNTAG2_MASK) << + RTL8367_TA_VLAN_UNTAG2_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367_TA_DATA_REG(i), data[i]); + + /* write VID */ + REG_WR(smi, RTL8367_TA_ADDR_REG, + vlan4k->vid & RTL8367_TA_VLAN_VID_MASK); + + /* write table access control word */ + REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_WRITE); + + return 0; +} + +static int rtl8367_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367_VLAN_MC_DATA_SIZE]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8367_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367_VLAN_MC_BASE(index) + i, &data[i]); + + vlanmc->member = (data[0] >> RTL8367_VLAN_MC_MEMBER_SHIFT) & + RTL8367_VLAN_MC_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8367_VLAN_MC_FID_SHIFT) & + RTL8367_VLAN_MC_FID_MASK; + vlanmc->vid = (data[3] >> RTL8367_VLAN_MC_EVID_SHIFT) & + RTL8367_VLAN_MC_EVID_MASK; + + return 0; +} + +static int rtl8367_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367_VLAN_MC_DATA_SIZE]; + int err; + int i; + + if (index >= RTL8367_NUM_VLANS || + vlanmc->vid >= RTL8367_NUM_VIDS || + vlanmc->priority > RTL8367_PRIORITYMAX || + vlanmc->member > RTL8367_VLAN_MC_MEMBER_MASK || + vlanmc->untag > RTL8367_UNTAG_MASK || + vlanmc->fid > RTL8367_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->member & RTL8367_VLAN_MC_MEMBER_MASK) << + RTL8367_VLAN_MC_MEMBER_SHIFT; + data[1] = (vlanmc->fid & RTL8367_VLAN_MC_FID_MASK) << + RTL8367_VLAN_MC_FID_SHIFT; + data[2] = 0; + data[3] = (vlanmc->vid & RTL8367_VLAN_MC_EVID_MASK) << + RTL8367_VLAN_MC_EVID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367_VLAN_MC_BASE(index) + i, data[i]); + + return 0; +} + +static int rtl8367_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + REG_RD(smi, RTL8367_VLAN_PVID_CTRL_REG(port), &data); + + *val = (data >> RTL8367_VLAN_PVID_CTRL_SHIFT(port)) & + RTL8367_VLAN_PVID_CTRL_MASK; + + return 0; +} + +static int rtl8367_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8367_NUM_PORTS || index >= RTL8367_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367_VLAN_PVID_CTRL_REG(port), + RTL8367_VLAN_PVID_CTRL_MASK << + RTL8367_VLAN_PVID_CTRL_SHIFT(port), + (index & RTL8367_VLAN_PVID_CTRL_MASK) << + RTL8367_VLAN_PVID_CTRL_SHIFT(port)); +} + +static int rtl8367_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8367_VLAN_CTRL_REG, + RTL8367_VLAN_CTRL_ENABLE, + (enable) ? RTL8367_VLAN_CTRL_ENABLE : 0); +} + +static int rtl8367_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return 0; +} + +static int rtl8367_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8367_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8367_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8367_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + + REG_WR(smi, RTL8367_PORT_ISOLATION_REG(port), + (enable) ? RTL8367_PORTS_ALL : 0); + + return 0; +} + +static int rtl8367_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(0), 0, + RTL8367_MIB_CTRL_GLOBAL_RESET_MASK); +} + +static int rtl8367_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8367_PORT_STATUS_REG(port), &data); + + link->link = !!(data & RTL8367_PORT_STATUS_LINK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8367_PORT_STATUS_DUPLEX); + link->rx_flow = !!(data & RTL8367_PORT_STATUS_RXPAUSE); + link->tx_flow = !!(data & RTL8367_PORT_STATUS_TXPAUSE); + link->aneg = !!(data & RTL8367_PORT_STATUS_NWAY); + + speed = (data & RTL8367_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8367_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8367_SWC0_REG, &data); + val->value.i = (data & RTL8367_SWC0_MAX_LENGTH_MASK) >> + RTL8367_SWC0_MAX_LENGTH_SHIFT; + + return 0; +} + +static int rtl8367_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 max_len; + + switch (val->value.i) { + case 0: + max_len = RTL8367_SWC0_MAX_LENGTH_1522; + break; + case 1: + max_len = RTL8367_SWC0_MAX_LENGTH_1536; + break; + case 2: + max_len = RTL8367_SWC0_MAX_LENGTH_1552; + break; + case 3: + max_len = RTL8367_SWC0_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8367_SWC0_REG, + RTL8367_SWC0_MAX_LENGTH_MASK, max_len); +} + + +static int rtl8367_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int port; + + port = val->port_vlan; + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(port / 8), 0, + RTL8367_MIB_CTRL_PORT_RESET_MASK(port % 8)); +} + +static struct switch_attr rtl8367_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8367_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + "(0:1522, 1:1536, 2:1552, 3:16000)", + .set = rtl8367_sw_set_max_length, + .get = rtl8367_sw_get_max_length, + .max = 3, + } +}; + +static struct switch_attr rtl8367_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8367_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, +}; + +static struct switch_attr rtl8367_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, +}; + +static const struct switch_dev_ops rtl8367_sw_ops = { + .attr_global = { + .attr = rtl8367_globals, + .n_attr = ARRAY_SIZE(rtl8367_globals), + }, + .attr_port = { + .attr = rtl8367_port, + .n_attr = ARRAY_SIZE(rtl8367_port), + }, + .attr_vlan = { + .attr = rtl8367_vlan, + .n_attr = ARRAY_SIZE(rtl8367_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8367_sw_get_port_link, +}; + +static int rtl8367_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8367"; + dev->cpu_port = RTL8367_CPU_PORT_NUM; + dev->ports = RTL8367_NUM_PORTS; + dev->vlans = RTL8367_NUM_VIDS; + dev->ops = &rtl8367_sw_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8367_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8367_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8367_read_phy_reg(smi, addr, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8367_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8367_write_phy_reg(smi, addr, reg, val); + if (err) + return err; + + /* flush write */ + (void) rtl8367_read_phy_reg(smi, addr, reg, &t); + + return err; +} + +static int rtl8367_detect(struct rtl8366_smi *smi) +{ + u32 rtl_no = 0; + u32 rtl_ver = 0; + char *chip_name; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_NO_REG, &rtl_no); + if (ret) { + dev_err(smi->parent, "unable to read chip number\n"); + return ret; + } + + switch (rtl_no) { + case RTL8367_RTL_NO_8367R: + chip_name = "8367R"; + break; + case RTL8367_RTL_NO_8367M: + chip_name = "8367M"; + break; + default: + dev_err(smi->parent, "unknown chip number (%04x)\n", rtl_no); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_VER_REG, &rtl_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%s ver. %u chip found\n", + chip_name, rtl_ver & RTL8367_RTL_VER_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8367_smi_ops = { + .detect = rtl8367_detect, + .reset_chip = rtl8367_reset_chip, + .setup = rtl8367_setup, + + .mii_read = rtl8367_mii_read, + .mii_write = rtl8367_mii_write, + + .get_vlan_mc = rtl8367_get_vlan_mc, + .set_vlan_mc = rtl8367_set_vlan_mc, + .get_vlan_4k = rtl8367_get_vlan_4k, + .set_vlan_4k = rtl8367_set_vlan_4k, + .get_mc_index = rtl8367_get_mc_index, + .set_mc_index = rtl8367_set_mc_index, + .get_mib_counter = rtl8367_get_mib_counter, + .is_vlan_valid = rtl8367_is_vlan_valid, + .enable_vlan = rtl8367_enable_vlan, + .enable_vlan4k = rtl8367_enable_vlan4k, + .enable_port = rtl8367_enable_port, +}; + +static int __devinit rtl8367_probe(struct platform_device *pdev) +{ + struct rtl8367_platform_data *pdata; + struct rtl8366_smi *smi; + int err; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data specified\n"); + err = -EINVAL; + goto err_out; + } + + smi = rtl8366_smi_alloc(&pdev->dev); + if (!smi) { + err = -ENOMEM; + goto err_out; + } + + smi->gpio_sda = pdata->gpio_sda; + smi->gpio_sck = pdata->gpio_sck; + smi->clk_delay = 1500; + smi->cmd_read = 0xb9; + smi->cmd_write = 0xb8; + smi->ops = &rtl8367_smi_ops; + smi->cpu_port = RTL8367_CPU_PORT_NUM; + smi->num_ports = RTL8367_NUM_PORTS; + smi->num_vlan_mc = RTL8367_NUM_VLANS; + smi->mib_counters = rtl8367_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8367_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8367_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + err_out: + return err; +} + +static int __devexit rtl8367_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8367_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static void rtl8367_shutdown(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) + rtl8367_reset_chip(smi); +} + +static struct platform_driver rtl8367_driver = { + .driver = { + .name = RTL8367_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = rtl8367_probe, + .remove = __devexit_p(rtl8367_remove), + .shutdown = rtl8367_shutdown, +}; + +static int __init rtl8367_module_init(void) +{ + return platform_driver_register(&rtl8367_driver); +} +module_init(rtl8367_module_init); + +static void __exit rtl8367_module_exit(void) +{ + platform_driver_unregister(&rtl8367_driver); +} +module_exit(rtl8367_module_exit); + +MODULE_DESCRIPTION(RTL8367_DRIVER_DESC); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME); diff --git a/drivers/net/phy/swconfig.c b/drivers/net/phy/swconfig.c new file mode 100644 index 0000000..1bae01c --- /dev/null +++ b/drivers/net/phy/swconfig.c @@ -0,0 +1,1042 @@ +/* + * swconfig.c: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG 1 +#ifdef DEBUG +#define DPRINTF(format, ...) printk("%s: " format, __func__, ##__VA_ARGS__) +#else +#define DPRINTF(...) do {} while(0) +#endif + +#define SWCONFIG_DEVNAME "switch%d" + +#include "swconfig_leds.c" + +MODULE_AUTHOR("Felix Fietkau "); +MODULE_LICENSE("GPL"); + +static int swdev_id = 0; +static struct list_head swdevs; +static DEFINE_SPINLOCK(swdevs_lock); +struct swconfig_callback; + +struct swconfig_callback +{ + struct sk_buff *msg; + struct genlmsghdr *hdr; + struct genl_info *info; + int cmd; + + /* callback for filling in the message data */ + int (*fill)(struct swconfig_callback *cb, void *arg); + + /* callback for closing the message before sending it */ + int (*close)(struct swconfig_callback *cb, void *arg); + + struct nlattr *nest[4]; + int args[4]; +}; + +/* defaults */ + +static int +swconfig_get_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int ret; + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + if (!dev->ops->get_vlan_ports) + return -EOPNOTSUPP; + + ret = dev->ops->get_vlan_ports(dev, val); + return ret; +} + +static int +swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct switch_port *ports = val->value.ports; + const struct switch_dev_ops *ops = dev->ops; + int i; + + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + /* validate ports */ + if (val->len > dev->ports) + return -EINVAL; + + if (!ops->set_vlan_ports) + return -EOPNOTSUPP; + + for (i = 0; i < val->len; i++) { + if (ports[i].id >= dev->ports) + return -EINVAL; + + if (ops->set_port_pvid && + !(ports[i].flags & (1 << SWITCH_PORT_FLAG_TAGGED))) + ops->set_port_pvid(dev, ports[i].id, val->port_vlan); + } + + return ops->set_vlan_ports(dev, val); +} + +static int +swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->set_port_pvid) + return -EOPNOTSUPP; + + return dev->ops->set_port_pvid(dev, val->port_vlan, val->value.i); +} + +static int +swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->get_port_pvid) + return -EOPNOTSUPP; + + return dev->ops->get_port_pvid(dev, val->port_vlan, &val->value.i); +} + +static const char * +swconfig_speed_str(enum switch_port_speed speed) +{ + switch (speed) { + case SWITCH_PORT_SPEED_10: + return "10baseT"; + case SWITCH_PORT_SPEED_100: + return "100baseT"; + case SWITCH_PORT_SPEED_1000: + return "1000baseT"; + default: + break; + } + + return "unknown"; +} + +static int +swconfig_get_link(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct switch_port_link link; + int len; + int ret; + + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->get_port_link) + return -EOPNOTSUPP; + + memset(&link, 0, sizeof(link)); + ret = dev->ops->get_port_link(dev, val->port_vlan, &link); + if (ret) + return ret; + + memset(dev->buf, 0, sizeof(dev->buf)); + + if (link.link) + len = snprintf(dev->buf, sizeof(dev->buf), + "port:%d link:up speed:%s %s-duplex %s%s%s", + val->port_vlan, + swconfig_speed_str(link.speed), + link.duplex ? "full" : "half", + link.tx_flow ? "txflow ": "", + link.rx_flow ? "rxflow " : "", + link.aneg ? "auto" : ""); + else + len = snprintf(dev->buf, sizeof(dev->buf), "port:%d link:down", + val->port_vlan); + + val->value.s = dev->buf; + val->len = len; + + return 0; +} + +static int +swconfig_apply_config(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + /* don't complain if not supported by the switch driver */ + if (!dev->ops->apply_config) + return 0; + + return dev->ops->apply_config(dev); +} + +static int +swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + /* don't complain if not supported by the switch driver */ + if (!dev->ops->reset_switch) + return 0; + + return dev->ops->reset_switch(dev); +} + +enum global_defaults { + GLOBAL_APPLY, + GLOBAL_RESET, +}; + +enum vlan_defaults { + VLAN_PORTS, +}; + +enum port_defaults { + PORT_PVID, + PORT_LINK, +}; + +static struct switch_attr default_global[] = { + [GLOBAL_APPLY] = { + .type = SWITCH_TYPE_NOVAL, + .name = "apply", + .description = "Activate changes in the hardware", + .set = swconfig_apply_config, + }, + [GLOBAL_RESET] = { + .type = SWITCH_TYPE_NOVAL, + .name = "reset", + .description = "Reset the switch", + .set = swconfig_reset_switch, + } +}; + +static struct switch_attr default_port[] = { + [PORT_PVID] = { + .type = SWITCH_TYPE_INT, + .name = "pvid", + .description = "Primary VLAN ID", + .set = swconfig_set_pvid, + .get = swconfig_get_pvid, + }, + [PORT_LINK] = { + .type = SWITCH_TYPE_STRING, + .name = "link", + .description = "Get port link information", + .set = NULL, + .get = swconfig_get_link, + } +}; + +static struct switch_attr default_vlan[] = { + [VLAN_PORTS] = { + .type = SWITCH_TYPE_PORTS, + .name = "ports", + .description = "VLAN port mapping", + .set = swconfig_set_vlan_ports, + .get = swconfig_get_vlan_ports, + }, +}; + +static const struct switch_attr * +swconfig_find_attr_by_name(const struct switch_attrlist *alist, const char *name) +{ + int i; + + for (i = 0; i < alist->n_attr; i++) + if (strcmp(name, alist->attr[i].name) == 0) + return &alist->attr[i]; + + return NULL; +} + +static void swconfig_defaults_init(struct switch_dev *dev) +{ + const struct switch_dev_ops *ops = dev->ops; + + dev->def_global = 0; + dev->def_vlan = 0; + dev->def_port = 0; + + if (ops->get_vlan_ports || ops->set_vlan_ports) + set_bit(VLAN_PORTS, &dev->def_vlan); + + if (ops->get_port_pvid || ops->set_port_pvid) + set_bit(PORT_PVID, &dev->def_port); + + if (ops->get_port_link && + !swconfig_find_attr_by_name(&ops->attr_port, "link")) + set_bit(PORT_LINK, &dev->def_port); + + /* always present, can be no-op */ + set_bit(GLOBAL_APPLY, &dev->def_global); + set_bit(GLOBAL_RESET, &dev->def_global); +} + + +static struct genl_family switch_fam = { + .id = GENL_ID_GENERATE, + .name = "switch", + .hdrsize = 0, + .version = 1, + .maxattr = SWITCH_ATTR_MAX, +}; + +static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = { + [SWITCH_ATTR_ID] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_ID] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING }, + [SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED }, + [SWITCH_ATTR_TYPE] = { .type = NLA_U32 }, +}; + +static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = { + [SWITCH_PORT_ID] = { .type = NLA_U32 }, + [SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, +}; + +static inline void +swconfig_lock(void) +{ + spin_lock(&swdevs_lock); +} + +static inline void +swconfig_unlock(void) +{ + spin_unlock(&swdevs_lock); +} + +static struct switch_dev * +swconfig_get_dev(struct genl_info *info) +{ + struct switch_dev *dev = NULL; + struct switch_dev *p; + int id; + + if (!info->attrs[SWITCH_ATTR_ID]) + goto done; + + id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]); + swconfig_lock(); + list_for_each_entry(p, &swdevs, dev_list) { + if (id != p->id) + continue; + + dev = p; + break; + } + if (dev) + mutex_lock(&dev->sw_mutex); + else + DPRINTF("device %d not found\n", id); + swconfig_unlock(); +done: + return dev; +} + +static inline void +swconfig_put_dev(struct switch_dev *dev) +{ + mutex_unlock(&dev->sw_mutex); +} + +static int +swconfig_dump_attr(struct swconfig_callback *cb, void *arg) +{ + struct switch_attr *op = arg; + struct genl_info *info = cb->info; + struct sk_buff *msg = cb->msg; + int id = cb->args[0]; + void *hdr; + + hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, &switch_fam, + NLM_F_MULTI, SWITCH_CMD_NEW_ATTR); + if (IS_ERR(hdr)) + return -1; + + NLA_PUT_U32(msg, SWITCH_ATTR_OP_ID, id); + NLA_PUT_U32(msg, SWITCH_ATTR_OP_TYPE, op->type); + NLA_PUT_STRING(msg, SWITCH_ATTR_OP_NAME, op->name); + if (op->description) + NLA_PUT_STRING(msg, SWITCH_ATTR_OP_DESCRIPTION, + op->description); + + return genlmsg_end(msg, hdr); +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +/* spread multipart messages across multiple message buffers */ +static int +swconfig_send_multipart(struct swconfig_callback *cb, void *arg) +{ + struct genl_info *info = cb->info; + int restart = 0; + int err; + + do { + if (!cb->msg) { + cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (cb->msg == NULL) + goto error; + } + + if (!(cb->fill(cb, arg) < 0)) + break; + + /* fill failed, check if this was already the second attempt */ + if (restart) + goto error; + + /* try again in a new message, send the current one */ + restart = 1; + if (cb->close) { + if (cb->close(cb, arg) < 0) + goto error; + } + err = genlmsg_reply(cb->msg, info); + cb->msg = NULL; + if (err < 0) + goto error; + + } while (restart); + + return 0; + +error: + if (cb->msg) + nlmsg_free(cb->msg); + return -1; +} + +static int +swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attrlist *alist; + struct switch_dev *dev; + struct swconfig_callback cb; + int err = -EINVAL; + int i; + + /* defaults */ + struct switch_attr *def_list; + unsigned long *def_active; + int n_def; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + switch(hdr->cmd) { + case SWITCH_CMD_LIST_GLOBAL: + alist = &dev->ops->attr_global; + def_list = default_global; + def_active = &dev->def_global; + n_def = ARRAY_SIZE(default_global); + break; + case SWITCH_CMD_LIST_VLAN: + alist = &dev->ops->attr_vlan; + def_list = default_vlan; + def_active = &dev->def_vlan; + n_def = ARRAY_SIZE(default_vlan); + break; + case SWITCH_CMD_LIST_PORT: + alist = &dev->ops->attr_port; + def_list = default_port; + def_active = &dev->def_port; + n_def = ARRAY_SIZE(default_port); + break; + default: + WARN_ON(1); + goto out; + } + + memset(&cb, 0, sizeof(cb)); + cb.info = info; + cb.fill = swconfig_dump_attr; + for (i = 0; i < alist->n_attr; i++) { + if (alist->attr[i].disabled) + continue; + cb.args[0] = i; + err = swconfig_send_multipart(&cb, (void *) &alist->attr[i]); + if (err < 0) + goto error; + } + + /* defaults */ + for (i = 0; i < n_def; i++) { + if (!test_bit(i, def_active)) + continue; + cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i; + err = swconfig_send_multipart(&cb, (void *) &def_list[i]); + if (err < 0) + goto error; + } + swconfig_put_dev(dev); + + if (!cb.msg) + return 0; + + return genlmsg_reply(cb.msg, info); + +error: + if (cb.msg) + nlmsg_free(cb.msg); +out: + swconfig_put_dev(dev); + return err; +} + +static const struct switch_attr * +swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info, + struct switch_val *val) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attrlist *alist; + const struct switch_attr *attr = NULL; + int attr_id; + + /* defaults */ + struct switch_attr *def_list; + unsigned long *def_active; + int n_def; + + if (!info->attrs[SWITCH_ATTR_OP_ID]) + goto done; + + switch(hdr->cmd) { + case SWITCH_CMD_SET_GLOBAL: + case SWITCH_CMD_GET_GLOBAL: + alist = &dev->ops->attr_global; + def_list = default_global; + def_active = &dev->def_global; + n_def = ARRAY_SIZE(default_global); + break; + case SWITCH_CMD_SET_VLAN: + case SWITCH_CMD_GET_VLAN: + alist = &dev->ops->attr_vlan; + def_list = default_vlan; + def_active = &dev->def_vlan; + n_def = ARRAY_SIZE(default_vlan); + if (!info->attrs[SWITCH_ATTR_OP_VLAN]) + goto done; + val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]); + if (val->port_vlan >= dev->vlans) + goto done; + break; + case SWITCH_CMD_SET_PORT: + case SWITCH_CMD_GET_PORT: + alist = &dev->ops->attr_port; + def_list = default_port; + def_active = &dev->def_port; + n_def = ARRAY_SIZE(default_port); + if (!info->attrs[SWITCH_ATTR_OP_PORT]) + goto done; + val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]); + if (val->port_vlan >= dev->ports) + goto done; + break; + default: + WARN_ON(1); + goto done; + } + + if (!alist) + goto done; + + attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]); + if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) { + attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET; + if (attr_id >= n_def) + goto done; + if (!test_bit(attr_id, def_active)) + goto done; + attr = &def_list[attr_id]; + } else { + if (attr_id >= alist->n_attr) + goto done; + attr = &alist->attr[attr_id]; + } + + if (attr->disabled) + attr = NULL; + +done: + if (!attr) + DPRINTF("attribute lookup failed\n"); + val->attr = attr; + return attr; +} + +static int +swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head, + struct switch_val *val, int max) +{ + struct nlattr *nla; + int rem; + + val->len = 0; + nla_for_each_nested(nla, head, rem) { + struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; + struct switch_port *port = &val->value.ports[val->len]; + + if (val->len >= max) + return -EINVAL; + + if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, + port_policy)) + return -EINVAL; + + if (!tb[SWITCH_PORT_ID]) + return -EINVAL; + + port->id = nla_get_u32(tb[SWITCH_PORT_ID]); + if (tb[SWITCH_PORT_FLAG_TAGGED]) + port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED); + val->len++; + } + + return 0; +} + +static int +swconfig_set_attr(struct sk_buff *skb, struct genl_info *info) +{ + const struct switch_attr *attr; + struct switch_dev *dev; + struct switch_val val; + int err = -EINVAL; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + memset(&val, 0, sizeof(val)); + attr = swconfig_lookup_attr(dev, info, &val); + if (!attr || !attr->set) + goto error; + + val.attr = attr; + switch(attr->type) { + case SWITCH_TYPE_NOVAL: + break; + case SWITCH_TYPE_INT: + if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT]) + goto error; + val.value.i = + nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]); + break; + case SWITCH_TYPE_STRING: + if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR]) + goto error; + val.value.s = + nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]); + break; + case SWITCH_TYPE_PORTS: + val.value.ports = dev->portbuf; + memset(dev->portbuf, 0, + sizeof(struct switch_port) * dev->ports); + + /* TODO: implement multipart? */ + if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) { + err = swconfig_parse_ports(skb, + info->attrs[SWITCH_ATTR_OP_VALUE_PORTS], &val, dev->ports); + if (err < 0) + goto error; + } else { + val.len = 0; + err = 0; + } + break; + default: + goto error; + } + + err = attr->set(dev, attr, &val); +error: + swconfig_put_dev(dev); + return err; +} + +static int +swconfig_close_portlist(struct swconfig_callback *cb, void *arg) +{ + if (cb->nest[0]) + nla_nest_end(cb->msg, cb->nest[0]); + return 0; +} + +static int +swconfig_send_port(struct swconfig_callback *cb, void *arg) +{ + const struct switch_port *port = arg; + struct nlattr *p = NULL; + + if (!cb->nest[0]) { + cb->nest[0] = nla_nest_start(cb->msg, cb->cmd); + if (!cb->nest[0]) + return -1; + } + + p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT); + if (!p) + goto error; + + NLA_PUT_U32(cb->msg, SWITCH_PORT_ID, port->id); + if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) + NLA_PUT_FLAG(cb->msg, SWITCH_PORT_FLAG_TAGGED); + + nla_nest_end(cb->msg, p); + return 0; + +nla_put_failure: + nla_nest_cancel(cb->msg, p); +error: + nla_nest_cancel(cb->msg, cb->nest[0]); + return -1; +} + +static int +swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr, + const struct switch_val *val) +{ + struct swconfig_callback cb; + int err = 0; + int i; + + if (!val->value.ports) + return -EINVAL; + + memset(&cb, 0, sizeof(cb)); + cb.cmd = attr; + cb.msg = *msg; + cb.info = info; + cb.fill = swconfig_send_port; + cb.close = swconfig_close_portlist; + + cb.nest[0] = nla_nest_start(cb.msg, cb.cmd); + for (i = 0; i < val->len; i++) { + err = swconfig_send_multipart(&cb, &val->value.ports[i]); + if (err) + goto done; + } + err = val->len; + swconfig_close_portlist(&cb, NULL); + *msg = cb.msg; + +done: + return err; +} + +static int +swconfig_get_attr(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attr *attr; + struct switch_dev *dev; + struct sk_buff *msg = NULL; + struct switch_val val; + int err = -EINVAL; + int cmd = hdr->cmd; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + memset(&val, 0, sizeof(val)); + attr = swconfig_lookup_attr(dev, info, &val); + if (!attr || !attr->get) + goto error; + + if (attr->type == SWITCH_TYPE_PORTS) { + val.value.ports = dev->portbuf; + memset(dev->portbuf, 0, + sizeof(struct switch_port) * dev->ports); + } + + err = attr->get(dev, attr, &val); + if (err) + goto error; + + msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + goto error; + + hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, &switch_fam, + 0, cmd); + if (IS_ERR(hdr)) + goto nla_put_failure; + + switch(attr->type) { + case SWITCH_TYPE_INT: + NLA_PUT_U32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i); + break; + case SWITCH_TYPE_STRING: + NLA_PUT_STRING(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s); + break; + case SWITCH_TYPE_PORTS: + err = swconfig_send_ports(&msg, info, + SWITCH_ATTR_OP_VALUE_PORTS, &val); + if (err < 0) + goto nla_put_failure; + break; + default: + DPRINTF("invalid type in attribute\n"); + err = -EINVAL; + goto error; + } + err = genlmsg_end(msg, hdr); + if (err < 0) + goto nla_put_failure; + + swconfig_put_dev(dev); + return genlmsg_reply(msg, info); + +nla_put_failure: + if (msg) + nlmsg_free(msg); +error: + swconfig_put_dev(dev); + if (!err) + err = -ENOMEM; + return err; +} + +static int +swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags, + const struct switch_dev *dev) +{ + void *hdr; + + hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags, + SWITCH_CMD_NEW_ATTR); + if (IS_ERR(hdr)) + return -1; + + NLA_PUT_U32(msg, SWITCH_ATTR_ID, dev->id); + NLA_PUT_STRING(msg, SWITCH_ATTR_DEV_NAME, dev->devname); + NLA_PUT_STRING(msg, SWITCH_ATTR_ALIAS, dev->alias); + NLA_PUT_STRING(msg, SWITCH_ATTR_NAME, dev->name); + NLA_PUT_U32(msg, SWITCH_ATTR_VLANS, dev->vlans); + NLA_PUT_U32(msg, SWITCH_ATTR_PORTS, dev->ports); + NLA_PUT_U32(msg, SWITCH_ATTR_CPU_PORT, dev->cpu_port); + + return genlmsg_end(msg, hdr); +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +static int swconfig_dump_switches(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct switch_dev *dev; + int start = cb->args[0]; + int idx = 0; + + swconfig_lock(); + list_for_each_entry(dev, &swdevs, dev_list) { + if (++idx <= start) + continue; + if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).pid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + dev) < 0) + break; + } + swconfig_unlock(); + cb->args[0] = idx; + + return skb->len; +} + +static int +swconfig_done(struct netlink_callback *cb) +{ + return 0; +} + +static struct genl_ops swconfig_ops[] = { + { + .cmd = SWITCH_CMD_LIST_GLOBAL, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_LIST_VLAN, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_LIST_PORT, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_GLOBAL, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_VLAN, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_PORT, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_GLOBAL, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_VLAN, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_PORT, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_SWITCH, + .dumpit = swconfig_dump_switches, + .policy = switch_policy, + .done = swconfig_done, + } +}; + +int +register_switch(struct switch_dev *dev, struct net_device *netdev) +{ + struct switch_dev *sdev; + const int max_switches = 8 * sizeof(unsigned long); + unsigned long in_use = 0; + int err; + int i; + + INIT_LIST_HEAD(&dev->dev_list); + if (netdev) { + dev->netdev = netdev; + if (!dev->alias) + dev->alias = netdev->name; + } + BUG_ON(!dev->alias); + + if (dev->ports > 0) { + dev->portbuf = kzalloc(sizeof(struct switch_port) * dev->ports, + GFP_KERNEL); + if (!dev->portbuf) + return -ENOMEM; + } + swconfig_defaults_init(dev); + mutex_init(&dev->sw_mutex); + swconfig_lock(); + dev->id = ++swdev_id; + + list_for_each_entry(sdev, &swdevs, dev_list) { + if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i)) + continue; + if (i < 0 || i > max_switches) + continue; + + set_bit(i, &in_use); + } + i = find_first_zero_bit(&in_use, max_switches); + + if (i == max_switches) { + swconfig_unlock(); + return -ENFILE; + } + + /* fill device name */ + snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i); + + list_add(&dev->dev_list, &swdevs); + swconfig_unlock(); + + err = swconfig_create_led_trigger(dev); + if (err) + return err; + + return 0; +} +EXPORT_SYMBOL_GPL(register_switch); + +void +unregister_switch(struct switch_dev *dev) +{ + swconfig_destroy_led_trigger(dev); + kfree(dev->portbuf); + mutex_lock(&dev->sw_mutex); + swconfig_lock(); + list_del(&dev->dev_list); + swconfig_unlock(); + mutex_unlock(&dev->sw_mutex); +} +EXPORT_SYMBOL_GPL(unregister_switch); + + +static int __init +swconfig_init(void) +{ + int i, err; + + INIT_LIST_HEAD(&swdevs); + err = genl_register_family(&switch_fam); + if (err) + return err; + + for (i = 0; i < ARRAY_SIZE(swconfig_ops); i++) { + err = genl_register_ops(&switch_fam, &swconfig_ops[i]); + if (err) + goto unregister; + } + + return 0; + +unregister: + genl_unregister_family(&switch_fam); + return err; +} + +static void __exit +swconfig_exit(void) +{ + genl_unregister_family(&switch_fam); +} + +module_init(swconfig_init); +module_exit(swconfig_exit); diff --git a/drivers/net/phy/swconfig_leds.c b/drivers/net/phy/swconfig_leds.c new file mode 100644 index 0000000..6f54cc1 --- /dev/null +++ b/drivers/net/phy/swconfig_leds.c @@ -0,0 +1,354 @@ +/* + * swconfig_led.c: LED trigger support for the switch configuration API + * + * Copyright (C) 2011 Gabor Juhos + * + * 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. + * + */ + +#ifdef CONFIG_SWCONFIG_LEDS + +#include +#include +#include +#include + +#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10) +#define SWCONFIG_LED_NUM_PORTS 32 + +struct switch_led_trigger { + struct led_trigger trig; + struct switch_dev *swdev; + + struct delayed_work sw_led_work; + u32 port_mask; + u32 port_link; + unsigned long port_traffic[SWCONFIG_LED_NUM_PORTS]; +}; + +struct swconfig_trig_data { + struct led_classdev *led_cdev; + struct switch_dev *swdev; + + rwlock_t lock; + u32 port_mask; + + bool prev_link; + unsigned long prev_traffic; + enum led_brightness prev_brightness; +}; + +static void +swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data, + enum led_brightness brightness) +{ + led_brightness_set(trig_data->led_cdev, brightness); + trig_data->prev_brightness = brightness; +} + +static void +swconfig_trig_update_port_mask(struct led_trigger *trigger) +{ + struct list_head *entry; + struct switch_led_trigger *sw_trig; + u32 port_mask; + + if (!trigger) + return; + + sw_trig = (void *) trigger; + + port_mask = 0; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + struct swconfig_trig_data *trig_data; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + trig_data = led_cdev->trigger_data; + if (trig_data) { + read_lock(&trig_data->lock); + port_mask |= trig_data->port_mask; + read_unlock(&trig_data->lock); + } + } + read_unlock(&trigger->leddev_list_lock); + + sw_trig->port_mask = port_mask; + + if (port_mask) + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); + else + cancel_delayed_work_sync(&sw_trig->sw_led_work); +} + +static ssize_t +swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + unsigned long port_mask; + ssize_t ret = -EINVAL; + char *after; + size_t count; + + port_mask = simple_strtoul(buf, &after, 16); + count = after - buf; + + if (*after && isspace(*after)) + count++; + + if (count == size) { + bool changed; + + write_lock(&trig_data->lock); + + changed = (trig_data->port_mask != port_mask); + if (changed) { + trig_data->port_mask = port_mask; + if (port_mask == 0) + swconfig_trig_set_brightness(trig_data, LED_OFF); + } + + write_unlock(&trig_data->lock); + + if (changed) + swconfig_trig_update_port_mask(led_cdev->trigger); + + ret = count; + } + + return ret; +} + +static ssize_t +swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + + read_lock(&trig_data->lock); + sprintf(buf, "%#x\n", trig_data->port_mask); + read_unlock(&trig_data->lock); + + return strlen(buf) + 1; +} + +static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show, + swconfig_trig_port_mask_store); + +static void +swconfig_trig_activate(struct led_classdev *led_cdev) +{ + struct switch_led_trigger *sw_trig; + struct swconfig_trig_data *trig_data; + int err; + + if (led_cdev->trigger->activate != swconfig_trig_activate) + return; + + trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL); + if (!trig_data) + return; + + sw_trig = (void *) led_cdev->trigger; + + rwlock_init(&trig_data->lock); + trig_data->led_cdev = led_cdev; + trig_data->swdev = sw_trig->swdev; + led_cdev->trigger_data = trig_data; + + err = device_create_file(led_cdev->dev, &dev_attr_port_mask); + if (err) + goto err_free; + + return; + +err_free: + led_cdev->trigger_data = NULL; + kfree(trig_data); +} + +static void +swconfig_trig_deactivate(struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + + swconfig_trig_update_port_mask(led_cdev->trigger); + + trig_data = (void *) led_cdev->trigger_data; + if (trig_data) { + device_remove_file(led_cdev->dev, &dev_attr_port_mask); + kfree(trig_data); + } +} + +static void +swconfig_trig_led_event(struct switch_led_trigger *sw_trig, + struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + u32 port_mask; + bool link; + + trig_data = led_cdev->trigger_data; + if (!trig_data) + return; + + read_lock(&trig_data->lock); + port_mask = trig_data->port_mask; + read_unlock(&trig_data->lock); + + link = !!(sw_trig->port_link & port_mask); + if (!link) { + if (link != trig_data->prev_link) + led_brightness_set(trig_data->led_cdev, LED_OFF); + } else { + unsigned long traffic; + int i; + + traffic = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + if (port_mask & (1 << i)) + traffic += sw_trig->port_traffic[i]; + } + + if (trig_data->prev_brightness != LED_FULL) + swconfig_trig_set_brightness(trig_data, LED_FULL); + else if (traffic != trig_data->prev_traffic) + swconfig_trig_set_brightness(trig_data, LED_OFF); + + trig_data->prev_traffic = traffic; + } + + trig_data->prev_link = link; +} + +static void +swconfig_trig_update_leds(struct switch_led_trigger *sw_trig) +{ + struct list_head *entry; + struct led_trigger *trigger; + + trigger = &sw_trig->trig; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + swconfig_trig_led_event(sw_trig, led_cdev); + } + read_unlock(&trigger->leddev_list_lock); +} + +static void +swconfig_led_work_func(struct work_struct *work) +{ + struct switch_led_trigger *sw_trig; + struct switch_dev *swdev; + u32 port_mask; + u32 link; + int i; + + sw_trig = container_of(work, struct switch_led_trigger, + sw_led_work.work); + + port_mask = sw_trig->port_mask; + swdev = sw_trig->swdev; + + link = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + u32 port_bit; + + port_bit = BIT(i); + if ((port_mask & port_bit) == 0) + continue; + + if (swdev->ops->get_port_link) { + struct switch_port_link port_link; + + memset(&port_link, '\0', sizeof(port_link)); + swdev->ops->get_port_link(swdev, i, &port_link); + + if (port_link.link) + link |= port_bit; + } + + if (swdev->ops->get_port_stats) { + struct switch_port_stats port_stats; + + memset(&port_stats, '\0', sizeof(port_stats)); + swdev->ops->get_port_stats(swdev, i, &port_stats); + sw_trig->port_traffic[i] = port_stats.tx_bytes + + port_stats.rx_bytes; + } + } + + sw_trig->port_link = link; + + swconfig_trig_update_leds(sw_trig); + + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); +} + +static int +swconfig_create_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + int err; + + if (!swdev->ops->get_port_link) + return 0; + + sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL); + if (!sw_trig) + return -ENOMEM; + + sw_trig->swdev = swdev; + sw_trig->trig.name = swdev->devname; + sw_trig->trig.activate = swconfig_trig_activate; + sw_trig->trig.deactivate = swconfig_trig_deactivate; + + INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func); + + err = led_trigger_register(&sw_trig->trig); + if (err) + goto err_free; + + swdev->led_trigger = sw_trig; + + return 0; + +err_free: + kfree(sw_trig); + return err; +} + +static void +swconfig_destroy_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + + sw_trig = swdev->led_trigger; + if (sw_trig) { + cancel_delayed_work_sync(&sw_trig->sw_led_work); + led_trigger_unregister(&sw_trig->trig); + kfree(sw_trig); + } +} + +#else /* SWCONFIG_LEDS */ +static inline int +swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; } + +static inline void +swconfig_destroy_led_trigger(struct switch_dev *swdev) { } +#endif /* CONFIG_SWCONFIG_LEDS */