|  | /* | 
|  | * ADM6996 switch driver | 
|  | * | 
|  | * swconfig interface based on ar8216.c | 
|  | * | 
|  | * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name> | 
|  | * VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com> | 
|  | * Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de> | 
|  | * Copyright (c) 2014 Matti Laakso <malaakso@elisanet.fi> | 
|  | * | 
|  | * 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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 
|  |  | 
|  | /*#define DEBUG 1*/ | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/string.h> | 
|  | #include <linux/errno.h> | 
|  | #include <linux/unistd.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/gpio.h> | 
|  | #include <linux/netdevice.h> | 
|  | #include <linux/etherdevice.h> | 
|  | #include <linux/skbuff.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/mm.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mii.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/platform_data/adm6996-gpio.h> | 
|  | #include <linux/ethtool.h> | 
|  | #include <linux/phy.h> | 
|  | #include <linux/switch.h> | 
|  | #include <linux/version.h> | 
|  |  | 
|  | #include <asm/io.h> | 
|  | #include <asm/irq.h> | 
|  | #include <asm/uaccess.h> | 
|  | #include "adm6996.h" | 
|  |  | 
|  | MODULE_DESCRIPTION("Infineon ADM6996 Switch"); | 
|  | MODULE_AUTHOR("Felix Fietkau, Peter Lebbing <peter@digitalbrains.com>"); | 
|  | MODULE_LICENSE("GPL"); | 
|  |  | 
|  | static const char * const adm6996_model_name[] = | 
|  | { | 
|  | NULL, | 
|  | "ADM6996FC", | 
|  | "ADM6996M", | 
|  | "ADM6996L" | 
|  | }; | 
|  |  | 
|  | struct adm6996_mib_desc { | 
|  | unsigned int offset; | 
|  | const char *name; | 
|  | }; | 
|  |  | 
|  | struct adm6996_priv { | 
|  | struct switch_dev dev; | 
|  | void *priv; | 
|  |  | 
|  | u8 eecs; | 
|  | u8 eesk; | 
|  | u8 eedi; | 
|  |  | 
|  | 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 */ | 
|  | u8 tagged_ports; | 
|  |  | 
|  | 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 mib_lock; | 
|  | char buf[2048]; | 
|  |  | 
|  | struct mutex reg_mutex; | 
|  |  | 
|  | /* use abstraction for regops, we want to add gpio support in the future */ | 
|  | u16 (*read)(struct adm6996_priv *priv, enum admreg reg); | 
|  | void (*write)(struct adm6996_priv *priv, 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) | 
|  |  | 
|  | #define MIB_DESC(_o, _n)	\ | 
|  | {			\ | 
|  | .offset = (_o),	\ | 
|  | .name = (_n),	\ | 
|  | } | 
|  |  | 
|  | static const struct adm6996_mib_desc adm6996_mibs[] = { | 
|  | MIB_DESC(ADM_CL0, "RxPacket"), | 
|  | MIB_DESC(ADM_CL6, "RxByte"), | 
|  | MIB_DESC(ADM_CL12, "TxPacket"), | 
|  | MIB_DESC(ADM_CL18, "TxByte"), | 
|  | MIB_DESC(ADM_CL24, "Collision"), | 
|  | MIB_DESC(ADM_CL30, "Error"), | 
|  | }; | 
|  |  | 
|  | #define ADM6996_MIB_RXB_ID	1 | 
|  | #define ADM6996_MIB_TXB_ID	3 | 
|  |  | 
|  | static inline u16 | 
|  | r16(struct adm6996_priv *priv, enum admreg reg) | 
|  | { | 
|  | return priv->read(priv, reg); | 
|  | } | 
|  |  | 
|  | static inline void | 
|  | w16(struct adm6996_priv *priv, enum admreg reg, u16 val) | 
|  | { | 
|  | priv->write(priv, reg, val); | 
|  | } | 
|  |  | 
|  | /* Minimum timing constants */ | 
|  | #define EECK_EDGE_TIME  3   /* 3us - max(adm 2.5us, 93c 1us) */ | 
|  | #define EEDI_SETUP_TIME 1   /* 1us - max(adm 10ns, 93c 400ns) */ | 
|  | #define EECS_SETUP_TIME 1   /* 1us - max(adm no, 93c 200ns) */ | 
|  |  | 
|  | static void adm6996_gpio_write(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) | 
|  | { | 
|  | int i, len = (bits + 7) / 8; | 
|  | u8 mask; | 
|  |  | 
|  | gpio_set_value(priv->eecs, cs); | 
|  | udelay(EECK_EDGE_TIME); | 
|  |  | 
|  | /* Byte assemble from MSB to LSB */ | 
|  | for (i = 0; i < len; i++) { | 
|  | /* Bit bang from MSB to LSB */ | 
|  | for (mask = 0x80; mask && bits > 0; mask >>= 1, bits --) { | 
|  | /* Clock low */ | 
|  | gpio_set_value(priv->eesk, 0); | 
|  | udelay(EECK_EDGE_TIME); | 
|  |  | 
|  | /* Output on rising edge */ | 
|  | gpio_set_value(priv->eedi, (mask & buf[i])); | 
|  | udelay(EEDI_SETUP_TIME); | 
|  |  | 
|  | /* Clock high */ | 
|  | gpio_set_value(priv->eesk, 1); | 
|  | udelay(EECK_EDGE_TIME); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Clock low */ | 
|  | gpio_set_value(priv->eesk, 0); | 
|  | udelay(EECK_EDGE_TIME); | 
|  |  | 
|  | if (cs) | 
|  | gpio_set_value(priv->eecs, 0); | 
|  | } | 
|  |  | 
|  | static void adm6996_gpio_read(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) | 
|  | { | 
|  | int i, len = (bits + 7) / 8; | 
|  | u8 mask; | 
|  |  | 
|  | gpio_set_value(priv->eecs, cs); | 
|  | udelay(EECK_EDGE_TIME); | 
|  |  | 
|  | /* Byte assemble from MSB to LSB */ | 
|  | for (i = 0; i < len; i++) { | 
|  | u8 byte; | 
|  |  | 
|  | /* Bit bang from MSB to LSB */ | 
|  | for (mask = 0x80, byte = 0; mask && bits > 0; mask >>= 1, bits --) { | 
|  | u8 gp; | 
|  |  | 
|  | /* Clock low */ | 
|  | gpio_set_value(priv->eesk, 0); | 
|  | udelay(EECK_EDGE_TIME); | 
|  |  | 
|  | /* Input on rising edge */ | 
|  | gp = gpio_get_value(priv->eedi); | 
|  | if (gp) | 
|  | byte |= mask; | 
|  |  | 
|  | /* Clock high */ | 
|  | gpio_set_value(priv->eesk, 1); | 
|  | udelay(EECK_EDGE_TIME); | 
|  | } | 
|  |  | 
|  | *buf++ = byte; | 
|  | } | 
|  |  | 
|  | /* Clock low */ | 
|  | gpio_set_value(priv->eesk, 0); | 
|  | udelay(EECK_EDGE_TIME); | 
|  |  | 
|  | if (cs) | 
|  | gpio_set_value(priv->eecs, 0); | 
|  | } | 
|  |  | 
|  | /* Advance clock(s) */ | 
|  | static void adm6996_gpio_adclk(struct adm6996_priv *priv, int clocks) | 
|  | { | 
|  | int i; | 
|  | for (i = 0; i < clocks; i++) { | 
|  | /* Clock high */ | 
|  | gpio_set_value(priv->eesk, 1); | 
|  | udelay(EECK_EDGE_TIME); | 
|  |  | 
|  | /* Clock low */ | 
|  | gpio_set_value(priv->eesk, 0); | 
|  | udelay(EECK_EDGE_TIME); | 
|  | } | 
|  | } | 
|  |  | 
|  | static u16 | 
|  | adm6996_read_gpio_reg(struct adm6996_priv *priv, enum admreg reg) | 
|  | { | 
|  | /* cmd: 01 10 T DD R RRRRRR */ | 
|  | u8 bits[6] = { | 
|  | 0xFF, 0xFF, 0xFF, 0xFF, | 
|  | (0x06 << 4) | ((0 & 0x01) << 3 | (reg&64)>>6), | 
|  | ((reg&63)<<2) | 
|  | }; | 
|  |  | 
|  | u8 rbits[4]; | 
|  |  | 
|  | /* Enable GPIO outputs with all pins to 0 */ | 
|  | gpio_direction_output(priv->eecs, 0); | 
|  | gpio_direction_output(priv->eesk, 0); | 
|  | gpio_direction_output(priv->eedi, 0); | 
|  |  | 
|  | adm6996_gpio_write(priv, 0, bits, 46); | 
|  | gpio_direction_input(priv->eedi); | 
|  | adm6996_gpio_adclk(priv, 2); | 
|  | adm6996_gpio_read(priv, 0, rbits, 32); | 
|  |  | 
|  | /* Extra clock(s) required per datasheet */ | 
|  | adm6996_gpio_adclk(priv, 2); | 
|  |  | 
|  | /* Disable GPIO outputs */ | 
|  | gpio_direction_input(priv->eecs); | 
|  | gpio_direction_input(priv->eesk); | 
|  |  | 
|  | /* EEPROM has 16-bit registers, but pumps out two registers in one request */ | 
|  | return (reg & 0x01 ?  (rbits[0]<<8) | rbits[1] : (rbits[2]<<8) | (rbits[3])); | 
|  | } | 
|  |  | 
|  | /* Write chip configuration register */ | 
|  | /* Follow 93c66 timing and chip's min EEPROM timing requirement */ | 
|  | static void | 
|  | adm6996_write_gpio_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) | 
|  | { | 
|  | /* cmd(27bits): sb(1) + opc(01) + addr(bbbbbbbb) + data(bbbbbbbbbbbbbbbb) */ | 
|  | u8 bits[4] = { | 
|  | (0x05 << 5) | (reg >> 3), | 
|  | (reg << 5) | (u8)(val >> 11), | 
|  | (u8)(val >> 3), | 
|  | (u8)(val << 5) | 
|  | }; | 
|  |  | 
|  | /* Enable GPIO outputs with all pins to 0 */ | 
|  | gpio_direction_output(priv->eecs, 0); | 
|  | gpio_direction_output(priv->eesk, 0); | 
|  | gpio_direction_output(priv->eedi, 0); | 
|  |  | 
|  | /* Write cmd. Total 27 bits */ | 
|  | adm6996_gpio_write(priv, 1, bits, 27); | 
|  |  | 
|  | /* Extra clock(s) required per datasheet */ | 
|  | adm6996_gpio_adclk(priv, 2); | 
|  |  | 
|  | /* Disable GPIO outputs */ | 
|  | gpio_direction_input(priv->eecs); | 
|  | gpio_direction_input(priv->eesk); | 
|  | gpio_direction_input(priv->eedi); | 
|  | } | 
|  |  | 
|  | static u16 | 
|  | adm6996_read_mii_reg(struct adm6996_priv *priv, enum admreg reg) | 
|  | { | 
|  | struct phy_device *phydev = priv->priv; | 
|  | struct mii_bus *bus = phydev->mdio.bus; | 
|  |  | 
|  | return bus->read(bus, PHYADDR(reg)); | 
|  | } | 
|  |  | 
|  | static void | 
|  | adm6996_write_mii_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) | 
|  | { | 
|  | struct phy_device *phydev = priv->priv; | 
|  | struct mii_bus *bus = phydev->mdio.bus; | 
|  |  | 
|  | bus->write(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, 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, 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); | 
|  |  | 
|  | pr_devel("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); | 
|  |  | 
|  | pr_devel("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); | 
|  |  | 
|  | pr_devel("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); | 
|  |  | 
|  | pr_devel("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; | 
|  |  | 
|  | pr_devel("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; | 
|  |  | 
|  | pr_devel("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); | 
|  | priv->tagged_ports |= (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, ADM_OTBE_P2_PVID); | 
|  | reg &= ~(ADM_OTBE_MASK); | 
|  | w16(priv, ADM_OTBE_P2_PVID, reg); | 
|  | reg = r16(priv, ADM_IFNTE); | 
|  | reg &= ~(ADM_IFNTE_MASK); | 
|  | w16(priv, ADM_IFNTE, reg); | 
|  | reg = r16(priv, ADM_VID_CHECK); | 
|  | reg |= ADM_VID_CHECK_MASK; | 
|  | w16(priv, ADM_VID_CHECK, reg); | 
|  | reg = r16(priv, ADM_SYSC0); | 
|  | reg |= ADM_NTTE; | 
|  | reg &= ~(ADM_RVID1); | 
|  | w16(priv, ADM_SYSC0, reg); | 
|  | reg = r16(priv, ADM_SYSC3); | 
|  | reg |= ADM_TBV; | 
|  | w16(priv, ADM_SYSC3, reg); | 
|  | } | 
|  |  | 
|  | static void | 
|  | adm6996_enable_vlan_6996l(struct adm6996_priv *priv) | 
|  | { | 
|  | u16 reg; | 
|  |  | 
|  | reg = r16(priv, ADM_SYSC3); | 
|  | reg |= ADM_TBV; | 
|  | reg |= ADM_MAC_CLONE; | 
|  | w16(priv, 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_VLANS; i++) { | 
|  | reg = ADM_VLAN_FILT_MEMBER_MASK; | 
|  | w16(priv, ADM_VLAN_FILT_L(i), reg); | 
|  | reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1); | 
|  | w16(priv, ADM_VLAN_FILT_H(i), reg); | 
|  | } | 
|  |  | 
|  | reg = r16(priv, ADM_OTBE_P2_PVID); | 
|  | reg |= ADM_OTBE_MASK; | 
|  | w16(priv, ADM_OTBE_P2_PVID, reg); | 
|  | reg = r16(priv, ADM_IFNTE); | 
|  | reg |= ADM_IFNTE_MASK; | 
|  | w16(priv, ADM_IFNTE, reg); | 
|  | reg = r16(priv, ADM_VID_CHECK); | 
|  | reg &= ~(ADM_VID_CHECK_MASK); | 
|  | w16(priv, ADM_VID_CHECK, reg); | 
|  | reg = r16(priv, ADM_SYSC0); | 
|  | reg &= ~(ADM_NTTE); | 
|  | reg |= ADM_RVID1; | 
|  | w16(priv, ADM_SYSC0, reg); | 
|  | reg = r16(priv, ADM_SYSC3); | 
|  | reg &= ~(ADM_TBV); | 
|  | w16(priv, 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_6996l(struct adm6996_priv *priv) | 
|  | { | 
|  | u16 reg; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < ADM_NUM_VLANS; i++) { | 
|  | w16(priv, ADM_VLAN_MAP(i), 0); | 
|  | } | 
|  |  | 
|  | reg = r16(priv, ADM_SYSC3); | 
|  | reg &= ~(ADM_TBV); | 
|  | reg &= ~(ADM_MAC_CLONE); | 
|  | w16(priv, 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, adm_portcfg[i]); | 
|  | reg &= ~(ADM_PORTCFG_PVID_MASK); | 
|  | reg |= ADM_PORTCFG_PVID(priv->pvid[i]); | 
|  | if (priv->model == ADM6996L) { | 
|  | if (priv->tagged_ports & (1 << i)) | 
|  | reg |= (1 << 4); | 
|  | else | 
|  | reg &= ~(1 << 4); | 
|  | } | 
|  | w16(priv, adm_portcfg[i], reg); | 
|  | } | 
|  |  | 
|  | w16(priv, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0])); | 
|  | w16(priv, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1])); | 
|  | reg = r16(priv, ADM_OTBE_P2_PVID); | 
|  | reg &= ~(ADM_P2_PVID_MASK); | 
|  | reg |= ADM_P2_PVID_VAL(priv->pvid[2]); | 
|  | w16(priv, ADM_OTBE_P2_PVID, reg); | 
|  | reg = ADM_P3_PVID_VAL(priv->pvid[3]); | 
|  | reg |= ADM_P4_PVID_VAL(priv->pvid[4]); | 
|  | w16(priv, ADM_P3_P4_PVID, reg); | 
|  | reg = r16(priv, ADM_P5_PVID); | 
|  | reg &= ~(ADM_P2_PVID_MASK); | 
|  | reg |= ADM_P5_PVID_VAL(priv->pvid[5]); | 
|  | w16(priv, ADM_P5_PVID, reg); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * 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, ADM_VLAN_FILT_H(i), 0); | 
|  | w16(priv, ADM_VLAN_FILT_L(i), 0); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | reg = ADM_VLAN_FILT_MEMBER(ports); | 
|  | reg |= ADM_VLAN_FILT_TAGGED(tagged); | 
|  | w16(priv, ADM_VLAN_FILT_L(i), reg); | 
|  | reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid); | 
|  | w16(priv, ADM_VLAN_FILT_H(i), reg); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | adm6996_apply_vlan_filters_6996l(struct adm6996_priv *priv) | 
|  | { | 
|  | u8 ports; | 
|  | u16 reg; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < ADM_NUM_VLANS; i++) { | 
|  | ports = priv->vlan_table[i]; | 
|  |  | 
|  | if (ports == 0) { | 
|  | /* Disable VLAN entry */ | 
|  | w16(priv, ADM_VLAN_MAP(i), 0); | 
|  | continue; | 
|  | } else { | 
|  | reg = ADM_VLAN_FILT(ports); | 
|  | w16(priv, ADM_VLAN_MAP(i), reg); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static int | 
|  | adm6996_hw_apply(struct switch_dev *dev) | 
|  | { | 
|  | struct adm6996_priv *priv = to_adm(dev); | 
|  |  | 
|  | pr_devel("hw_apply\n"); | 
|  |  | 
|  | mutex_lock(&priv->reg_mutex); | 
|  |  | 
|  | if (!priv->enable_vlan) { | 
|  | if (priv->vlan_enabled) { | 
|  | if (priv->model == ADM6996L) | 
|  | adm6996_disable_vlan_6996l(priv); | 
|  | else | 
|  | adm6996_disable_vlan(priv); | 
|  | priv->vlan_enabled = 0; | 
|  | } | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (!priv->vlan_enabled) { | 
|  | if (priv->model == ADM6996L) | 
|  | adm6996_enable_vlan_6996l(priv); | 
|  | else | 
|  | adm6996_enable_vlan(priv); | 
|  | priv->vlan_enabled = 1; | 
|  | } | 
|  |  | 
|  | adm6996_apply_port_pvids(priv); | 
|  | if (priv->model == ADM6996L) | 
|  | adm6996_apply_vlan_filters_6996l(priv); | 
|  | else | 
|  | 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, adm_portcfg[i], ADM_PORTCFG_INIT | | 
|  | ADM_PORTCFG_PVID(0)); | 
|  | } | 
|  | w16(priv, adm_portcfg[5], ADM_PORTCFG_CPU); | 
|  |  | 
|  | if (priv->model == ADM6996M || priv->model == ADM6996FC) { | 
|  | /* reset all PHY ports */ | 
|  | for (i = 0; i < ADM_PHY_PORTS; i++) { | 
|  | w16(priv, 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, ADM_VLAN_PRIOMAP, 0); | 
|  |  | 
|  | adm6996_disable_vlan(priv); | 
|  | adm6996_apply_port_pvids(priv); | 
|  | } else if (priv->model == ADM6996L) { | 
|  | /* Clear VLAN priority map so prio's are unused */ | 
|  | w16 (priv, ADM_VLAN_PRIOMAP, 0); | 
|  |  | 
|  | adm6996_disable_vlan_6996l(priv); | 
|  | adm6996_apply_port_pvids(priv); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int | 
|  | adm6996_reset_switch(struct switch_dev *dev) | 
|  | { | 
|  | struct adm6996_priv *priv = to_adm(dev); | 
|  |  | 
|  | pr_devel("reset\n"); | 
|  |  | 
|  | mutex_lock(&priv->reg_mutex); | 
|  | adm6996_perform_reset (priv); | 
|  | mutex_unlock(&priv->reg_mutex); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | adm6996_get_port_link(struct switch_dev *dev, int port, | 
|  | struct switch_port_link *link) | 
|  | { | 
|  | struct adm6996_priv *priv = to_adm(dev); | 
|  |  | 
|  | u16 reg = 0; | 
|  |  | 
|  | if (port >= ADM_NUM_PORTS) | 
|  | return -EINVAL; | 
|  |  | 
|  | switch (port) { | 
|  | case 0: | 
|  | reg = r16(priv, ADM_PS0); | 
|  | break; | 
|  | case 1: | 
|  | reg = r16(priv, ADM_PS0); | 
|  | reg = reg >> 8; | 
|  | break; | 
|  | case 2: | 
|  | reg = r16(priv, ADM_PS1); | 
|  | break; | 
|  | case 3: | 
|  | reg = r16(priv, ADM_PS1); | 
|  | reg = reg >> 8; | 
|  | break; | 
|  | case 4: | 
|  | reg = r16(priv, ADM_PS1); | 
|  | reg = reg >> 12; | 
|  | break; | 
|  | case 5: | 
|  | reg = r16(priv, ADM_PS2); | 
|  | /* Bits 0, 1, 3 and 4. */ | 
|  | reg = (reg & 3) | ((reg & 24) >> 1); | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | link->link = reg & ADM_PS_LS; | 
|  | if (!link->link) | 
|  | return 0; | 
|  | link->aneg = true; | 
|  | link->duplex = reg & ADM_PS_DS; | 
|  | link->tx_flow = reg & ADM_PS_FCS; | 
|  | link->rx_flow = reg & ADM_PS_FCS; | 
|  | if (reg & ADM_PS_SS) | 
|  | link->speed = SWITCH_PORT_SPEED_100; | 
|  | else | 
|  | link->speed = SWITCH_PORT_SPEED_10; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | adm6996_sw_get_port_mib(struct switch_dev *dev, | 
|  | const struct switch_attr *attr, | 
|  | struct switch_val *val) | 
|  | { | 
|  | struct adm6996_priv *priv = to_adm(dev); | 
|  | int port; | 
|  | char *buf = priv->buf; | 
|  | int i, len = 0; | 
|  | u32 reg = 0; | 
|  |  | 
|  | port = val->port_vlan; | 
|  | if (port >= ADM_NUM_PORTS) | 
|  | return -EINVAL; | 
|  |  | 
|  | mutex_lock(&priv->mib_lock); | 
|  |  | 
|  | len += snprintf(buf + len, sizeof(priv->buf) - len, | 
|  | "Port %d MIB counters\n", | 
|  | port); | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(adm6996_mibs); i++) { | 
|  | reg = r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port)); | 
|  | reg += r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port) + 1) << 16; | 
|  | len += snprintf(buf + len, sizeof(priv->buf) - len, | 
|  | "%-12s: %u\n", | 
|  | adm6996_mibs[i].name, | 
|  | reg); | 
|  | } | 
|  |  | 
|  | mutex_unlock(&priv->mib_lock); | 
|  |  | 
|  | val->value.s = buf; | 
|  | val->len = len; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | adm6996_get_port_stats(struct switch_dev *dev, int port, | 
|  | struct switch_port_stats *stats) | 
|  | { | 
|  | struct adm6996_priv *priv = to_adm(dev); | 
|  | int id; | 
|  | u32 reg = 0; | 
|  |  | 
|  | if (port >= ADM_NUM_PORTS) | 
|  | return -EINVAL; | 
|  |  | 
|  | mutex_lock(&priv->mib_lock); | 
|  |  | 
|  | id = ADM6996_MIB_TXB_ID; | 
|  | reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); | 
|  | reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; | 
|  | stats->tx_bytes = reg; | 
|  |  | 
|  | id = ADM6996_MIB_RXB_ID; | 
|  | reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); | 
|  | reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; | 
|  | stats->rx_bytes = reg; | 
|  |  | 
|  | mutex_unlock(&priv->mib_lock); | 
|  |  | 
|  | 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[] = { | 
|  | { | 
|  | .type = SWITCH_TYPE_STRING, | 
|  | .name = "mib", | 
|  | .description = "Get port's MIB counters", | 
|  | .set = NULL, | 
|  | .get = adm6996_sw_get_port_mib, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static struct switch_attr adm6996_vlan[] = { | 
|  | { | 
|  | .type = SWITCH_TYPE_INT, | 
|  | .name = "vid", | 
|  | .description = "VLAN ID", | 
|  | .set = adm6996_set_vid, | 
|  | .get = adm6996_get_vid, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static 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, | 
|  | .get_port_link = adm6996_get_port_link, | 
|  | .get_port_stats = adm6996_get_port_stats, | 
|  | }; | 
|  |  | 
|  | static int adm6996_switch_init(struct adm6996_priv *priv, const char *alias, struct net_device *netdev) | 
|  | { | 
|  | struct switch_dev *swdev; | 
|  | u16 test, old; | 
|  |  | 
|  | if (!priv->model) { | 
|  | /* Detect type of chip */ | 
|  | old = r16(priv, ADM_VID_CHECK); | 
|  | test = old ^ (1 << 12); | 
|  | w16(priv, ADM_VID_CHECK, test); | 
|  | test ^= r16(priv, 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(priv, 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; | 
|  | swdev->alias = alias; | 
|  |  | 
|  | /* The ADM6996L connected through GPIOs does not support any switch | 
|  | status calls */ | 
|  | if (priv->model == ADM6996L) { | 
|  | adm6996_ops.attr_port.n_attr = 0; | 
|  | adm6996_ops.get_port_link = NULL; | 
|  | } | 
|  |  | 
|  | pr_info ("%s: %s model PHY found.\n", alias, swdev->name); | 
|  |  | 
|  | mutex_lock(&priv->reg_mutex); | 
|  | adm6996_perform_reset (priv); | 
|  | mutex_unlock(&priv->reg_mutex); | 
|  |  | 
|  | if (priv->model == ADM6996M || priv->model == ADM6996L) { | 
|  | return register_switch(swdev, netdev); | 
|  | } | 
|  |  | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | static int adm6996_config_init(struct phy_device *pdev) | 
|  | { | 
|  | struct adm6996_priv *priv; | 
|  | int ret; | 
|  |  | 
|  | linkmode_zero(pdev->supported); | 
|  | linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, pdev->supported); | 
|  | linkmode_copy(pdev->advertising, pdev->supported); | 
|  |  | 
|  | if (pdev->mdio.addr != 0) { | 
|  | pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n" | 
|  | , pdev->attached_dev->name, pdev->mdio.addr); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | priv = devm_kzalloc(&pdev->mdio.dev, sizeof(struct adm6996_priv), GFP_KERNEL); | 
|  | if (!priv) | 
|  | return -ENOMEM; | 
|  |  | 
|  | mutex_init(&priv->reg_mutex); | 
|  | mutex_init(&priv->mib_lock); | 
|  | priv->priv = pdev; | 
|  | priv->read = adm6996_read_mii_reg; | 
|  | priv->write = adm6996_write_mii_reg; | 
|  |  | 
|  | ret = adm6996_switch_init(priv, pdev->attached_dev->name, pdev->attached_dev); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | pdev->priv = priv; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Warning: phydev->priv is NULL if phydev->mdio.addr != 0 | 
|  | */ | 
|  | static int adm6996_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; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Warning: phydev->priv is NULL if phydev->mdio.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->mdio.bus; | 
|  | u16 reg; | 
|  |  | 
|  | /* Our custom registers are at PHY addresses 0-10. Claim those. */ | 
|  | if (dev->mdio.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 && (priv->model == ADM6996M || priv->model == ADM6996L)) | 
|  | unregister_switch(&priv->dev); | 
|  | } | 
|  |  | 
|  | static int adm6996_soft_reset(struct phy_device *phydev) | 
|  | { | 
|  | /* we don't need an extra reset */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct phy_driver adm6996_phy_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, | 
|  | .soft_reset	= adm6996_soft_reset, | 
|  | }; | 
|  |  | 
|  | static int adm6996_gpio_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct adm6996_gpio_platform_data *pdata = pdev->dev.platform_data; | 
|  | struct adm6996_priv *priv; | 
|  | int ret; | 
|  |  | 
|  | if (!pdata) | 
|  | return -EINVAL; | 
|  |  | 
|  | priv = devm_kzalloc(&pdev->dev, sizeof(struct adm6996_priv), GFP_KERNEL); | 
|  | if (!priv) | 
|  | return -ENOMEM; | 
|  |  | 
|  | mutex_init(&priv->reg_mutex); | 
|  | mutex_init(&priv->mib_lock); | 
|  |  | 
|  | priv->eecs = pdata->eecs; | 
|  | priv->eedi = pdata->eedi; | 
|  | priv->eesk = pdata->eesk; | 
|  |  | 
|  | priv->model = pdata->model; | 
|  | priv->read = adm6996_read_gpio_reg; | 
|  | priv->write = adm6996_write_gpio_reg; | 
|  |  | 
|  | ret = devm_gpio_request(&pdev->dev, priv->eecs, "adm_eecs"); | 
|  | if (ret) | 
|  | return ret; | 
|  | ret = devm_gpio_request(&pdev->dev, priv->eedi, "adm_eedi"); | 
|  | if (ret) | 
|  | return ret; | 
|  | ret = devm_gpio_request(&pdev->dev, priv->eesk, "adm_eesk"); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = adm6996_switch_init(priv, dev_name(&pdev->dev), NULL); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | platform_set_drvdata(pdev, priv); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int adm6996_gpio_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct adm6996_priv *priv = platform_get_drvdata(pdev); | 
|  |  | 
|  | if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) | 
|  | unregister_switch(&priv->dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct platform_driver adm6996_gpio_driver = { | 
|  | .probe = adm6996_gpio_probe, | 
|  | .remove = adm6996_gpio_remove, | 
|  | .driver = { | 
|  | .name = "adm6996_gpio", | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static int __init adm6996_init(void) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | phy_register_fixup_for_id(PHY_ANY_ID, adm6996_fixup); | 
|  | err = phy_driver_register(&adm6996_phy_driver, THIS_MODULE); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | err = platform_driver_register(&adm6996_gpio_driver); | 
|  | if (err) | 
|  | phy_driver_unregister(&adm6996_phy_driver); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static void __exit adm6996_exit(void) | 
|  | { | 
|  | platform_driver_unregister(&adm6996_gpio_driver); | 
|  | phy_driver_unregister(&adm6996_phy_driver); | 
|  | } | 
|  |  | 
|  | module_init(adm6996_init); | 
|  | module_exit(adm6996_exit); |