summaryrefslogtreecommitdiffstats
path: root/drivers/net/ar231x.c
diff options
context:
space:
mode:
authorOleksij Rempel <linux@rempel-privat.de>2013-05-30 20:18:41 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2013-05-31 08:56:33 +0200
commit7d27f0db932d2a584409a3c5c85a229638597e65 (patch)
treea9629537504af40c55b476f4c9146f06550ffd7a /drivers/net/ar231x.c
parent59fe549733fb21f1e669bea148dc8c7899890844 (diff)
downloadbarebox-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.c437
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);