summaryrefslogtreecommitdiffstats
path: root/src/drivers/net/myson.c
diff options
context:
space:
mode:
authorMichael Brown2012-04-26 23:10:54 +0200
committerMichael Brown2012-04-27 12:46:58 +0200
commit1fe27a3e0e774d319a1bf3ecefb40590c660cf8d (patch)
tree9306c655d22e035200672398d70331c4610983f5 /src/drivers/net/myson.c
parent[natsemi] Fix test for addresses below 4GB (diff)
downloadipxe-1fe27a3e0e774d319a1bf3ecefb40590c660cf8d.tar.gz
ipxe-1fe27a3e0e774d319a1bf3ecefb40590c660cf8d.tar.xz
ipxe-1fe27a3e0e774d319a1bf3ecefb40590c660cf8d.zip
[myson] Replace driver for Myson Technology NICs
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/drivers/net/myson.c')
-rw-r--r--src/drivers/net/myson.c668
1 files changed, 668 insertions, 0 deletions
diff --git a/src/drivers/net/myson.c b/src/drivers/net/myson.c
new file mode 100644
index 00000000..25f1a270
--- /dev/null
+++ b/src/drivers/net/myson.c
@@ -0,0 +1,668 @@
+/*
+ * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/ethernet.h>
+#include <ipxe/if_ether.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/malloc.h>
+#include <ipxe/pci.h>
+#include <ipxe/mii.h>
+#include "myson.h"
+
+/** @file
+ *
+ * Myson Technology network card driver
+ *
+ */
+
+/******************************************************************************
+ *
+ * Device reset
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Reset controller chip
+ *
+ * @v myson Myson device
+ * @ret rc Return status code
+ */
+static int myson_soft_reset ( struct myson_nic *myson ) {
+ uint32_t bcr;
+ unsigned int i;
+
+ /* Initiate reset */
+ bcr = readl ( myson->regs + MYSON_BCR );
+ writel ( ( bcr | MYSON_BCR_SWR ), myson->regs + MYSON_BCR );
+
+ /* Wait for reset to complete */
+ for ( i = 0 ; i < MYSON_RESET_MAX_WAIT_MS ; i++ ) {
+
+ /* If reset is not complete, delay 1ms and retry */
+ if ( readl ( myson->regs + MYSON_BCR ) & MYSON_BCR_SWR ) {
+ mdelay ( 1 );
+ continue;
+ }
+
+ /* Apply a sensible default bus configuration */
+ bcr = readl ( myson->regs + MYSON_BCR );
+ bcr &= ~MYSON_BCR_PBL_MASK;
+ bcr |= ( MYSON_BCR_RLE | MYSON_BCR_RME | MYSON_BCR_WIE |
+ MYSON_BCR_PBL_DEFAULT );
+ writel ( bcr, myson->regs + MYSON_BCR );
+ DBGC ( myson, "MYSON %p using configuration %08x\n",
+ myson, bcr );
+
+ return 0;
+ }
+
+ DBGC ( myson, "MYSON %p timed out waiting for reset\n", myson );
+ return -ETIMEDOUT;
+}
+
+/**
+ * Reload configuration from EEPROM
+ *
+ * @v myson Myson device
+ * @ret rc Return status code
+ */
+static int myson_reload_config ( struct myson_nic *myson ) {
+ unsigned int i;
+
+ /* Initiate reload */
+ writel ( MYSON_ROM_AUTOLD, myson->regs + MYSON_ROM_MII );
+
+ /* Wait for reload to complete */
+ for ( i = 0 ; i < MYSON_AUTOLD_MAX_WAIT_MS ; i++ ) {
+
+ /* If reload is not complete, delay 1ms and retry */
+ if ( readl ( myson->regs + MYSON_ROM_MII ) & MYSON_ROM_AUTOLD ){
+ mdelay ( 1 );
+ continue;
+ }
+
+ return 0;
+ }
+
+ DBGC ( myson, "MYSON %p timed out waiting for configuration "
+ "reload\n", myson );
+ return -ETIMEDOUT;
+}
+
+/**
+ * Reset hardware
+ *
+ * @v myson Myson device
+ * @ret rc Return status code
+ */
+static int myson_reset ( struct myson_nic *myson ) {
+ int rc;
+
+ /* Disable all interrupts */
+ writel ( 0, myson->regs + MYSON_IMR );
+
+ /* Perform soft reset */
+ if ( ( rc = myson_soft_reset ( myson ) ) != 0 )
+ return rc;
+
+ /* Reload configuration from EEPROM */
+ if ( ( rc = myson_reload_config ( myson ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/******************************************************************************
+ *
+ * Network device interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Create descriptor ring
+ *
+ * @v myson Myson device
+ * @v ring Descriptor ring
+ * @ret rc Return status code
+ */
+static int myson_create_ring ( struct myson_nic *myson,
+ struct myson_ring *ring ) {
+ size_t len = ( ring->count * sizeof ( ring->desc[0] ) );
+ struct myson_descriptor *desc;
+ struct myson_descriptor *next;
+ physaddr_t address;
+ unsigned int i;
+ int rc;
+
+ /* Allocate descriptor ring */
+ ring->desc = malloc_dma ( len, MYSON_RING_ALIGN );
+ if ( ! ring->desc ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ address = virt_to_bus ( ring->desc );
+
+ /* Check address is usable by card */
+ if ( ! myson_address_ok ( address + len ) ) {
+ DBGC ( myson, "MYSON %p cannot support 64-bit ring address\n",
+ myson );
+ rc = -ENOTSUP;
+ goto err_64bit;
+ }
+
+ /* Initialise descriptor ring */
+ memset ( ring->desc, 0, len );
+ for ( i = 0 ; i < ring->count ; i++ ) {
+ desc = &ring->desc[i];
+ next = &ring->desc[ ( i + 1 ) % ring->count ];
+ desc->next = cpu_to_le32 ( virt_to_bus ( next ) );
+ }
+
+ /* Program ring address */
+ writel ( address, myson->regs + ring->reg );
+ DBGC ( myson, "MYSON %p ring %02x is at [%08llx,%08llx)\n",
+ myson, ring->reg, ( ( unsigned long long ) address ),
+ ( ( unsigned long long ) address + len ) );
+
+ return 0;
+
+ err_64bit:
+ free_dma ( ring->desc, len );
+ ring->desc = NULL;
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Destroy descriptor ring
+ *
+ * @v myson Myson device
+ * @v ring Descriptor ring
+ */
+static void myson_destroy_ring ( struct myson_nic *myson,
+ struct myson_ring *ring ) {
+ size_t len = ( ring->count * sizeof ( ring->desc[0] ) );
+
+ /* Clear ring address */
+ writel ( 0, myson->regs + ring->reg );
+
+ /* Free descriptor ring */
+ free_dma ( ring->desc, len );
+ ring->desc = NULL;
+ ring->prod = 0;
+ ring->cons = 0;
+}
+
+/**
+ * Refill receive descriptor ring
+ *
+ * @v netdev Network device
+ */
+static void myson_refill_rx ( struct net_device *netdev ) {
+ struct myson_nic *myson = netdev->priv;
+ struct myson_descriptor *rx;
+ struct io_buffer *iobuf;
+ unsigned int rx_idx;
+ physaddr_t address;
+
+ while ( ( myson->rx.prod - myson->rx.cons ) < MYSON_NUM_RX_DESC ) {
+
+ /* Allocate I/O buffer */
+ iobuf = alloc_iob ( MYSON_RX_MAX_LEN );
+ if ( ! iobuf ) {
+ /* Wait for next refill */
+ return;
+ }
+
+ /* Check address is usable by card */
+ address = virt_to_bus ( iobuf->data );
+ if ( ! myson_address_ok ( address ) ) {
+ DBGC ( myson, "MYSON %p cannot support 64-bit RX "
+ "buffer address\n", myson );
+ netdev_rx_err ( netdev, iobuf, -ENOTSUP );
+ return;
+ }
+
+ /* Get next receive descriptor */
+ rx_idx = ( myson->rx.prod++ % MYSON_NUM_RX_DESC );
+ rx = &myson->rx.desc[rx_idx];
+
+ /* Populate receive descriptor */
+ rx->address = cpu_to_le32 ( address );
+ rx->control =
+ cpu_to_le32 ( MYSON_RX_CTRL_RBS ( MYSON_RX_MAX_LEN ) );
+ wmb();
+ rx->status = cpu_to_le32 ( MYSON_RX_STAT_OWN );
+ wmb();
+
+ /* Record I/O buffer */
+ assert ( myson->rx_iobuf[rx_idx] == NULL );
+ myson->rx_iobuf[rx_idx] = iobuf;
+
+ /* Notify card that there are descriptors available */
+ writel ( 0, myson->regs + MYSON_RXPDR );
+
+ DBGC2 ( myson, "MYSON %p RX %d is [%llx,%llx)\n", myson,
+ rx_idx, ( ( unsigned long long ) address ),
+ ( ( unsigned long long ) address + MYSON_RX_MAX_LEN ) );
+ }
+}
+
+/**
+ * Open network device
+ *
+ * @v netdev Network device
+ * @ret rc Return status code
+ */
+static int myson_open ( struct net_device *netdev ) {
+ struct myson_nic *myson = netdev->priv;
+ union myson_physical_address mac;
+ int rc;
+
+ /* Set MAC address */
+ memset ( &mac, 0, sizeof ( mac ) );
+ memcpy ( mac.raw, netdev->ll_addr, ETH_ALEN );
+ writel ( le32_to_cpu ( mac.reg.low ), myson->regs + MYSON_PAR0 );
+ writel ( le32_to_cpu ( mac.reg.high ), myson->regs + MYSON_PAR4 );
+
+ /* Create transmit descriptor ring */
+ if ( ( rc = myson_create_ring ( myson, &myson->tx ) ) != 0 )
+ goto err_create_tx;
+
+ /* Create receive descriptor ring */
+ if ( ( rc = myson_create_ring ( myson, &myson->rx ) ) != 0 )
+ goto err_create_rx;
+
+ /* Configure transmitter and receiver */
+ writel ( ( MYSON_TCR_TE | MYSON_RCR_PROM | MYSON_RCR_AB | MYSON_RCR_AM |
+ MYSON_RCR_ARP | MYSON_RCR_ALP | MYSON_RCR_RE ),
+ myson->regs + MYSON_TCR_RCR );
+
+ /* Fill receive ring */
+ myson_refill_rx ( netdev );
+
+ return 0;
+
+ myson_destroy_ring ( myson, &myson->rx );
+ err_create_rx:
+ myson_destroy_ring ( myson, &myson->tx );
+ err_create_tx:
+ return rc;
+}
+
+/**
+ * Wait for transmit and receive to become idle
+ *
+ * @v myson Myson device
+ * @ret rc Return status code
+ */
+static int myson_wait_idle ( struct myson_nic *myson ) {
+ uint32_t tcr_rcr;
+ unsigned int i;
+
+ /* Wait for both transmit and receive to be idle */
+ for ( i = 0 ; i < MYSON_IDLE_MAX_WAIT_MS ; i++ ) {
+
+ /* If either process is running, delay 1ms and retry */
+ tcr_rcr = readl ( myson->regs + MYSON_TCR_RCR );
+ if ( tcr_rcr & ( MYSON_TCR_TXS | MYSON_RCR_RXS ) ) {
+ mdelay ( 1 );
+ continue;
+ }
+
+ return 0;
+ }
+
+ DBGC ( myson, "MYSON %p timed out waiting for idle state (status "
+ "%08x)\n", myson, tcr_rcr );
+ return -ETIMEDOUT;
+}
+
+/**
+ * Close network device
+ *
+ * @v netdev Network device
+ */
+static void myson_close ( struct net_device *netdev ) {
+ struct myson_nic *myson = netdev->priv;
+ unsigned int i;
+
+ /* Disable receiver and transmitter */
+ writel ( 0, myson->regs + MYSON_TCR_RCR );
+
+ /* Allow time for receiver and transmitter to become idle */
+ myson_wait_idle ( myson );
+
+ /* Destroy receive descriptor ring */
+ myson_destroy_ring ( myson, &myson->rx );
+
+ /* Discard any unused receive buffers */
+ for ( i = 0 ; i < MYSON_NUM_RX_DESC ; i++ ) {
+ if ( myson->rx_iobuf[i] )
+ free_iob ( myson->rx_iobuf[i] );
+ myson->rx_iobuf[i] = NULL;
+ }
+
+ /* Destroy transmit descriptor ring */
+ myson_destroy_ring ( myson, &myson->tx );
+}
+
+/**
+ * Transmit packet
+ *
+ * @v netdev Network device
+ * @v iobuf I/O buffer
+ * @ret rc Return status code
+ */
+static int myson_transmit ( struct net_device *netdev,
+ struct io_buffer *iobuf ) {
+ struct myson_nic *myson = netdev->priv;
+ struct myson_descriptor *tx;
+ unsigned int tx_idx;
+ physaddr_t address;
+
+ /* Check address is usable by card */
+ address = virt_to_bus ( iobuf->data );
+ if ( ! myson_address_ok ( address ) ) {
+ DBGC ( myson, "MYSON %p cannot support 64-bit TX buffer "
+ "address\n", myson );
+ return -ENOTSUP;
+ }
+
+ /* Get next transmit descriptor */
+ if ( ( myson->tx.prod - myson->tx.cons ) >= MYSON_NUM_TX_DESC ) {
+ DBGC ( myson, "MYSON %p out of transmit descriptors\n",
+ myson );
+ return -ENOBUFS;
+ }
+ tx_idx = ( myson->tx.prod++ % MYSON_NUM_TX_DESC );
+ tx = &myson->tx.desc[tx_idx];
+
+ /* Populate transmit descriptor */
+ tx->address = cpu_to_le32 ( address );
+ tx->control = cpu_to_le32 ( MYSON_TX_CTRL_IC | MYSON_TX_CTRL_LD |
+ MYSON_TX_CTRL_FD | MYSON_TX_CTRL_CRC |
+ MYSON_TX_CTRL_PAD | MYSON_TX_CTRL_RTLC |
+ MYSON_TX_CTRL_PKTS ( iob_len ( iobuf ) ) |
+ MYSON_TX_CTRL_TBS ( iob_len ( iobuf ) ) );
+ wmb();
+ tx->status = cpu_to_le32 ( MYSON_TX_STAT_OWN );
+ wmb();
+
+ /* Notify card that there are packets ready to transmit */
+ writel ( 0, myson->regs + MYSON_TXPDR );
+
+ DBGC2 ( myson, "MYSON %p TX %d is [%llx,%llx)\n", myson, tx_idx,
+ ( ( unsigned long long ) address ),
+ ( ( unsigned long long ) address + iob_len ( iobuf ) ) );
+
+ return 0;
+}
+
+/**
+ * Poll for completed packets
+ *
+ * @v netdev Network device
+ */
+static void myson_poll_tx ( struct net_device *netdev ) {
+ struct myson_nic *myson = netdev->priv;
+ struct myson_descriptor *tx;
+ unsigned int tx_idx;
+
+ /* Check for completed packets */
+ while ( myson->tx.cons != myson->tx.prod ) {
+
+ /* Get next transmit descriptor */
+ tx_idx = ( myson->tx.cons % MYSON_NUM_TX_DESC );
+ tx = &myson->tx.desc[tx_idx];
+
+ /* Stop if descriptor is still in use */
+ if ( tx->status & cpu_to_le32 ( MYSON_TX_STAT_OWN ) )
+ return;
+
+ /* Complete TX descriptor */
+ if ( tx->status & cpu_to_le32 ( MYSON_TX_STAT_ABORT |
+ MYSON_TX_STAT_CSL ) ) {
+ DBGC ( myson, "MYSON %p TX %d completion error "
+ "(%08x)\n", myson, tx_idx,
+ le32_to_cpu ( tx->status ) );
+ netdev_tx_complete_next_err ( netdev, -EIO );
+ } else {
+ DBGC2 ( myson, "MYSON %p TX %d complete\n",
+ myson, tx_idx );
+ netdev_tx_complete_next ( netdev );
+ }
+ myson->tx.cons++;
+ }
+}
+
+/**
+ * Poll for received packets
+ *
+ * @v netdev Network device
+ */
+static void myson_poll_rx ( struct net_device *netdev ) {
+ struct myson_nic *myson = netdev->priv;
+ struct myson_descriptor *rx;
+ struct io_buffer *iobuf;
+ unsigned int rx_idx;
+ size_t len;
+
+ /* Check for received packets */
+ while ( myson->rx.cons != myson->rx.prod ) {
+
+ /* Get next receive descriptor */
+ rx_idx = ( myson->rx.cons % MYSON_NUM_RX_DESC );
+ rx = &myson->rx.desc[rx_idx];
+
+ /* Stop if descriptor is still in use */
+ if ( rx->status & MYSON_RX_STAT_OWN )
+ return;
+
+ /* Populate I/O buffer */
+ iobuf = myson->rx_iobuf[rx_idx];
+ myson->rx_iobuf[rx_idx] = NULL;
+ len = MYSON_RX_STAT_FLNG ( le32_to_cpu ( rx->status ) );
+ iob_put ( iobuf, len - 4 /* strip CRC */ );
+
+ /* Hand off to network stack */
+ if ( rx->status & cpu_to_le32 ( MYSON_RX_STAT_ES ) ) {
+ DBGC ( myson, "MYSON %p RX %d error (length %zd, "
+ "status %08x)\n", myson, rx_idx, len,
+ le32_to_cpu ( rx->status ) );
+ netdev_rx_err ( netdev, iobuf, -EIO );
+ } else {
+ DBGC2 ( myson, "MYSON %p RX %d complete (length "
+ "%zd)\n", myson, rx_idx, len );
+ netdev_rx ( netdev, iobuf );
+ }
+ myson->rx.cons++;
+ }
+}
+
+/**
+ * Poll for completed and received packets
+ *
+ * @v netdev Network device
+ */
+static void myson_poll ( struct net_device *netdev ) {
+ struct myson_nic *myson = netdev->priv;
+ uint32_t isr;
+ unsigned int i;
+
+ /* Polling the ISR seems to really upset this card; it ends up
+ * getting no useful PCI transfers done and, for some reason,
+ * flooding the network with invalid packets. Work around
+ * this by introducing deliberate delays between ISR reads.
+ */
+ for ( i = 0 ; i < MYSON_ISR_IODELAY_COUNT ; i++ )
+ iodelay();
+
+ /* Check for and acknowledge interrupts */
+ isr = readl ( myson->regs + MYSON_ISR );
+ if ( ! isr )
+ return;
+ writel ( isr, myson->regs + MYSON_ISR );
+
+ /* Poll for TX completions, if applicable */
+ if ( isr & MYSON_IRQ_TI )
+ myson_poll_tx ( netdev );
+
+ /* Poll for RX completionsm, if applicable */
+ if ( isr & MYSON_IRQ_RI )
+ myson_poll_rx ( netdev );
+
+ /* Refill RX ring */
+ myson_refill_rx ( netdev );
+}
+
+/**
+ * Enable or disable interrupts
+ *
+ * @v netdev Network device
+ * @v enable Interrupts should be enabled
+ */
+static void myson_irq ( struct net_device *netdev, int enable ) {
+ struct myson_nic *myson = netdev->priv;
+ uint32_t imr;
+
+ imr = ( enable ? ( MYSON_IRQ_TI | MYSON_IRQ_RI ) : 0 );
+ writel ( imr, myson->regs + MYSON_IMR );
+}
+
+/** Myson network device operations */
+static struct net_device_operations myson_operations = {
+ .open = myson_open,
+ .close = myson_close,
+ .transmit = myson_transmit,
+ .poll = myson_poll,
+ .irq = myson_irq,
+};
+
+/******************************************************************************
+ *
+ * PCI interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Probe PCI device
+ *
+ * @v pci PCI device
+ * @ret rc Return status code
+ */
+static int myson_probe ( struct pci_device *pci ) {
+ struct net_device *netdev;
+ struct myson_nic *myson;
+ union myson_physical_address mac;
+ int rc;
+
+ /* Allocate and initialise net device */
+ netdev = alloc_etherdev ( sizeof ( *myson ) );
+ if ( ! netdev ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ netdev_init ( netdev, &myson_operations );
+ myson = netdev->priv;
+ pci_set_drvdata ( pci, netdev );
+ netdev->dev = &pci->dev;
+ memset ( myson, 0, sizeof ( *myson ) );
+ myson_init_ring ( &myson->tx, MYSON_NUM_TX_DESC, MYSON_TXLBA );
+ myson_init_ring ( &myson->rx, MYSON_NUM_RX_DESC, MYSON_RXLBA );
+
+ /* Fix up PCI device */
+ adjust_pci_device ( pci );
+
+ /* Map registers */
+ myson->regs = ioremap ( pci->membase, MYSON_BAR_SIZE );
+
+ /* Reset the NIC */
+ if ( ( rc = myson_reset ( myson ) ) != 0 )
+ goto err_reset;
+
+ /* Read MAC address */
+ mac.reg.low = cpu_to_le32 ( readl ( myson->regs + MYSON_PAR0 ) );
+ mac.reg.high = cpu_to_le32 ( readl ( myson->regs + MYSON_PAR4 ) );
+ memcpy ( netdev->hw_addr, mac.raw, ETH_ALEN );
+
+ /* Register network device */
+ if ( ( rc = register_netdev ( netdev ) ) != 0 )
+ goto err_register_netdev;
+
+ /* Mark as link up; we don't yet handle link state */
+ netdev_link_up ( netdev );
+
+ return 0;
+
+ unregister_netdev ( netdev );
+ err_register_netdev:
+ myson_reset ( myson );
+ err_reset:
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Remove PCI device
+ *
+ * @v pci PCI device
+ */
+static void myson_remove ( struct pci_device *pci ) {
+ struct net_device *netdev = pci_get_drvdata ( pci );
+ struct myson_nic *myson = netdev->priv;
+
+ /* Unregister network device */
+ unregister_netdev ( netdev );
+
+ /* Reset card */
+ myson_reset ( myson );
+
+ /* Free network device */
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+}
+
+/** Myson PCI device IDs */
+static struct pci_device_id myson_nics[] = {
+ PCI_ROM ( 0x1516, 0x0800, "mtd800", "MTD-8xx", 0 ),
+ PCI_ROM ( 0x1516, 0x0803, "mtd803", "Surecom EP-320X-S", 0 ),
+ PCI_ROM ( 0x1516, 0x0891, "mtd891", "MTD-8xx", 0 ),
+};
+
+/** Myson PCI driver */
+struct pci_driver myson_driver __pci_driver = {
+ .ids = myson_nics,
+ .id_count = ( sizeof ( myson_nics ) / sizeof ( myson_nics[0] ) ),
+ .probe = myson_probe,
+ .remove = myson_remove,
+};