openwrt/target/linux/generic/files/drivers/net/phy/adm6996.c
Hauke Mehrtens 7a82667372 kernel: adm6996: add support for ADM6996L and GPIO interface
This patch makes it possible to use adm6996.c on first generation
BCM47XX devices with ADM switches.
The GPIO bit banging protocol implementation was copied from the old
switch driver and adapted to this driver and changed to the mainline
kernel GPIO interface.
The ADM6996L is different from the ADM6996M which is supported, for
both specs are available in the Internet.

This was tested on a WRT54GS version 1.0, thank you Dirk Neukirchen for
the device.

Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>

SVN-Revision: 38698
2013-11-09 19:27:27 +00:00

1072 lines
23 KiB
C

/*
* ADM6996 switch driver
*
* swconfig interface based on ar8216.c
*
* Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
* VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com>
* Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de>
*
* 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 <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_priv {
struct switch_dev dev;
void *priv;
u8 eecs;
u8 eesk;
u8 eedi;
u8 eerc;
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 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)
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->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->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 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_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;
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;
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 = devm_kzalloc(&pdev->dev, sizeof(struct adm6996_priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
mutex_init(&priv->reg_mutex);
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->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 && (priv->model == ADM6996M || priv->model == ADM6996L))
unregister_switch(&priv->dev);
}
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,
.driver = { .owner = THIS_MODULE,},
};
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);
priv->eecs = pdata->eecs;
priv->eedi = pdata->eedi;
priv->eerc = pdata->eerc;
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->eerc, "adm_eerc");
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);
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);