ax88796: use generic mdio_bitbang driver
..instead of using hand-crafted and not proper working version. Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
This commit is contained in:
parent
c9218c3a82
commit
f6d7f2c60d
|
@ -238,8 +238,8 @@ source "drivers/net/arm/Kconfig"
|
|||
config AX88796
|
||||
tristate "ASIX AX88796 NE2000 clone support"
|
||||
depends on ARM || MIPS || SUPERH
|
||||
select CRC32
|
||||
select MII
|
||||
select PHYLIB
|
||||
select MDIO_BITBANG
|
||||
help
|
||||
AX88796 driver, using platform bus to provide
|
||||
chip detection and resources
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
#include <linux/netdevice.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/ethtool.h>
|
||||
#include <linux/mii.h>
|
||||
#include <linux/mdio-bitbang.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/eeprom_93cx6.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
|
@ -32,8 +33,6 @@
|
|||
|
||||
#include <asm/system.h>
|
||||
|
||||
static int phy_debug;
|
||||
|
||||
/* Rename the lib8390.c functions to show that they are in this driver */
|
||||
#define __ei_open ax_ei_open
|
||||
#define __ei_close ax_ei_close
|
||||
|
@ -78,14 +77,20 @@ static unsigned char version[] = "ax88796.c: Copyright 2005,2007 Simtec Electron
|
|||
#define NESM_START_PG 0x40 /* First page of TX buffer */
|
||||
#define NESM_STOP_PG 0x80 /* Last page +1 of RX ring */
|
||||
|
||||
#define AX_GPOC_PPDSET BIT(6)
|
||||
|
||||
/* device private data */
|
||||
|
||||
struct ax_device {
|
||||
struct timer_list mii_timer;
|
||||
spinlock_t mii_lock;
|
||||
struct mii_if_info mii;
|
||||
struct mii_bus *mii_bus;
|
||||
struct mdiobb_ctrl bb_ctrl;
|
||||
struct phy_device *phy_dev;
|
||||
void __iomem *addr_memr;
|
||||
u8 reg_memr;
|
||||
int link;
|
||||
int speed;
|
||||
int duplex;
|
||||
|
||||
u32 msg_enable;
|
||||
void __iomem *map2;
|
||||
const struct ax_plat_data *plat;
|
||||
|
||||
|
@ -313,159 +318,84 @@ static void ax_block_output(struct net_device *dev, int count,
|
|||
#define AX_MEMR_EEO BIT(6)
|
||||
#define AX_MEMR_EECLK BIT(7)
|
||||
|
||||
/*
|
||||
* ax_mii_ei_outbits
|
||||
*
|
||||
* write the specified set of bits to the phy
|
||||
*/
|
||||
static void
|
||||
ax_mii_ei_outbits(struct net_device *dev, unsigned int bits, int len)
|
||||
static void ax_handle_link_change(struct net_device *dev)
|
||||
{
|
||||
struct ei_device *ei_local = netdev_priv(dev);
|
||||
void __iomem *memr_addr = (void __iomem *)dev->base_addr + AX_MEMR;
|
||||
unsigned int memr;
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
struct phy_device *phy_dev = ax->phy_dev;
|
||||
int status_change = 0;
|
||||
|
||||
/* clock low, data to output mode */
|
||||
memr = ei_inb(memr_addr);
|
||||
memr &= ~(AX_MEMR_MDC | AX_MEMR_MDIR);
|
||||
ei_outb(memr, memr_addr);
|
||||
if (phy_dev->link && ((ax->speed != phy_dev->speed) ||
|
||||
(ax->duplex != phy_dev->duplex))) {
|
||||
|
||||
for (len--; len >= 0; len--) {
|
||||
if (bits & (1 << len))
|
||||
memr |= AX_MEMR_MDO;
|
||||
else
|
||||
memr &= ~AX_MEMR_MDO;
|
||||
|
||||
ei_outb(memr, memr_addr);
|
||||
|
||||
/* clock high */
|
||||
|
||||
ei_outb(memr | AX_MEMR_MDC, memr_addr);
|
||||
udelay(1);
|
||||
|
||||
/* clock low */
|
||||
ei_outb(memr, memr_addr);
|
||||
ax->speed = phy_dev->speed;
|
||||
ax->duplex = phy_dev->duplex;
|
||||
status_change = 1;
|
||||
}
|
||||
|
||||
/* leaves the clock line low, mdir input */
|
||||
memr |= AX_MEMR_MDIR;
|
||||
ei_outb(memr, (void __iomem *)dev->base_addr + AX_MEMR);
|
||||
}
|
||||
if (phy_dev->link != ax->link) {
|
||||
if (!phy_dev->link) {
|
||||
ax->speed = 0;
|
||||
ax->duplex = -1;
|
||||
}
|
||||
ax->link = phy_dev->link;
|
||||
|
||||
/*
|
||||
* ax_phy_ei_inbits
|
||||
*
|
||||
* read a specified number of bits from the phy
|
||||
*/
|
||||
static unsigned int
|
||||
ax_phy_ei_inbits(struct net_device *dev, int no)
|
||||
{
|
||||
struct ei_device *ei_local = netdev_priv(dev);
|
||||
void __iomem *memr_addr = (void __iomem *)dev->base_addr + AX_MEMR;
|
||||
unsigned int memr;
|
||||
unsigned int result = 0;
|
||||
|
||||
/* clock low, data to input mode */
|
||||
memr = ei_inb(memr_addr);
|
||||
memr &= ~AX_MEMR_MDC;
|
||||
memr |= AX_MEMR_MDIR;
|
||||
ei_outb(memr, memr_addr);
|
||||
|
||||
for (no--; no >= 0; no--) {
|
||||
ei_outb(memr | AX_MEMR_MDC, memr_addr);
|
||||
|
||||
udelay(1);
|
||||
|
||||
if (ei_inb(memr_addr) & AX_MEMR_MDI)
|
||||
result |= (1 << no);
|
||||
|
||||
ei_outb(memr, memr_addr);
|
||||
status_change = 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
if (status_change)
|
||||
phy_print_status(phy_dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* ax_phy_issueaddr
|
||||
*
|
||||
* use the low level bit shifting routines to send the address
|
||||
* and command to the specified phy
|
||||
*/
|
||||
static void
|
||||
ax_phy_issueaddr(struct net_device *dev, int phy_addr, int reg, int opc)
|
||||
static int ax_mii_probe(struct net_device *dev)
|
||||
{
|
||||
if (phy_debug)
|
||||
netdev_dbg(dev, "%s: dev %p, %04x, %04x, %d\n",
|
||||
__func__, dev, phy_addr, reg, opc);
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
struct phy_device *phy_dev = NULL;
|
||||
int ret;
|
||||
|
||||
ax_mii_ei_outbits(dev, 0x3f, 6); /* pre-amble */
|
||||
ax_mii_ei_outbits(dev, 1, 2); /* frame-start */
|
||||
ax_mii_ei_outbits(dev, opc, 2); /* op code */
|
||||
ax_mii_ei_outbits(dev, phy_addr, 5); /* phy address */
|
||||
ax_mii_ei_outbits(dev, reg, 5); /* reg address */
|
||||
/* find the first phy */
|
||||
phy_dev = phy_find_first(ax->mii_bus);
|
||||
if (!phy_dev) {
|
||||
netdev_err(dev, "no PHY found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = phy_connect_direct(dev, phy_dev, ax_handle_link_change, 0,
|
||||
PHY_INTERFACE_MODE_MII);
|
||||
if (ret) {
|
||||
netdev_err(dev, "Could not attach to PHY\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* mask with MAC supported features */
|
||||
phy_dev->supported &= PHY_BASIC_FEATURES;
|
||||
phy_dev->advertising = phy_dev->supported;
|
||||
|
||||
ax->phy_dev = phy_dev;
|
||||
|
||||
netdev_info(dev, "PHY driver [%s] (mii_bus:phy_addr=%s, irq=%d)\n",
|
||||
phy_dev->drv->name, dev_name(&phy_dev->dev), phy_dev->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
ax_phy_read(struct net_device *dev, int phy_addr, int reg)
|
||||
static void ax_phy_switch(struct net_device *dev, int on)
|
||||
{
|
||||
struct ei_device *ei_local = netdev_priv(dev);
|
||||
unsigned long flags;
|
||||
unsigned int result;
|
||||
|
||||
spin_lock_irqsave(&ei_local->page_lock, flags);
|
||||
|
||||
ax_phy_issueaddr(dev, phy_addr, reg, 2);
|
||||
|
||||
result = ax_phy_ei_inbits(dev, 17);
|
||||
result &= ~(3 << 16);
|
||||
|
||||
spin_unlock_irqrestore(&ei_local->page_lock, flags);
|
||||
|
||||
if (phy_debug)
|
||||
netdev_dbg(dev, "%s: %04x.%04x => read %04x\n", __func__,
|
||||
phy_addr, reg, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
ax_phy_write(struct net_device *dev, int phy_addr, int reg, int value)
|
||||
{
|
||||
struct ei_device *ei = netdev_priv(dev);
|
||||
unsigned long flags;
|
||||
|
||||
netdev_dbg(dev, "%s: %p, %04x, %04x %04x\n",
|
||||
__func__, dev, phy_addr, reg, value);
|
||||
|
||||
spin_lock_irqsave(&ei->page_lock, flags);
|
||||
|
||||
ax_phy_issueaddr(dev, phy_addr, reg, 1);
|
||||
ax_mii_ei_outbits(dev, 2, 2); /* send TA */
|
||||
ax_mii_ei_outbits(dev, value, 16);
|
||||
|
||||
spin_unlock_irqrestore(&ei->page_lock, flags);
|
||||
}
|
||||
|
||||
static void ax_mii_expiry(unsigned long data)
|
||||
{
|
||||
struct net_device *dev = (struct net_device *)data;
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ax->mii_lock, flags);
|
||||
mii_check_media(&ax->mii, netif_msg_link(ax), 0);
|
||||
spin_unlock_irqrestore(&ax->mii_lock, flags);
|
||||
u8 reg_gpoc = ax->plat->gpoc_val;
|
||||
|
||||
if (ax->running) {
|
||||
ax->mii_timer.expires = jiffies + HZ*2;
|
||||
add_timer(&ax->mii_timer);
|
||||
}
|
||||
if (!!on)
|
||||
reg_gpoc &= ~AX_GPOC_PPDSET;
|
||||
else
|
||||
reg_gpoc |= AX_GPOC_PPDSET;
|
||||
|
||||
ei_outb(reg_gpoc, ei_local->mem + EI_SHIFT(0x17));
|
||||
}
|
||||
|
||||
static int ax_open(struct net_device *dev)
|
||||
{
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
struct ei_device *ei_local = netdev_priv(dev);
|
||||
int ret;
|
||||
|
||||
netdev_dbg(dev, "open\n");
|
||||
|
@ -473,50 +403,48 @@ static int ax_open(struct net_device *dev)
|
|||
ret = request_irq(dev->irq, ax_ei_interrupt, ax->irqflags,
|
||||
dev->name, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ax_ei_open(dev);
|
||||
if (ret) {
|
||||
free_irq(dev->irq, dev);
|
||||
return ret;
|
||||
}
|
||||
goto failed_request_irq;
|
||||
|
||||
/* turn the phy on (if turned off) */
|
||||
ax_phy_switch(dev, 1);
|
||||
|
||||
ret = ax_mii_probe(dev);
|
||||
if (ret)
|
||||
goto failed_mii_probe;
|
||||
phy_start(ax->phy_dev);
|
||||
|
||||
ret = ax_ei_open(dev);
|
||||
if (ret)
|
||||
goto failed_ax_ei_open;
|
||||
|
||||
ei_outb(ax->plat->gpoc_val, ei_local->mem + EI_SHIFT(0x17));
|
||||
ax->running = 1;
|
||||
|
||||
/* start the MII timer */
|
||||
|
||||
init_timer(&ax->mii_timer);
|
||||
|
||||
ax->mii_timer.expires = jiffies + 1;
|
||||
ax->mii_timer.data = (unsigned long) dev;
|
||||
ax->mii_timer.function = ax_mii_expiry;
|
||||
|
||||
add_timer(&ax->mii_timer);
|
||||
|
||||
return 0;
|
||||
|
||||
failed_ax_ei_open:
|
||||
phy_disconnect(ax->phy_dev);
|
||||
failed_mii_probe:
|
||||
ax_phy_switch(dev, 0);
|
||||
free_irq(dev->irq, dev);
|
||||
failed_request_irq:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ax_close(struct net_device *dev)
|
||||
{
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
struct ei_device *ei_local = netdev_priv(dev);
|
||||
|
||||
netdev_dbg(dev, "close\n");
|
||||
|
||||
/* turn the phy off */
|
||||
|
||||
ei_outb(ax->plat->gpoc_val | (1 << 6),
|
||||
ei_local->mem + EI_SHIFT(0x17));
|
||||
|
||||
ax->running = 0;
|
||||
wmb();
|
||||
|
||||
del_timer_sync(&ax->mii_timer);
|
||||
ax_ei_close(dev);
|
||||
|
||||
/* turn the phy off */
|
||||
ax_phy_switch(dev, 0);
|
||||
phy_disconnect(ax->phy_dev);
|
||||
|
||||
free_irq(dev->irq, dev);
|
||||
return 0;
|
||||
}
|
||||
|
@ -524,17 +452,15 @@ static int ax_close(struct net_device *dev)
|
|||
static int ax_ioctl(struct net_device *dev, struct ifreq *req, int cmd)
|
||||
{
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
struct phy_device *phy_dev = ax->phy_dev;
|
||||
|
||||
if (!netif_running(dev))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&ax->mii_lock, flags);
|
||||
rc = generic_mii_ioctl(&ax->mii, if_mii(req), cmd, NULL);
|
||||
spin_unlock_irqrestore(&ax->mii_lock, flags);
|
||||
if (!phy_dev)
|
||||
return -ENODEV;
|
||||
|
||||
return rc;
|
||||
return phy_mii_ioctl(phy_dev, req, cmd);
|
||||
}
|
||||
|
||||
/* ethtool ops */
|
||||
|
@ -552,46 +478,30 @@ static void ax_get_drvinfo(struct net_device *dev,
|
|||
static int ax_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
|
||||
{
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
unsigned long flags;
|
||||
struct phy_device *phy_dev = ax->phy_dev;
|
||||
|
||||
spin_lock_irqsave(&ax->mii_lock, flags);
|
||||
mii_ethtool_gset(&ax->mii, cmd);
|
||||
spin_unlock_irqrestore(&ax->mii_lock, flags);
|
||||
if (!phy_dev)
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
return phy_ethtool_gset(phy_dev, cmd);
|
||||
}
|
||||
|
||||
static int ax_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
|
||||
{
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
struct phy_device *phy_dev = ax->phy_dev;
|
||||
|
||||
spin_lock_irqsave(&ax->mii_lock, flags);
|
||||
rc = mii_ethtool_sset(&ax->mii, cmd);
|
||||
spin_unlock_irqrestore(&ax->mii_lock, flags);
|
||||
if (!phy_dev)
|
||||
return -ENODEV;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ax_nway_reset(struct net_device *dev)
|
||||
{
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
return mii_nway_restart(&ax->mii);
|
||||
}
|
||||
|
||||
static u32 ax_get_link(struct net_device *dev)
|
||||
{
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
return mii_link_ok(&ax->mii);
|
||||
return phy_ethtool_sset(phy_dev, cmd);
|
||||
}
|
||||
|
||||
static const struct ethtool_ops ax_ethtool_ops = {
|
||||
.get_drvinfo = ax_get_drvinfo,
|
||||
.get_settings = ax_get_settings,
|
||||
.set_settings = ax_set_settings,
|
||||
.nway_reset = ax_nway_reset,
|
||||
.get_link = ax_get_link,
|
||||
.get_link = ethtool_op_get_link,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_AX88796_93CX6
|
||||
|
@ -642,8 +552,102 @@ static const struct net_device_ops ax_netdev_ops = {
|
|||
#endif
|
||||
};
|
||||
|
||||
static void ax_bb_mdc(struct mdiobb_ctrl *ctrl, int level)
|
||||
{
|
||||
struct ax_device *ax = container_of(ctrl, struct ax_device, bb_ctrl);
|
||||
|
||||
if (level)
|
||||
ax->reg_memr |= AX_MEMR_MDC;
|
||||
else
|
||||
ax->reg_memr &= ~AX_MEMR_MDC;
|
||||
|
||||
ei_outb(ax->reg_memr, ax->addr_memr);
|
||||
}
|
||||
|
||||
static void ax_bb_dir(struct mdiobb_ctrl *ctrl, int output)
|
||||
{
|
||||
struct ax_device *ax = container_of(ctrl, struct ax_device, bb_ctrl);
|
||||
|
||||
if (output)
|
||||
ax->reg_memr &= ~AX_MEMR_MDIR;
|
||||
else
|
||||
ax->reg_memr |= AX_MEMR_MDIR;
|
||||
|
||||
ei_outb(ax->reg_memr, ax->addr_memr);
|
||||
}
|
||||
|
||||
static void ax_bb_set_data(struct mdiobb_ctrl *ctrl, int value)
|
||||
{
|
||||
struct ax_device *ax = container_of(ctrl, struct ax_device, bb_ctrl);
|
||||
|
||||
if (value)
|
||||
ax->reg_memr |= AX_MEMR_MDO;
|
||||
else
|
||||
ax->reg_memr &= ~AX_MEMR_MDO;
|
||||
|
||||
ei_outb(ax->reg_memr, ax->addr_memr);
|
||||
}
|
||||
|
||||
static int ax_bb_get_data(struct mdiobb_ctrl *ctrl)
|
||||
{
|
||||
struct ax_device *ax = container_of(ctrl, struct ax_device, bb_ctrl);
|
||||
int reg_memr = ei_inb(ax->addr_memr);
|
||||
|
||||
return reg_memr & AX_MEMR_MDI ? 1 : 0;
|
||||
}
|
||||
|
||||
static struct mdiobb_ops bb_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.set_mdc = ax_bb_mdc,
|
||||
.set_mdio_dir = ax_bb_dir,
|
||||
.set_mdio_data = ax_bb_set_data,
|
||||
.get_mdio_data = ax_bb_get_data,
|
||||
};
|
||||
|
||||
/* setup code */
|
||||
|
||||
static int ax_mii_init(struct net_device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev->dev.parent);
|
||||
struct ei_device *ei_local = netdev_priv(dev);
|
||||
struct ax_device *ax = to_ax_dev(dev);
|
||||
int err, i;
|
||||
|
||||
ax->bb_ctrl.ops = &bb_ops;
|
||||
ax->addr_memr = ei_local->mem + AX_MEMR;
|
||||
ax->mii_bus = alloc_mdio_bitbang(&ax->bb_ctrl);
|
||||
if (!ax->mii_bus) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ax->mii_bus->name = "ax88796_mii_bus";
|
||||
ax->mii_bus->parent = dev->dev.parent;
|
||||
snprintf(ax->mii_bus->id, MII_BUS_ID_SIZE, "%x", pdev->id);
|
||||
|
||||
ax->mii_bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL);
|
||||
if (!ax->mii_bus->irq) {
|
||||
err = -ENOMEM;
|
||||
goto out_free_mdio_bitbang;
|
||||
}
|
||||
|
||||
for (i = 0; i < PHY_MAX_ADDR; i++)
|
||||
ax->mii_bus->irq[i] = PHY_POLL;
|
||||
|
||||
err = mdiobus_register(ax->mii_bus);
|
||||
if (err)
|
||||
goto out_free_irq;
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_irq:
|
||||
kfree(ax->mii_bus->irq);
|
||||
out_free_mdio_bitbang:
|
||||
free_mdio_bitbang(ax->mii_bus);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void ax_initial_setup(struct net_device *dev, struct ei_device *ei_local)
|
||||
{
|
||||
void __iomem *ioaddr = ei_local->mem;
|
||||
|
@ -763,15 +767,9 @@ static int ax_init_dev(struct net_device *dev)
|
|||
dev->netdev_ops = &ax_netdev_ops;
|
||||
dev->ethtool_ops = &ax_ethtool_ops;
|
||||
|
||||
ax->msg_enable = NETIF_MSG_LINK;
|
||||
ax->mii.phy_id_mask = 0x1f;
|
||||
ax->mii.reg_num_mask = 0x1f;
|
||||
ax->mii.phy_id = 0x10; /* onboard phy */
|
||||
ax->mii.force_media = 0;
|
||||
ax->mii.full_duplex = 0;
|
||||
ax->mii.mdio_read = ax_phy_read;
|
||||
ax->mii.mdio_write = ax_phy_write;
|
||||
ax->mii.dev = dev;
|
||||
ret = ax_mii_init(dev);
|
||||
if (ret)
|
||||
goto out_irq;
|
||||
|
||||
ax_NS8390_init(dev, 0);
|
||||
|
||||
|
@ -842,8 +840,6 @@ static int ax_probe(struct platform_device *pdev)
|
|||
ei_local = netdev_priv(dev);
|
||||
ax = to_ax_dev(dev);
|
||||
|
||||
spin_lock_init(&ax->mii_lock);
|
||||
|
||||
ax->plat = pdev->dev.platform_data;
|
||||
platform_set_drvdata(pdev, dev);
|
||||
|
||||
|
|
Loading…
Reference in New Issue