9
0
Fork 0
barebox/drivers/net/smc911x.c

434 lines
9.9 KiB
C
Raw Normal View History

2007-09-11 08:16:37 +00:00
/*
* SMSC LAN9[12]1[567] Network driver
*
* (c) 2007 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
2007-10-18 19:02:02 +00:00
#ifdef CONFIG_ENABLE_DEVICE_NOISE
# define DEBUG
#endif
2007-09-11 08:16:37 +00:00
#include <common.h>
#include <command.h>
#include <net.h>
#include <miidev.h>
2007-09-11 08:16:37 +00:00
#include <malloc.h>
#include <init.h>
#include <xfuncs.h>
#include <errno.h>
#include <clock.h>
#include <io.h>
2007-09-11 08:16:37 +00:00
#define AS CONFIG_DRIVER_NET_SMC911X_ADDRESS_SHIFT
#include "smc911x.h"
2007-09-11 08:16:37 +00:00
struct smc911x_priv {
struct eth_device edev;
struct mii_device miidev;
void __iomem *base;
2007-09-11 08:16:37 +00:00
};
struct chip_id {
u16 id;
char *name;
};
static const struct chip_id chip_ids[] = {
{ CHIP_9115, "LAN9115" },
{ CHIP_9116, "LAN9116" },
{ CHIP_9117, "LAN9117" },
{ CHIP_9118, "LAN9118" },
{ CHIP_9215, "LAN9215" },
{ CHIP_9216, "LAN9216" },
{ CHIP_9217, "LAN9217" },
{ CHIP_9218, "LAN9218" },
{ CHIP_9221, "LAN9221" },
2007-09-11 08:16:37 +00:00
{ 0, NULL },
};
#define DRIVERNAME "smc911x"
static int smc911x_mac_wait_busy(struct smc911x_priv *priv)
{
uint64_t start = get_time_ns();
while (!is_timeout(start, MSECOND)) {
if (!(readl(priv->base + MAC_CSR_CMD) & MAC_CSR_CMD_CSR_BUSY))
return 0;
}
printf("%s: mac timeout\n", __FUNCTION__);
return -1;
}
static u32 smc911x_get_mac_csr(struct eth_device *edev, u8 reg)
{
struct smc911x_priv *priv = edev->priv;
ulong val;
smc911x_mac_wait_busy(priv);
writel(MAC_CSR_CMD_CSR_BUSY | MAC_CSR_CMD_R_NOT_W | reg,
priv->base + MAC_CSR_CMD);
2007-09-11 08:16:37 +00:00
smc911x_mac_wait_busy(priv);
val = readl(priv->base + MAC_CSR_DATA);
return val;
}
static void smc911x_set_mac_csr(struct eth_device *edev, u8 reg, u32 data)
{
struct smc911x_priv *priv = edev->priv;
smc911x_mac_wait_busy(priv);
writel(data, priv->base + MAC_CSR_DATA);
writel(MAC_CSR_CMD_CSR_BUSY | reg, priv->base + MAC_CSR_CMD);
smc911x_mac_wait_busy(priv);
}
static int smc911x_get_ethaddr(struct eth_device *edev, unsigned char *m)
2007-09-11 08:16:37 +00:00
{
unsigned long addrh, addrl;
addrh = smc911x_get_mac_csr(edev, ADDRH);
addrl = smc911x_get_mac_csr(edev, ADDRL);
m[0] = (addrl ) & 0xff;
m[1] = (addrl >> 8 ) & 0xff;
m[2] = (addrl >> 16 ) & 0xff;
m[3] = (addrl >> 24 ) & 0xff;
m[4] = (addrh ) & 0xff;
m[5] = (addrh >> 8 ) & 0xff;
/* we get 0xff when there is no eeprom connected */
if ((m[0] & m[1] & m[2] & m[3] & m[4] & m[5]) == 0xff)
2007-09-11 08:16:37 +00:00
return -1;
2007-09-11 08:16:37 +00:00
return 0;
}
static int smc911x_set_ethaddr(struct eth_device *edev, unsigned char *m)
2007-09-11 08:16:37 +00:00
{
unsigned long addrh, addrl;
addrl = m[0] | m[1] << 8 | m[2] << 16 | m[3] << 24;
addrh = m[4] | m[5] << 8;
smc911x_set_mac_csr(edev, ADDRH, addrh);
smc911x_set_mac_csr(edev, ADDRL, addrl);
return 0;
}
static int smc911x_phy_read(struct mii_device *mdev, int phy_addr, int reg)
2007-09-11 08:16:37 +00:00
{
struct eth_device *edev = mdev->edev;
while (smc911x_get_mac_csr(edev, MII_ACC) & MII_ACC_MII_BUSY);
smc911x_set_mac_csr(edev, MII_ACC, phy_addr << 11 | reg << 6 |
MII_ACC_MII_BUSY);
2007-09-11 08:16:37 +00:00
while (smc911x_get_mac_csr(edev, MII_ACC) & MII_ACC_MII_BUSY);
return smc911x_get_mac_csr(edev, MII_DATA);
2007-09-11 08:16:37 +00:00
}
static int smc911x_phy_write(struct mii_device *mdev, int phy_addr,
int reg, int val)
2007-09-11 08:16:37 +00:00
{
struct eth_device *edev = mdev->edev;
while (smc911x_get_mac_csr(edev, MII_ACC) & MII_ACC_MII_BUSY);
smc911x_set_mac_csr(edev, MII_DATA, val);
smc911x_set_mac_csr(edev, MII_ACC,
phy_addr << 11 | reg << 6 | MII_ACC_MII_BUSY |
MII_ACC_MII_WRITE);
2007-09-11 08:16:37 +00:00
while (smc911x_get_mac_csr(edev, MII_ACC) & MII_ACC_MII_BUSY);
return 0;
}
static int smc911x_phy_reset(struct eth_device *edev)
{
struct smc911x_priv *priv = edev->priv;
u32 reg;
reg = readl(priv->base + PMT_CTRL);
reg &= 0xfcf;
2007-09-11 08:16:37 +00:00
reg |= PMT_CTRL_PHY_RST;
writel(reg, priv->base + PMT_CTRL);
mdelay(100);
return 0;
}
static void smc911x_reset(struct eth_device *edev)
{
struct smc911x_priv *priv = edev->priv;
uint64_t start;
/* Take out of PM setting first */
if (readl(priv->base + PMT_CTRL) & PMT_CTRL_READY) {
/* Write to the bytetest will take out of powerdown */
writel(0, priv->base + BYTE_TEST);
start = get_time_ns();
while(1) {
if ((readl(priv->base + PMT_CTRL) & PMT_CTRL_READY))
break;
if (is_timeout(start, 100 * USECOND)) {
dev_err(&edev->dev,
"timeout waiting for PM restore\n");
2007-09-11 08:16:37 +00:00
return;
}
}
}
/* Disable interrupts */
writel(0, priv->base + INT_EN);
writel(HW_CFG_SRST, priv->base + HW_CFG);
start = get_time_ns();
while(1) {
if (!(readl(priv->base + E2P_CMD) & E2P_CMD_EPC_BUSY))
break;
if (is_timeout(start, 10 * MSECOND)) {
dev_err(&edev->dev, "reset timeout\n");
2007-09-11 08:16:37 +00:00
return;
}
}
/* Reset the FIFO level and flow control settings */
smc911x_set_mac_csr(edev, FLOW, FLOW_FCPT | FLOW_FCEN);
writel(0x0050287F, priv->base + AFC_CFG);
/* Set to LED outputs */
writel(0x70070000, priv->base + GPIO_CFG);
}
static void smc911x_enable(struct eth_device *edev)
{
struct smc911x_priv *priv = edev->priv;
/* Enable TX */
writel(8 << 16 | HW_CFG_SF, priv->base + HW_CFG);
writel(GPT_CFG_TIMER_EN | 10000, priv->base + GPT_CFG);
writel(TX_CFG_TX_ON, priv->base + TX_CFG);
/* no padding to start of packets */
writel(RX_CFG_RX_DUMP, priv->base + RX_CFG);
2007-09-11 08:16:37 +00:00
}
static int smc911x_eth_open(struct eth_device *edev)
{
struct smc911x_priv *priv = (struct smc911x_priv *)edev->priv;
miidev_wait_aneg(&priv->miidev);
miidev_print_status(&priv->miidev);
2007-09-11 08:16:37 +00:00
/* Turn on Tx + Rx */
smc911x_enable(edev);
return 0;
}
static int smc911x_eth_send(struct eth_device *edev, void *packet, int length)
{
struct smc911x_priv *priv = (struct smc911x_priv *)edev->priv;
u32 *data = (u32*)packet;
u32 tmplen;
u32 status;
uint64_t start;
2007-09-11 08:16:37 +00:00
writel(TX_CMD_A_INT_FIRST_SEG | TX_CMD_A_INT_LAST_SEG | length,
priv->base + TX_DATA_FIFO);
2007-09-11 08:16:37 +00:00
writel(length, priv->base + TX_DATA_FIFO);
tmplen = (length + 3) / 4;
while(tmplen--)
writel(*data++, priv->base + TX_DATA_FIFO);
/* wait for transmission */
start = get_time_ns();
while (1) {
if ((readl(priv->base + TX_FIFO_INF) &
TX_FIFO_INF_TSUSED) >> 16)
break;
if (is_timeout(start, 100 * MSECOND)) {
dev_err(&edev->dev, "TX timeout\n");
return -1;
}
}
2007-09-11 08:16:37 +00:00
/* get status. Ignore 'no carrier' error, it has no meaning for
* full duplex operation
*/
status = readl(priv->base + TX_STATUS_FIFO) & (TX_STS_LOC |
TX_STS_LATE_COLL | TX_STS_MANY_COLL | TX_STS_MANY_DEFER |
TX_STS_UNDERRUN);
2007-09-11 08:16:37 +00:00
if(!status)
return 0;
dev_err(&edev->dev, "failed to send packet: %s%s%s%s%s\n",
2007-09-11 08:16:37 +00:00
status & TX_STS_LOC ? "TX_STS_LOC " : "",
status & TX_STS_LATE_COLL ? "TX_STS_LATE_COLL " : "",
status & TX_STS_MANY_COLL ? "TX_STS_MANY_COLL " : "",
status & TX_STS_MANY_DEFER ? "TX_STS_MANY_DEFER " : "",
status & TX_STS_UNDERRUN ? "TX_STS_UNDERRUN" : "");
return -1;
}
static void smc911x_eth_halt(struct eth_device *edev)
{
struct smc911x_priv *priv = (struct smc911x_priv *)edev->priv;
/* Disable TX */
writel(TX_CFG_STOP_TX, priv->base + TX_CFG);
// smc911x_reset(edev);
2007-09-11 08:16:37 +00:00
}
static int smc911x_eth_rx(struct eth_device *edev)
{
struct smc911x_priv *priv = (struct smc911x_priv *)edev->priv;
u32 *data = (u32 *)NetRxPackets[0];
u32 pktlen, tmplen;
u32 status;
if((readl(priv->base + RX_FIFO_INF) & RX_FIFO_INF_RXSUSED) >> 16) {
status = readl(priv->base + RX_STATUS_FIFO);
pktlen = (status & RX_STS_PKT_LEN) >> 16;
writel(0, priv->base + RX_CFG);
tmplen = (pktlen + 2 + 3) / 4;
2007-09-11 08:16:37 +00:00
while(tmplen--)
*data++ = readl(priv->base + RX_DATA_FIFO);
if(status & RX_STS_ES)
dev_err(&edev->dev, "dropped bad packet. Status: 0x%08x\n",
2007-09-11 08:16:37 +00:00
status);
else
net_receive(NetRxPackets[0], pktlen);
2007-09-11 08:16:37 +00:00
}
return 0;
}
static int smc911x_init_dev(struct eth_device *edev)
{
struct smc911x_priv *priv = (struct smc911x_priv *)edev->priv;
smc911x_set_mac_csr(edev, MAC_CR, MAC_CR_TXEN | MAC_CR_RXEN |
MAC_CR_HBDIS);
miidev_restart_aneg(&priv->miidev);
2007-09-11 08:16:37 +00:00
return 0;
}
static int smc911x_probe(struct device_d *dev)
{
struct eth_device *edev;
struct smc911x_priv *priv;
uint32_t val;
2007-09-11 08:16:37 +00:00
int i;
void __iomem *base;
2007-09-11 08:16:37 +00:00
base = dev_request_mem_region(dev, 0);
val = readl(base + BYTE_TEST);
2007-09-11 08:16:37 +00:00
if(val != 0x87654321) {
dev_err(dev, "no smc911x found on 0x%p (byte_test=0x%08x)\n",
base, val);
2007-09-11 08:16:37 +00:00
return -ENODEV;
}
val = readl(base + ID_REV) >> 16;
2007-09-11 08:16:37 +00:00
for(i = 0; chip_ids[i].id != 0; i++) {
if (chip_ids[i].id == val) break;
}
if (!chip_ids[i].id) {
dev_err(dev, "Unknown chip ID %04x\n", val);
2007-09-11 08:16:37 +00:00
return -ENODEV;
}
dev_info(dev, "detected %s controller\n", chip_ids[i].name);
2007-09-11 08:16:37 +00:00
priv = xzalloc(sizeof(*priv));
edev = &priv->edev;
edev->priv = priv;
2007-09-11 08:16:37 +00:00
edev->init = smc911x_init_dev;
edev->open = smc911x_eth_open;
edev->send = smc911x_eth_send;
edev->recv = smc911x_eth_rx;
edev->halt = smc911x_eth_halt;
edev->get_ethaddr = smc911x_get_ethaddr;
edev->set_ethaddr = smc911x_set_ethaddr;
edev->parent = dev;
2007-09-11 08:16:37 +00:00
priv->miidev.read = smc911x_phy_read;
priv->miidev.write = smc911x_phy_write;
priv->miidev.address = 1;
priv->miidev.flags = 0;
priv->miidev.edev = edev;
priv->miidev.parent = dev;
priv->base = base;
2007-09-11 08:16:37 +00:00
smc911x_reset(edev);
smc911x_phy_reset(edev);
mii_register(&priv->miidev);
2007-09-11 08:16:37 +00:00
eth_register(edev);
return 0;
}
static struct driver_d smc911x_driver = {
.name = "smc911x",
.probe = smc911x_probe,
};
static int smc911x_init(void)
{
register_driver(&smc911x_driver);
return 0;
}
device_initcall(smc911x_init);