diff options
author | Oleksij Rempel <linux@rempel-privat.de> | 2013-05-30 20:18:41 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2013-05-31 08:56:33 +0200 |
commit | 7d27f0db932d2a584409a3c5c85a229638597e65 (patch) | |
tree | a9629537504af40c55b476f4c9146f06550ffd7a /drivers/net/ar231x.c | |
parent | 59fe549733fb21f1e669bea148dc8c7899890844 (diff) | |
download | barebox-7d27f0db932d2a584409a3c5c85a229638597e65.tar.gz barebox-7d27f0db932d2a584409a3c5c85a229638597e65.tar.xz |
net: add ar231x-eth support
This driver should work with some Atheros WiSoCs:
- ar2312, ar2313
- ar2315, ar2316 ...
Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/net/ar231x.c')
-rw-r--r-- | drivers/net/ar231x.c | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/drivers/net/ar231x.c b/drivers/net/ar231x.c new file mode 100644 index 0000000000..5c091140ac --- /dev/null +++ b/drivers/net/ar231x.c @@ -0,0 +1,437 @@ +/* + * ar231x.c: driver for the Atheros AR231x Ethernet device. + * This device is build in to SoC on ar231x series. + * All known of them are big endian. + * + * Based on Linux driver: + * Copyright (C) 2004 by Sameer Dekate <sdekate@arubanetworks.com> + * Copyright (C) 2006 Imre Kaloz <kaloz@openwrt.org> + * Copyright (C) 2006-2009 Felix Fietkau <nbd@openwrt.org> + * Ported to Barebox: + * Copyright (C) 2013 Oleksij Rempel <linux@rempel-privat.de> + * + * 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. + */ +/* + * Known issues: + * - broadcast packets are not filtered by hardware. On noisy network with + * lots of bcast packages rx_buffer can be completely filled after. Currently + * we clear rx_buffer transmit some package. + */ + +#include <common.h> +#include <net.h> +#include <init.h> +#include <io.h> + +#include "ar231x.h" + +static inline void dma_writel(struct ar231x_eth_priv *priv, + u32 val, int reg) +{ + __raw_writel(val, priv->dma_regs + reg); +} + +static inline u32 dma_readl(struct ar231x_eth_priv *priv, int reg) +{ + return __raw_readl(priv->dma_regs + reg); +} + +static inline void eth_writel(struct ar231x_eth_priv *priv, + u32 val, int reg) +{ + __raw_writel(val, priv->eth_regs + reg); +} + +static inline u32 eth_readl(struct ar231x_eth_priv *priv, int reg) +{ + return __raw_readl(priv->eth_regs + reg); +} + +static inline void phy_writel(struct ar231x_eth_priv *priv, + u32 val, int reg) +{ + __raw_writel(val, priv->phy_regs + reg); +} + +static inline u32 phy_readl(struct ar231x_eth_priv *priv, int reg) +{ + return __raw_readl(priv->phy_regs + reg); +} + +static void ar231x_reset_bit_(struct ar231x_eth_priv *priv, + u32 val, enum reset_state state) +{ + if (priv->reset_bit) + (*priv->reset_bit)(val, state); +} + +static int ar231x_set_ethaddr(struct eth_device *edev, unsigned char *addr); +static void ar231x_reset_regs(struct eth_device *edev) +{ + struct ar231x_eth_priv *priv = edev->priv; + struct ar231x_eth_platform_data *cfg = priv->cfg; + u32 flags; + + ar231x_reset_bit_(priv, cfg->reset_mac, SET); + mdelay(10); + + ar231x_reset_bit_(priv, cfg->reset_mac, REMOVE); + mdelay(10); + + ar231x_reset_bit_(priv, cfg->reset_phy, SET); + mdelay(10); + + ar231x_reset_bit_(priv, cfg->reset_phy, REMOVE); + mdelay(10); + + dma_writel(priv, DMA_BUS_MODE_SWR, AR231X_DMA_BUS_MODE); + mdelay(10); + + dma_writel(priv, ((32 << DMA_BUS_MODE_PBL_SHIFT) | DMA_BUS_MODE_BLE), + AR231X_DMA_BUS_MODE); + + /* FIXME: priv->{t,r}x_ring are virtual addresses, + * use virt-to-phys convertion */ + dma_writel(priv, (u32)priv->tx_ring, AR231X_DMA_TX_RING); + dma_writel(priv, (u32)priv->rx_ring, AR231X_DMA_RX_RING); + + dma_writel(priv, (DMA_CONTROL_SR | DMA_CONTROL_ST | DMA_CONTROL_SF), + AR231X_DMA_CONTROL); + + eth_writel(priv, FLOW_CONTROL_FCE, AR231X_ETH_FLOW_CONTROL); + /* TODO: not sure if we need it here. */ + eth_writel(priv, 0x8100, AR231X_ETH_VLAN_TAG); + + /* Enable Ethernet Interface */ + flags = (MAC_CONTROL_TE | /* transmit enable */ + /* FIXME: MAC_CONTROL_PM - pass mcast. + * Seems like it makes no difference on some WiSoCs, + * for example ar2313. + * It should be tested on ar231[5,6,7] */ + MAC_CONTROL_PM | + MAC_CONTROL_F | /* full duplex */ + MAC_CONTROL_HBD); /* heart beat disabled */ + eth_writel(priv, flags, AR231X_ETH_MAC_CONTROL); +} + +static void ar231x_flash_rxdsc(struct ar231x_descr *rxdsc) +{ + rxdsc->status = DMA_RX_OWN; + rxdsc->devcs = ((AR2313_RX_BUFSIZE << DMA_RX1_BSIZE_SHIFT) | + DMA_RX1_CHAINED); +} + +static void ar231x_allocate_dma_descriptors(struct eth_device *edev) +{ + struct ar231x_eth_priv *priv = edev->priv; + u16 ar231x_descr_size = sizeof(struct ar231x_descr); + u16 i; + + priv->tx_ring = xmalloc(ar231x_descr_size); + dev_dbg(&edev->dev, "allocate tx_ring @ %p\n", priv->tx_ring); + + priv->rx_ring = xmalloc(ar231x_descr_size * AR2313_RXDSC_ENTRIES); + dev_dbg(&edev->dev, "allocate rx_ring @ %p\n", priv->rx_ring); + + priv->rx_buffer = xmalloc(AR2313_RX_BUFSIZE * AR2313_RXDSC_ENTRIES); + dev_dbg(&edev->dev, "allocate rx_buffer @ %p\n", priv->rx_buffer); + + /* Initialize the rx Descriptors */ + for (i = 0; i < AR2313_RXDSC_ENTRIES; i++) { + struct ar231x_descr *rxdsc = &priv->rx_ring[i]; + ar231x_flash_rxdsc(rxdsc); + rxdsc->buffer_ptr = + (u32)(priv->rx_buffer + AR2313_RX_BUFSIZE * i); + rxdsc->next_dsc_ptr = (u32)&priv->rx_ring[DSC_NEXT(i)]; + } + /* set initial position of ring descriptor */ + priv->next_rxdsc = &priv->rx_ring[0]; +} + +static void ar231x_adjust_link(struct eth_device *edev) +{ + struct ar231x_eth_priv *priv = edev->priv; + u32 mc; + + if (edev->phydev->duplex != priv->oldduplex) { + mc = eth_readl(priv, AR231X_ETH_MAC_CONTROL); + mc &= ~(MAC_CONTROL_F | MAC_CONTROL_DRO); + if (edev->phydev->duplex) + mc |= MAC_CONTROL_F; + else + mc |= MAC_CONTROL_DRO; + eth_writel(priv, mc, AR231X_ETH_MAC_CONTROL); + priv->oldduplex = edev->phydev->duplex; + } +} + +static int ar231x_eth_init(struct eth_device *edev) +{ + struct ar231x_eth_priv *priv = edev->priv; + + ar231x_allocate_dma_descriptors(edev); + ar231x_reset_regs(edev); + ar231x_set_ethaddr(edev, priv->mac); + return 0; +} + +static int ar231x_eth_open(struct eth_device *edev) +{ + struct ar231x_eth_priv *priv = edev->priv; + u32 tmp; + + /* Enable RX. Now the rx_buffer will be filled. + * If it is full we may lose first transmission. In this case + * barebox should retry it. + * Or TODO: - force HW to filter some how broadcasts + * - disable RX if we do not need it. */ + tmp = eth_readl(priv, AR231X_ETH_MAC_CONTROL); + eth_writel(priv, (tmp | MAC_CONTROL_RE), AR231X_ETH_MAC_CONTROL); + + return phy_device_connect(edev, &priv->miibus, (int)priv->phy_regs, + ar231x_adjust_link, 0, PHY_INTERFACE_MODE_MII); +} + +static int ar231x_eth_recv(struct eth_device *edev) +{ + struct ar231x_eth_priv *priv = edev->priv; + + while (1) { + struct ar231x_descr *rxdsc = priv->next_rxdsc; + u32 status = rxdsc->status; + + /* owned by DMA? */ + if (status & DMA_RX_OWN) + break; + + /* Pick only packets what we can handle: + * - only complete packet per buffer + * (First and Last at same time) + * - drop multicast */ + if (!priv->kill_rx_ring && + ((status & DMA_RX_MASK) == DMA_RX_FSLS)) { + u16 length = + ((status >> DMA_RX_LEN_SHIFT) & 0x3fff) + - CRC_LEN; + net_receive((void *)rxdsc->buffer_ptr, length); + } + /* Clean descriptor. now it is owned by DMA. */ + priv->next_rxdsc = (struct ar231x_descr *)rxdsc->next_dsc_ptr; + ar231x_flash_rxdsc(rxdsc); + } + priv->kill_rx_ring = 0; + return 0; +} + +static int ar231x_eth_send(struct eth_device *edev, void *packet, + int length) +{ + struct ar231x_eth_priv *priv = edev->priv; + struct ar231x_descr *txdsc = priv->tx_ring; + u32 rx_missed; + + /* We do not do async work. + * If rx_ring is full, there is nothing we can use. */ + rx_missed = dma_readl(priv, AR231X_DMA_RX_MISSED); + if (rx_missed) { + priv->kill_rx_ring = 1; + ar231x_eth_recv(edev); + } + + /* Setup the transmit descriptor. */ + txdsc->devcs = ((length << DMA_TX1_BSIZE_SHIFT) | DMA_TX1_DEFAULT); + txdsc->buffer_ptr = (uint)packet; + txdsc->status = DMA_TX_OWN; + + /* Trigger transmission */ + dma_writel(priv, 0, AR231X_DMA_TX_POLL); + + /* Take enough time to transmit packet. 100 is not enough. */ + wait_on_timeout(2000 * MSECOND, + !(txdsc->status & DMA_TX_OWN)); + + /* We can't do match here. If it is still in progress, + * then engine is probably stalled or we wait not enough. */ + if (txdsc->status & DMA_TX_OWN) + dev_err(&edev->dev, "Frame is still in progress.\n"); + + if (txdsc->status & DMA_TX_ERROR) + dev_err(&edev->dev, "Frame was aborted by engine\n"); + + /* Ready or not. Stop it. */ + txdsc->status = 0; + return 0; +} + +static void ar231x_eth_halt(struct eth_device *edev) +{ + struct ar231x_eth_priv *priv = edev->priv; + u32 tmp; + + /* kill the MAC: disable RX and TX */ + tmp = eth_readl(priv, AR231X_ETH_MAC_CONTROL); + eth_writel(priv, tmp & ~(MAC_CONTROL_RE | MAC_CONTROL_TE), + AR231X_ETH_MAC_CONTROL); + + /* stop DMA */ + dma_writel(priv, 0, AR231X_DMA_CONTROL); + dma_writel(priv, DMA_BUS_MODE_SWR, AR231X_DMA_BUS_MODE); + + /* place PHY and MAC in reset */ + ar231x_reset_bit_(priv, (priv->cfg->reset_mac | priv->cfg->reset_phy), SET); +} + +static int ar231x_get_ethaddr(struct eth_device *edev, unsigned char *addr) +{ + struct ar231x_eth_priv *priv = edev->priv; + + /* MAC address is stored on flash, in some kind of atheros config + * area. Platform code should read it and pass to the driver. */ + memcpy(addr, priv->mac, 6); + return 0; +} + +/** + * These device do not have build in MAC address. + * It is located on atheros-config field on flash. + */ +static int ar231x_set_ethaddr(struct eth_device *edev, unsigned char *addr) +{ + struct ar231x_eth_priv *priv = edev->priv; + + eth_writel(priv, + (addr[5] << 8) | (addr[4]), + AR231X_ETH_MAC_ADDR1); + eth_writel(priv, + (addr[3] << 24) | (addr[2] << 16) | + (addr[1] << 8) | addr[0], + AR231X_ETH_MAC_ADDR2); + + mdelay(10); + return 0; +} + +#define MII_ADDR(phy, reg) \ + ((reg << MII_ADDR_REG_SHIFT) | (phy << MII_ADDR_PHY_SHIFT)) + +static int ar231x_miibus_read(struct mii_bus *bus, int phy_id, int regnum) +{ + struct ar231x_eth_priv *priv = bus->priv; + uint64_t time_start; + + phy_writel(priv, MII_ADDR(phy_id, regnum), AR231X_ETH_MII_ADDR); + time_start = get_time_ns(); + while (phy_readl(priv, AR231X_ETH_MII_ADDR) & MII_ADDR_BUSY) { + if (is_timeout(time_start, SECOND)) { + dev_err(&bus->dev, "miibus read timeout\n"); + return -ETIMEDOUT; + } + } + return phy_readl(priv, AR231X_ETH_MII_DATA) >> MII_DATA_SHIFT; +} + +static int ar231x_miibus_write(struct mii_bus *bus, int phy_id, + int regnum, u16 val) +{ + struct ar231x_eth_priv *priv = bus->priv; + uint64_t time_start = get_time_ns(); + + while (phy_readl(priv, AR231X_ETH_MII_ADDR) & MII_ADDR_BUSY) { + if (is_timeout(time_start, SECOND)) { + dev_err(&bus->dev, "miibus write timeout\n"); + return -ETIMEDOUT; + } + } + phy_writel(priv, val << MII_DATA_SHIFT, AR231X_ETH_MII_DATA); + phy_writel(priv, MII_ADDR(phy_id, regnum) | MII_ADDR_WRITE, + AR231X_ETH_MII_ADDR); + return 0; +} + +static int ar231x_mdiibus_reset(struct mii_bus *bus) +{ + struct ar231x_eth_priv *priv = bus->priv; + + ar231x_reset_regs(&priv->edev); + return 0; +} + +static int ar231x_eth_probe(struct device_d *dev) +{ + struct ar231x_eth_priv *priv; + struct eth_device *edev; + struct mii_bus *miibus; + struct ar231x_eth_platform_data *pdata; + + if (!dev->platform_data) { + dev_err(dev, "no platform data\n"); + return -ENODEV; + } + + pdata = dev->platform_data; + + priv = xzalloc(sizeof(struct ar231x_eth_priv)); + edev = &priv->edev; + miibus = &priv->miibus; + edev->priv = priv; + + /* link all platform depended regs */ + priv->mac = pdata->mac; + priv->reset_bit = pdata->reset_bit; + + priv->eth_regs = dev_request_mem_region(dev, 0); + if (priv->eth_regs == NULL) { + dev_err(dev, "No eth_regs!!\n"); + return -ENODEV; + } + /* we have 0x100000 for eth, part of it are dma regs. + * So they are already requested */ + priv->dma_regs = (void *)(priv->eth_regs + 0x1000); + + priv->phy_regs = dev_request_mem_region(dev, 1); + if (priv->phy_regs == NULL) { + dev_err(dev, "No phy_regs!!\n"); + return -ENODEV; + } + + priv->cfg = pdata; + edev->init = ar231x_eth_init; + edev->open = ar231x_eth_open; + edev->send = ar231x_eth_send; + edev->recv = ar231x_eth_recv; + edev->halt = ar231x_eth_halt; + edev->get_ethaddr = ar231x_get_ethaddr; + edev->set_ethaddr = ar231x_set_ethaddr; + + priv->miibus.read = ar231x_miibus_read; + priv->miibus.write = ar231x_miibus_write; + priv->miibus.reset = ar231x_mdiibus_reset; + priv->miibus.priv = priv; + priv->miibus.parent = dev; + + mdiobus_register(miibus); + eth_register(edev); + + return 0; +} + +static void ar231x_eth_remove(struct device_d *dev) +{ + +} + +static struct driver_d ar231x_eth_driver = { + .name = "ar231x_eth", + .probe = ar231x_eth_probe, + .remove = ar231x_eth_remove, +}; + +static int ar231x_eth_driver_init(void) +{ + return platform_driver_register(&ar231x_eth_driver); +} +device_initcall(ar231x_eth_driver_init); |