/*
* Dave DNET Ethernet Controller driver
*
* Copyright (C) 2008 Dave S.r.l. <www.dave.eu>
* Copyright (C) 2009 Ilya Yanok, Emcraft Systems Ltd, <yanok@emcraft.com>
*
* 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 <linux/io.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/phy.h>
#include "dnet.h"
#undef DEBUG
/* function for reading internal MAC register */
static u16 dnet_readw_mac(struct dnet *bp, u16 reg)
{
u16 data_read;
/* issue a read */
dnet_writel(bp, reg, MACREG_ADDR);
/* since a read/write op to the MAC is very slow,
* we must wait before reading the data */
ndelay(500);
/* read data read from the MAC register */
data_read = dnet_readl(bp, MACREG_DATA);
/* all done */
return data_read;
}
/* function for writing internal MAC register */
static void dnet_writew_mac(struct dnet *bp, u16 reg, u16 val)
{
/* load data to write */
dnet_writel(bp, val, MACREG_DATA);
/* issue a write */
dnet_writel(bp, reg | DNET_INTERNAL_WRITE, MACREG_ADDR);
/* since a read/write op to the MAC is very slow,
* we must wait before exiting */
ndelay(500);
}
static void __dnet_set_hwaddr(struct dnet *bp)
{
u16 tmp;
tmp = be16_to_cpup((__be16 *)bp->dev->dev_addr);
dnet_writew_mac(bp, DNET_INTERNAL_MAC_ADDR_0_REG, tmp);
tmp = be16_to_cpup((__be16 *)(bp->dev->dev_addr + 2));
dnet_writew_mac(bp, DNET_INTERNAL_MAC_ADDR_1_REG, tmp);
tmp = be16_to_cpup((__be16 *)(bp->dev->dev_addr + 4));
dnet_writew_mac(bp, DNET_INTERNAL_MAC_ADDR_2_REG, tmp);
}
static void dnet_get_hwaddr(struct dnet *bp)
{
u16 tmp;
u8 addr[6];
/*
* from MAC docs:
* "Note that the MAC address is stored in the registers in Hexadecimal
* form. For example, to set the MAC Address to: AC-DE-48-00-00-80
* would require writing 0xAC (octet 0) to address 0x0B (high byte of
* Mac_addr[15:0]), 0xDE (octet 1) to address 0x0A (Low byte of
* Mac_addr[15:0]), 0x48 (octet 2) to address 0x0D (high byte of
* Mac_addr[15:0]), 0x00 (octet 3) to address 0x0C (Low byte of
* Mac_addr[15:0]), 0x00 (octet 4) to address 0x0F (high byte of
* Mac_addr[15:0]), and 0x80 (octet 5) to address * 0x0E (Low byte of
* Mac_addr[15:0]).
*/
tmp = dnet_readw_mac(bp, DNET_INTERNAL_MAC_ADDR_0_REG);
*((__be16 *)addr) = cpu_to_be16(tmp);
tmp = dnet_readw_mac(bp, DNET_INTERNAL_MAC_ADDR_1_REG);
*((__be16 *)(addr + 2)) = cpu_to_be16(tmp);
tmp = dnet_readw_mac(bp, DNET_INTERNAL_MAC_ADDR_2_REG);
*((__be16 *)(addr + 4)) = cpu_to_be16(tmp);
if (is_valid_ether_addr(addr))
memcpy(bp->dev->dev_addr, addr, sizeof(addr));
}
static int dnet_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
{
struct dnet *bp = bus->priv;
u16 value;
while (!(dnet_readw_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG)
& DNET_INTERNAL_GMII_MNG_CMD_FIN))
cpu_relax();
/* only 5 bits allowed for phy-addr and reg_offset */
mii_id &= 0x1f;
regnum &= 0x1f;
/* prepare reg_value for a read */
value = (mii_id << 8);
value |= regnum;
/* write control word */
dnet_writew_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG, value);
/* wait for end of transfer */
while (!(dnet_readw_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG)
& DNET_INTERNAL_GMII_MNG_CMD_FIN))
cpu_relax();
value = dnet_readw_mac(bp, DNET_INTERNAL_GMII_MNG_DAT_REG);
pr_debug("mdio_read %02x:%02x <- %04x\n", mii_id, regnum, value);
return value;
}
static int dnet_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
u16 value)
{
struct dnet *bp = bus->priv;
u16 tmp;
pr_debug("mdio_write %02x:%02x <- %04x\n", mii_id, regnum, value);
while (!(dnet_readw_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG)
& DNET_INTERNAL_GMII_MNG_CMD_FIN))
cpu_relax();
/* prepare for a write operation */
tmp = (1 << 13);
/* only 5 bits allowed for phy-addr and reg_offset */
mii_id &= 0x1f;
regnum &= 0x1f;
/* only 16 bits on data */
value &= 0xffff;
/* prepare reg_value for a write */
tmp |= (mii_id << 8);
tmp |= regnum;
/* write data to write first */
dnet_writew_mac(bp, DNET_INTERNAL_GMII_MNG_DAT_REG, value);
/* write control word */
dnet_writew_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG, tmp);
while (!(dnet_readw_mac(bp, DNET_INTERNAL_GMII_MNG_CTL_REG)
& DNET_INTERNAL_GMII_MNG_CMD_FIN))
cpu_relax();
return 0;
}
static void dnet_handle_link_change(struct net_device *dev)
{
struct dnet *bp = netdev_priv(dev);
struct phy_device *phydev = bp->phy_dev;
unsigned long flags;
u32 mode_reg, ctl_reg;
int status_change = 0;
spin_lock_irqsave(&bp->lock, flags);
mode_reg = dnet_readw_mac(bp, DNET_INTERNAL_MODE_REG);
ctl_reg = dnet_readw_mac(bp, DNET_INTERNAL_RXTX_CONTROL_REG);
if (phydev->link) {
if (bp->duplex != phydev->duplex) {
if (phydev->duplex)
ctl_reg &=
~(DNET_INTERNAL_RXTX_CONTROL_ENABLEHALFDUP);
else
ctl_reg |=
DNET_INTERNAL_RXTX_CONTROL_ENABLEHALFDUP;
bp->duplex = phydev->duplex;
status_change = 1;
}
if (bp->speed != phydev->speed) {
status_change = 1;
switch (phydev->speed) {
case 1000:
mode_reg |= DNET_INTERNAL_MODE_GBITEN;
break;
case 100:
case 10:
mode_reg &= ~DNET_INTERNAL_MODE_GBITEN;
break;
default:
printk(KERN_WARNING
"%s: Ack! Speed (%d) is not "
"10/100/1000!\n", dev->name,
phydev->speed);
break;
}
bp->speed = phydev->speed;
}
}
if (phydev->link != bp->link) {
if (phydev->link) {
mode_reg |=
(DNET_INTERNAL_MODE_RXEN | DNET_INTERNAL_MODE_TXEN);
} else {
mode_reg &=
~(DNET_INTERNAL_MODE_RXEN |
DNET_INTERNAL_MODE_TXEN);
bp->speed = 0;
bp->duplex = -1;
}
bp->link = phydev->link;
status_change = 1;
}
if (status_change) {
dnet_writew_mac(bp, DNET_INTERNAL_RXTX_CONTROL_REG, ctl_reg);
dnet_writew_mac(bp, DNET_INTERNAL_MODE_REG, mode_reg);
}
spin_unlock_irqrestore(&bp->lock, flags);
if (status_change) {
if (phydev->link)
printk(KERN_INFO "%s: link up (%d/%s)\n",
dev->name, phydev->speed,
DUPLEX_FULL == phydev->duplex ? "Full" : "Half");
else
printk(KERN_INFO "%s: link down\n", dev->name);
}
}
static int dnet_mii_probe(struct net_device *dev)
{
struct dnet *bp = netdev_priv(dev);
struct phy_device *phydev = NULL;
int phy_addr;
/* find the first phy */
for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) {
if (bp->mii_bus->phy_map[phy_addr]) {
phydev = bp->mii_bus->phy_map[phy_addr];
break;
}
}
if (!phydev) {
printk(KERN_ERR "%s: no PHY found\n", dev->name);
return -ENODEV;
}
/* TODO : add pin_irq */
/* attach the mac to the phy */
if (bp->capabilities & DNET_HAS_RMII) {
phydev = phy_connect(dev, dev_name(&phydev->dev),
&dnet_handle_link_change,
PHY_INTERFACE_MODE_RMII);
} else {
phydev = phy_connect(dev, dev_name(&phydev->dev),
&dnet_handle_link_change,
PHY_INTERFACE_MODE_MII);
}
if (IS_ERR(phydev)) {
printk(KERN_ERR "%s: Could not attach to PHY\n", dev->name);
return PTR_ERR(phydev);
}
/* mask with MAC supported features */
if (bp->capabilities & DNET_HAS_GIGABIT)
phydev->supported &= PHY_GBIT_FEATURES;
else
phydev->supported &= PHY_BASIC_FEATURES;
phydev->supported |= SUPPORTED_Asym_Pause | SUPPORTED_Pause;
phydev->advertising = phydev->supported;
bp->link = 0;
bp->speed = 0;
bp->duplex = -1;
bp->phy_dev = phydev;
return 0;
}
static int dnet_mii_init(struct dnet *bp)
{
int err, i;
bp->mii_bus = mdiobus_alloc();
if (bp->mii_bus == NULL)
return -ENOMEM;
bp->mii_bus->name = "dnet_mii_bus";
bp->mii_bus->read = &dnet_mdio_read;
bp->mii_bus->write = &dnet_mdi