From patchwork Wed Oct 29 22:22:51 2008 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ira Snyder X-Patchwork-Id: 6325 Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from ozlabs.org (localhost [127.0.0.1]) by ozlabs.org (Postfix) with ESMTP id 119D9DE0CC for ; Thu, 30 Oct 2008 09:23:23 +1100 (EST) X-Original-To: linuxppc-dev@ozlabs.org Delivered-To: linuxppc-dev@ozlabs.org Received: from ovro.ovro.caltech.edu (ovro.ovro.caltech.edu [192.100.16.2]) by ozlabs.org (Postfix) with ESMTP id 8A8D6DDDF5 for ; Thu, 30 Oct 2008 09:22:54 +1100 (EST) Received: from ovro.caltech.edu (desk1.correlator.pvt [192.168.17.65]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by ovro.ovro.caltech.edu (Postfix) with ESMTP id 623E711C80B2; Wed, 29 Oct 2008 15:22:52 -0700 (PDT) Date: Wed, 29 Oct 2008 15:22:51 -0700 From: Ira Snyder To: Matt Sealey Subject: Re: [PATCH RFC v2] net: add PCINet driver Message-ID: <20081029222251.GK12879@ovro.caltech.edu> Mail-Followup-To: Matt Sealey , Scott Wood , Stephen Hemminger , netdev@vger.kernel.org, linuxppc-dev@ozlabs.org, linux-kernel@vger.kernel.org References: <20081029202027.GH12879@ovro.caltech.edu> <20081029132506.55b93555@extreme> <20081029205002.GI12879@ovro.caltech.edu> <4908CD96.300@freescale.com> <20081029211312.GJ12879@ovro.caltech.edu> <4908D912.8010003@genesi-usa.com> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <4908D912.8010003@genesi-usa.com> User-Agent: Mutt/1.5.17+20080114 (2008-01-14) X-Greylist: Sender succeeded SMTP AUTH, not delayed by milter-greylist-4.0 (ovro.ovro.caltech.edu); Wed, 29 Oct 2008 15:22:52 -0700 (PDT) Cc: Scott Wood , netdev@vger.kernel.org, Stephen Hemminger , linux-kernel@vger.kernel.org, linuxppc-dev@ozlabs.org X-BeenThere: linuxppc-dev@ozlabs.org X-Mailman-Version: 2.1.11 Precedence: list List-Id: Linux on PowerPC Developers Mail List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@ozlabs.org Errors-To: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@ozlabs.org On Wed, Oct 29, 2008 at 04:43:46PM -0500, Matt Sealey wrote: > > Ira Snyder wrote: >> >> I'll go ahead change the typedef to a struct. Hopefully some of the >> other inspiration I took wasn't too bad. I'm totally open to suggestions >> on improvements for this driver. > > Not a suggestion for improvement, but I have to say this is by far the > single coolest thing I have EVER seen submitted to mainline :) > Awesome, that's what I like to hear! Here is another quick version of the patch, just for fun. There are only two changes from the original patch, which I didn't think are big enough to start another thread. 1) Use struct circ_buf_desc instead of cbd_t 2) Use get_immrbase() to get the IMMR register address Ira ====================================================================== [PATCH] net: add PCINet driver This adds support to Linux for a virtual ethernet interface which uses the PCI bus as its transport mechanism. It creates a simple, familiar, and fast method of communication for two devices connected by a PCI interface. I have implemented client support for the Freescale MPC8349EMDS board, which is capable of running in PCI Agent mode (It acts like a PCI card, but is a complete PowerPC computer, running Linux). It is almost certainly trivially ported to any MPC83xx system. It was developed to work in a CompactPCI crate of computers, one of which is a relatively standard x86 system (acting as the host) and many PowerPC systems (acting as clients). RFC v2 -> RFC v3pre: * replace typedef cbd_t with struct circ_buf_desc * use get_immrbase() to get IMMR register offsets RFC v1 -> RFC v2: * remove vim modelines * use net_device->name in request_irq(), for irqbalance * remove unneccesary wqt_get_stats(), use default get_stats() instead * use dev_printk() and friends * add message unit to MPC8349EMDS dts file Signed-off-by: Ira W. Snyder --- arch/powerpc/boot/dts/mpc834x_mds.dts | 7 + drivers/net/Kconfig | 34 + drivers/net/Makefile | 3 + drivers/net/pcinet.h | 74 ++ drivers/net/pcinet_fsl.c | 1354 ++++++++++++++++++++++++++++++++ drivers/net/pcinet_host.c | 1383 +++++++++++++++++++++++++++++++++ drivers/net/pcinet_hw.h | 77 ++ 7 files changed, 2932 insertions(+), 0 deletions(-) create mode 100644 drivers/net/pcinet.h create mode 100644 drivers/net/pcinet_fsl.c create mode 100644 drivers/net/pcinet_host.c create mode 100644 drivers/net/pcinet_hw.h diff --git a/arch/powerpc/boot/dts/mpc834x_mds.dts b/arch/powerpc/boot/dts/mpc834x_mds.dts index c986c54..3bc8975 100644 --- a/arch/powerpc/boot/dts/mpc834x_mds.dts +++ b/arch/powerpc/boot/dts/mpc834x_mds.dts @@ -104,6 +104,13 @@ mode = "cpu"; }; + message-unit@8030 { + compatible = "fsl,mpc8349-mu"; + reg = <0x8030 0xd0>; + interrupts = <69 0x8>; + interrupt-parent = <&ipic>; + }; + dma@82a8 { #address-cells = <1>; #size-cells = <1>; diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index f749b40..eef7af7 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -2279,6 +2279,40 @@ config UGETH_TX_ON_DEMAND bool "Transmit on Demand support" depends on UCC_GETH +config PCINET_FSL + tristate "PCINet Virtual Ethernet over PCI support (Freescale)" + depends on MPC834x_MDS && !PCI + select DMA_ENGINE + select FSL_DMA + help + When running as a PCI Agent, this driver will create a virtual + ethernet link running over the PCI bus, allowing simplified + communication with the host system. The host system will need + to use the corresponding driver. + + If in doubt, say N. + +config PCINET_HOST + tristate "PCINet Virtual Ethernet over PCI support (Host)" + depends on PCI + help + This driver will let you communicate with a PCINet client device + using a virtual ethernet link running over the PCI bus. This + allows simplified communication with the client system. + + This is inteded for use in a system that has a crate full of + computers running Linux, all connected by a PCI backplane. + + If in doubt, say N. + +config PCINET_DISABLE_CHECKSUM + bool "Disable packet checksumming" + depends on PCINET_FSL || PCINET_HOST + default n + help + Disable packet checksumming on packets received by the PCINet + driver. This gives a possible speed boost. + config MV643XX_ETH tristate "Marvell Discovery (643XX) and Orion ethernet support" depends on MV64360 || MV64X60 || (PPC_MULTIPLATFORM && PPC32) || PLAT_ORION diff --git a/drivers/net/Makefile b/drivers/net/Makefile index f19acf8..c6fbafc 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -30,6 +30,9 @@ gianfar_driver-objs := gianfar.o \ obj-$(CONFIG_UCC_GETH) += ucc_geth_driver.o ucc_geth_driver-objs := ucc_geth.o ucc_geth_mii.o ucc_geth_ethtool.o +obj-$(CONFIG_PCINET_FSL) += pcinet_fsl.o +obj-$(CONFIG_PCINET_HOST) += pcinet_host.o + # # link order important here # diff --git a/drivers/net/pcinet.h b/drivers/net/pcinet.h new file mode 100644 index 0000000..054a0ee --- /dev/null +++ b/drivers/net/pcinet.h @@ -0,0 +1,74 @@ +/* + * Shared Definitions for the PCINet / PCISerial drivers + * + * Copyright (c) 2008 Ira W. Snyder + * + * Heavily inspired by the drivers/net/fs_enet driver + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef PCINET_H +#define PCINET_H + +#include +#include + +/* Ring and Frame size -- these must match between the drivers */ +#define PH_RING_SIZE (64) +#define PH_MAX_FRSIZE (64 * 1024) +#define PH_MAX_MTU (PH_MAX_FRSIZE - ETH_HLEN) + +struct circ_buf_desc { + __le32 sc; + __le32 len; + __le32 addr; +} __attribute__((__packed__)); + +/* Buffer Descriptor Accessors */ +#define CBDW_SC(_cbd, _sc) iowrite32((_sc), &(_cbd)->sc) +#define CBDW_LEN(_cbd, _len) iowrite32((_len), &(_cbd)->len) +#define CBDW_ADDR(_cbd, _addr) iowrite32((_addr), &(_cbd)->addr) + +#define CBDR_SC(_cbd) ioread32(&(_cbd)->sc) +#define CBDR_LEN(_cbd) ioread32(&(_cbd)->len) +#define CBDR_ADDR(_cbd) ioread32(&(_cbd)->addr) + +/* Buffer Descriptor Registers */ +#define PCINET_TXBD_BASE 0x400 +#define PCINET_RXBD_BASE 0x800 + +/* Buffer Descriptor Status */ +#define BD_MEM_READY 0x1 +#define BD_MEM_DIRTY 0x2 +#define BD_MEM_FREE 0x3 + +/* IMMR Accessor Helpers */ +#define IMMR_R32(_off) ioread32(priv->immr+(_off)) +#define IMMR_W32(_off, _val) iowrite32((_val), priv->immr+(_off)) +#define IMMR_R32BE(_off) ioread32be(priv->immr+(_off)) +#define IMMR_W32BE(_off, _val) iowrite32be((_val), priv->immr+(_off)) + +/* Status Register Bits */ +#define PCINET_UART_RX_ENABLED (1<<0) +#define PCINET_NET_STATUS_RUNNING (1<<1) +#define PCINET_NET_RXINT_OFF (1<<2) +#define PCINET_NET_REGISTERS_VALID (1<<3) + +/* Driver State Bits */ +#define NET_STATE_STOPPED 0 +#define NET_STATE_RUNNING 1 + +/* Doorbell Registers */ +#define UART_RX_READY_DBELL (1<<0) +#define UART_TX_EMPTY_DBELL (1<<1) +#define NET_RX_PACKET_DBELL (1<<2) +#define NET_TX_COMPLETE_DBELL (1<<3) +#define NET_START_REQ_DBELL (1<<4) +#define NET_START_ACK_DBELL (1<<5) +#define NET_STOP_REQ_DBELL (1<<6) +#define NET_STOP_ACK_DBELL (1<<7) + +#endif /* PCINET_H */ diff --git a/drivers/net/pcinet_fsl.c b/drivers/net/pcinet_fsl.c new file mode 100644 index 0000000..a6262fe --- /dev/null +++ b/drivers/net/pcinet_fsl.c @@ -0,0 +1,1354 @@ +/* + * PCINet and PCISerial Driver for Freescale MPC8349EMDS + * + * Copyright (c) 2008 Ira W. Snyder + * + * Heavily inspired by the drivers/net/fs_enet driver + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* MPC8349EMDS specific get_immrbase() */ +#include + +#include "pcinet.h" +#include "pcinet_hw.h" + +static const char driver_name[] = "wqt"; + +static void wqtuart_rx_char(struct uart_port *port, const char ch); +static void wqtuart_stop_tx(struct uart_port *port); + +struct wqt_dev; +typedef void (*wqt_irqhandler_t)(struct wqt_dev *); + +struct wqt_irqhandlers { + wqt_irqhandler_t net_start_req_handler; + wqt_irqhandler_t net_start_ack_handler; + wqt_irqhandler_t net_stop_req_handler; + wqt_irqhandler_t net_stop_ack_handler; + wqt_irqhandler_t net_rx_packet_handler; + wqt_irqhandler_t net_tx_complete_handler; + wqt_irqhandler_t uart_rx_ready_handler; + wqt_irqhandler_t uart_tx_empty_handler; +}; + +struct wqt_dev { + /*--------------------------------------------------------------------*/ + /* OpenFirmware Infrastructure */ + /*--------------------------------------------------------------------*/ + struct of_device *op; + struct device *dev; + int irq; + void __iomem *immr; + + struct mutex irq_mutex; + int interrupt_count; + + spinlock_t irq_lock; + struct wqt_irqhandlers handlers; + + /*--------------------------------------------------------------------*/ + /* UART Device Infrastructure */ + /*--------------------------------------------------------------------*/ + struct uart_port port; + bool uart_rx_enabled; + bool uart_open; + + struct workqueue_struct *wq; + struct work_struct uart_tx_work; + wait_queue_head_t uart_tx_wait; /* sleep for uart_tx_ready */ + bool uart_tx_ready; /* transmitter state */ + + /*--------------------------------------------------------------------*/ + /* Ethernet Device Infrastructure */ + /*--------------------------------------------------------------------*/ + struct net_device *ndev; + void __iomem *netregs; + dma_addr_t netregs_addr; + + /* Outstanding SKB */ + struct sk_buff *tx_skbs[PH_RING_SIZE]; + + /* Circular Buffer Descriptor base */ + struct circ_buf_desc __iomem *rx_base; + struct circ_buf_desc __iomem *tx_base; + + /* Current SKB index */ + struct circ_buf_desc __iomem *cur_rx; + struct circ_buf_desc __iomem *cur_tx; + struct circ_buf_desc __iomem *dirty_tx; + int tx_free; + + struct tasklet_struct tx_complete_tasklet; + spinlock_t net_lock; + + struct mutex net_mutex; + int net_state; + struct work_struct net_start_work; + struct work_struct net_stop_work; + struct completion net_start_completion; + struct completion net_stop_completion; + struct napi_struct napi; + + struct dma_client client; + struct dma_chan *chan; +}; + +/*----------------------------------------------------------------------------*/ +/* Status Register Helper Operations */ +/*----------------------------------------------------------------------------*/ + +static DEFINE_SPINLOCK(status_lock); + +static void wqtstatus_setbit(struct wqt_dev *priv, u32 bit) +{ + unsigned long flags; + + spin_lock_irqsave(&status_lock, flags); + IMMR_W32(OMR1_OFFSET, IMMR_R32(OMR1_OFFSET) | bit); + spin_unlock_irqrestore(&status_lock, flags); +} + +static void wqtstatus_clrbit(struct wqt_dev *priv, u32 bit) +{ + unsigned long flags; + + spin_lock_irqsave(&status_lock, flags); + IMMR_W32(OMR1_OFFSET, IMMR_R32(OMR1_OFFSET) & ~bit); + spin_unlock_irqrestore(&status_lock, flags); +} + +static int wqtstatus_remote_testbit(struct wqt_dev *priv, u32 bit) +{ + return IMMR_R32(IMR1_OFFSET) & bit; +} + +/*----------------------------------------------------------------------------*/ +/* Message Sending and Processing Operations */ +/*----------------------------------------------------------------------------*/ + +static irqreturn_t wqt_interrupt(int irq, void *dev_id) +{ + struct wqt_dev *priv = dev_id; + u32 imisr, idr; + unsigned long flags; + + imisr = IMMR_R32(IMISR_OFFSET); + idr = IMMR_R32(IDR_OFFSET); + + if (!(imisr & 0x8)) + return IRQ_NONE; + + /* Clear all of the interrupt sources, we'll handle them next */ + IMMR_W32(IDR_OFFSET, idr); + + /* Lock over all of the handlers, so they cannot get called when + * the code doesn't expect them to be called */ + spin_lock_irqsave(&priv->irq_lock, flags); + + if (idr & UART_RX_READY_DBELL) + priv->handlers.uart_rx_ready_handler(priv); + + if (idr & UART_TX_EMPTY_DBELL) + priv->handlers.uart_tx_empty_handler(priv); + + if (idr & NET_RX_PACKET_DBELL) + priv->handlers.net_rx_packet_handler(priv); + + if (idr & NET_TX_COMPLETE_DBELL) + priv->handlers.net_tx_complete_handler(priv); + + if (idr & NET_START_REQ_DBELL) + priv->handlers.net_start_req_handler(priv); + + if (idr & NET_START_ACK_DBELL) + priv->handlers.net_start_ack_handler(priv); + + if (idr & NET_STOP_REQ_DBELL) + priv->handlers.net_stop_req_handler(priv); + + if (idr & NET_STOP_ACK_DBELL) + priv->handlers.net_stop_ack_handler(priv); + + spin_unlock_irqrestore(&priv->irq_lock, flags); + + return IRQ_HANDLED; +} + +/* Send a character through the mbox when it becomes available + * Blocking, must not be called with any spinlocks held */ +static int do_send_message(struct wqt_dev *priv, const char ch) +{ + struct uart_port *port = &priv->port; + bool tmp; + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + while (priv->uart_tx_ready != true) { + spin_unlock_irqrestore(&priv->irq_lock, flags); + wait_event_timeout(priv->uart_tx_wait, priv->uart_tx_ready, HZ); + + spin_lock_irqsave(&port->lock, flags); + tmp = priv->uart_open; + spin_unlock_irqrestore(&port->lock, flags); + + if (!tmp) + return -EIO; + + spin_lock_irqsave(&priv->irq_lock, flags); + } + + /* Now the transmitter is free, send the message */ + IMMR_W32(OMR0_OFFSET, ch); + IMMR_W32(ODR_OFFSET, UART_RX_READY_DBELL); + + /* Mark the transmitter busy */ + priv->uart_tx_ready = false; + spin_unlock_irqrestore(&priv->irq_lock, flags); + return 0; +} + +/* Grab a character out of the uart tx buffer and send it */ +static void uart_tx_work_fn(struct work_struct *work) +{ + struct wqt_dev *priv = container_of(work, struct wqt_dev, uart_tx_work); + struct uart_port *port = &priv->port; + struct circ_buf *xmit = &port->info->xmit; + char ch; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + while (true) { + + /* Check for XON/XOFF (high priority) */ + if (port->x_char) { + ch = port->x_char; + port->x_char = 0; + spin_unlock_irqrestore(&port->lock, flags); + + if (do_send_message(priv, ch)) + return; + + spin_lock_irqsave(&port->lock, flags); + continue; + } + + /* If we're out of chars or the port is stopped, we're done */ + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + wqtuart_stop_tx(port); + break; + } + + /* Grab the next char out of the buffer and send it */ + ch = xmit->buf[xmit->tail]; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + spin_unlock_irqrestore(&port->lock, flags); + + if (do_send_message(priv, ch)) + return; + + spin_lock_irqsave(&port->lock, flags); + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (uart_circ_empty(xmit)) + wqtuart_stop_tx(port); + + spin_unlock_irqrestore(&port->lock, flags); +} + +/*----------------------------------------------------------------------------*/ +/* Interrupt Handlers */ +/*----------------------------------------------------------------------------*/ + +/* NOTE: All handlers are called with priv->irq_lock held */ + +static void empty_handler(struct wqt_dev *priv) +{ + /* Intentionally left empty */ +} + +static void net_start_req_handler(struct wqt_dev *priv) +{ + schedule_work(&priv->net_start_work); +} + +static void net_start_ack_handler(struct wqt_dev *priv) +{ + complete(&priv->net_start_completion); +} + +static void net_stop_req_handler(struct wqt_dev *priv) +{ + schedule_work(&priv->net_stop_work); +} + +static void net_stop_ack_handler(struct wqt_dev *priv) +{ + complete(&priv->net_stop_completion); +} + +static void net_tx_complete_handler(struct wqt_dev *priv) +{ + tasklet_schedule(&priv->tx_complete_tasklet); +} + +static void net_rx_packet_handler(struct wqt_dev *priv) +{ + wqtstatus_setbit(priv, PCINET_NET_RXINT_OFF); + netif_rx_schedule(priv->ndev, &priv->napi); +} + +static void uart_rx_ready_handler(struct wqt_dev *priv) +{ + wqtuart_rx_char(&priv->port, IMMR_R32(IMR0_OFFSET) & 0xff); + IMMR_W32(ODR_OFFSET, UART_TX_EMPTY_DBELL); +} + +static void uart_tx_empty_handler(struct wqt_dev *priv) +{ + priv->uart_tx_ready = true; + wake_up(&priv->uart_tx_wait); +} + +/*----------------------------------------------------------------------------*/ +/* Interrupt Request / Free Helpers */ +/*----------------------------------------------------------------------------*/ + +static void do_enable_net_startstop_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.net_start_req_handler = net_start_req_handler; + priv->handlers.net_start_ack_handler = net_start_ack_handler; + priv->handlers.net_stop_req_handler = net_stop_req_handler; + priv->handlers.net_stop_ack_handler = net_stop_ack_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); + + wqtstatus_setbit(priv, PCINET_NET_STATUS_RUNNING); +} + +static void do_disable_net_startstop_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + wqtstatus_clrbit(priv, PCINET_NET_STATUS_RUNNING); + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.net_start_req_handler = empty_handler; + priv->handlers.net_start_ack_handler = empty_handler; + priv->handlers.net_stop_req_handler = empty_handler; + priv->handlers.net_stop_ack_handler = empty_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void do_enable_net_rxtx_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.net_rx_packet_handler = net_rx_packet_handler; + priv->handlers.net_tx_complete_handler = net_tx_complete_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void do_disable_net_rxtx_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.net_rx_packet_handler = empty_handler; + priv->handlers.net_tx_complete_handler = empty_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void do_enable_uart_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.uart_rx_ready_handler = uart_rx_ready_handler; + priv->handlers.uart_tx_empty_handler = uart_tx_empty_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void do_disable_uart_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.uart_rx_ready_handler = empty_handler; + priv->handlers.uart_tx_empty_handler = empty_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static int wqt_request_irq(struct wqt_dev *priv) +{ + int ret = 0; + + mutex_lock(&priv->irq_mutex); + + if (priv->interrupt_count > 0) + goto out_unlock; + + /* Force all handlers to be disabled before attaching the handler */ + do_disable_net_startstop_handlers(priv); + do_disable_net_rxtx_handlers(priv); + do_disable_uart_handlers(priv); + + ret = request_irq(priv->irq, + wqt_interrupt, + IRQF_SHARED, + priv->ndev->name, + priv); + +out_unlock: + priv->interrupt_count++; + mutex_unlock(&priv->irq_mutex); + + return ret; +} + +static void wqt_free_irq(struct wqt_dev *priv) +{ + mutex_lock(&priv->irq_mutex); + priv->interrupt_count--; + + if (priv->interrupt_count > 0) + goto out_unlock; + + free_irq(priv->irq, priv); + +out_unlock: + mutex_unlock(&priv->irq_mutex); +} + +/*----------------------------------------------------------------------------*/ +/* Network Startup and Shutdown Helpers */ +/*----------------------------------------------------------------------------*/ + +/* NOTE: All helper functions prefixed with "do" must be called only from + * process context, with priv->net_mutex held. They are expected to sleep */ + +static void do_net_start_queues(struct wqt_dev *priv) +{ + if (priv->net_state == NET_STATE_RUNNING) + return; + + dev_dbg(priv->dev, "resetting buffer positions\n"); + priv->cur_rx = priv->rx_base; + priv->cur_tx = priv->tx_base; + priv->dirty_tx = priv->tx_base; + priv->tx_free = PH_RING_SIZE; + + dev_dbg(priv->dev, "enabling NAPI queue\n"); + napi_enable(&priv->napi); + + dev_dbg(priv->dev, "enabling tx_complete() tasklet\n"); + tasklet_enable(&priv->tx_complete_tasklet); + + dev_dbg(priv->dev, "enabling TX queue\n"); + netif_start_queue(priv->ndev); + + dev_dbg(priv->dev, "carrier on!\n"); + netif_carrier_on(priv->ndev); + + /* Enable the RX_PACKET and TX_COMPLETE interrupt handlers */ + do_enable_net_rxtx_handlers(priv); + + priv->net_state = NET_STATE_RUNNING; +} + +static void do_net_stop_queues(struct wqt_dev *priv) +{ + if (priv->net_state == NET_STATE_STOPPED) + return; + + /* Disable the RX_PACKET and TX_COMPLETE interrupt handlers */ + do_disable_net_rxtx_handlers(priv); + + dev_dbg(priv->dev, "disabling NAPI queue\n"); + napi_disable(&priv->napi); + + dev_dbg(priv->dev, "disabling tx_complete() tasklet\n"); + tasklet_disable(&priv->tx_complete_tasklet); + + dev_dbg(priv->dev, "disabling TX queue\n"); + netif_tx_disable(priv->ndev); + + dev_dbg(priv->dev, "carrier off!\n"); + netif_carrier_off(priv->ndev); + + priv->net_state = NET_STATE_STOPPED; +} + +/* Called when we get a request to start our queues and acknowledge */ +static void wqtnet_start_work_fn(struct work_struct *work) +{ + struct wqt_dev *priv = container_of(work, struct wqt_dev, + net_start_work); + + mutex_lock(&priv->net_mutex); + + do_net_start_queues(priv); + IMMR_W32(ODR_OFFSET, NET_START_ACK_DBELL); + + mutex_unlock(&priv->net_mutex); +} + +/* Called when we get a request to stop our queues and acknowledge */ +static void wqtnet_stop_work_fn(struct work_struct *work) +{ + struct wqt_dev *priv = container_of(work, struct wqt_dev, + net_stop_work); + + mutex_lock(&priv->net_mutex); + + do_net_stop_queues(priv); + IMMR_W32(ODR_OFFSET, NET_STOP_ACK_DBELL); + + mutex_unlock(&priv->net_mutex); +} + +/*----------------------------------------------------------------------------*/ +/* DMA Operation Helpers */ +/*----------------------------------------------------------------------------*/ + +/* Setup a static 1GB window starting at PCI address 0x0 + * + * This means that all DMA must be within the first 1GB of the other side's + * memory, which shouldn't be a problem + */ +static int wqtdma_setup_outbound_window(struct wqt_dev *priv) +{ + IMMR_W32BE(LAWAR0_OFFSET, LAWAR0_ENABLE | 0x1d); + IMMR_W32BE(POCMR0_OFFSET, POCMR0_ENABLE | 0xc0000); + IMMR_W32BE(POTAR0_OFFSET, 0x0); + + return 0; +} + +static enum dma_state_client dmatest_event(struct dma_client *client, + struct dma_chan *chan, + enum dma_state state) +{ + struct wqt_dev *priv = container_of(client, struct wqt_dev, client); + enum dma_state_client ack = DMA_NAK; + + switch (state) { + case DMA_RESOURCE_AVAILABLE: + if (chan == priv->chan) + ack = DMA_DUP; + else if (priv->chan) + ack = DMA_NAK; + else { + priv->chan = chan; + ack = DMA_ACK; + } + + break; + + case DMA_RESOURCE_REMOVED: + priv->chan = NULL; + ack = DMA_ACK; + break; + + default: + dev_dbg(priv->dev, "unhandled DMA event %u (%s)\n", + state, chan->dev.bus_id); + break; + } + + return ack; +} + +static dma_cookie_t dma_async_memcpy_raw_to_buf(struct dma_chan *chan, + void *dest, + dma_addr_t src, + size_t len) +{ + struct dma_device *dev = chan->device; + struct dma_async_tx_descriptor *tx; + dma_addr_t dma_dest, dma_src; + dma_cookie_t cookie; + int cpu; + + dma_src = src; + dma_dest = dma_map_single(dev->dev, dest, len, DMA_FROM_DEVICE); + tx = dev->device_prep_dma_memcpy(chan, dma_dest, dma_src, len, + DMA_CTRL_ACK); + + if (!tx) { + dma_unmap_single(dev->dev, dma_dest, len, DMA_FROM_DEVICE); + return -ENOMEM; + } + + tx->callback = NULL; + cookie = tx->tx_submit(tx); + + cpu = get_cpu(); + per_cpu_ptr(chan->local, cpu)->bytes_transferred += len; + per_cpu_ptr(chan->local, cpu)->memcpy_count++; + put_cpu(); + + return cookie; +} + +static dma_cookie_t dma_async_memcpy_buf_to_raw(struct dma_chan *chan, + dma_addr_t dest, + void *src, + size_t len) +{ + struct dma_device *dev = chan->device; + struct dma_async_tx_descriptor *tx; + dma_addr_t dma_dest, dma_src; + dma_cookie_t cookie; + int cpu; + + dma_src = dma_map_single(dev->dev, src, len, DMA_TO_DEVICE); + dma_dest = dest; + tx = dev->device_prep_dma_memcpy(chan, dma_dest, dma_src, len, + DMA_CTRL_ACK); + + if (!tx) { + dma_unmap_single(dev->dev, dma_src, len, DMA_TO_DEVICE); + return -ENOMEM; + } + + tx->callback = NULL; + cookie = tx->tx_submit(tx); + + cpu = get_cpu(); + per_cpu_ptr(chan->local, cpu)->bytes_transferred += len; + per_cpu_ptr(chan->local, cpu)->memcpy_count++; + put_cpu(); + + return cookie; +} + +/*----------------------------------------------------------------------------*/ +/* Network Device Operations */ +/*----------------------------------------------------------------------------*/ + +static int wqt_open(struct net_device *dev) +{ + struct wqt_dev *priv = netdev_priv(dev); + int ret; + + /* Pretend the cable is unplugged until we are up and running */ + netif_carrier_off(dev); + + mutex_lock(&priv->net_mutex); + + ret = wqt_request_irq(priv); + + if (ret) + goto out_unlock; + + /* Enable only the network start/stop interrupts */ + do_enable_net_startstop_handlers(priv); + + /* Check if the other side is running, if not, it will start us. + * Without the interrupt handler installed, there's no way it can + * respond to us anyway */ + if (!wqtstatus_remote_testbit(priv, PCINET_NET_STATUS_RUNNING)) { + ret = 0; + goto out_unlock; + } + + IMMR_W32(ODR_OFFSET, NET_START_REQ_DBELL); + ret = wait_for_completion_timeout(&priv->net_start_completion, 5*HZ); + + if (!ret) { + /* Our start request timed out, therefore, the other + * side will start us when it comes back up */ + dev_dbg(priv->dev, "start timed out\n"); + } else { + do_net_start_queues(priv); + ret = 0; + } + +out_unlock: + mutex_unlock(&priv->net_mutex); + return ret; +} + +static int wqt_stop(struct net_device *dev) +{ + struct wqt_dev *priv = netdev_priv(dev); + int ret; + + mutex_lock(&priv->net_mutex); + + do_net_stop_queues(priv); + + IMMR_W32(ODR_OFFSET, NET_STOP_REQ_DBELL); + ret = wait_for_completion_timeout(&priv->net_stop_completion, 5*HZ); + + if (!ret) + dev_warn(priv->dev, "other side did not stop on time!\n"); + else + ret = 0; + + do_disable_net_startstop_handlers(priv); + wqt_free_irq(priv); + + mutex_unlock(&priv->net_mutex); + return 0; +} + +static int wqt_change_mtu(struct net_device *dev, int new_mtu) +{ + if ((new_mtu < 68) || (new_mtu > PH_MAX_MTU)) + return -EINVAL; + + dev->mtu = new_mtu; + return 0; +} + +static int wqt_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct wqt_dev *priv = netdev_priv(dev); + dma_cookie_t cookie; + enum dma_status status; + struct circ_buf_desc __iomem *bdp; + int dirty_idx; + + spin_lock_bh(&priv->net_lock); + + bdp = priv->cur_tx; + dirty_idx = bdp - priv->tx_base; + + /* This should not happen, the queue should be stopped */ + if (priv->tx_free == 0 || CBDR_SC(bdp) != BD_MEM_READY) { + netif_stop_queue(dev); + spin_unlock_bh(&priv->net_lock); + return NETDEV_TX_BUSY; + } + + cookie = dma_async_memcpy_buf_to_raw(priv->chan, + (dma_addr_t)(0x80000000 + CBDR_ADDR(bdp)), + skb->data, + skb->len); + + if (dma_submit_error(cookie)) { + dev_warn(priv->dev, "DMA submit error\n"); + spin_unlock_bh(&priv->net_lock); + return -ENOMEM; + } + + status = dma_sync_wait(priv->chan, cookie); + + if (status == DMA_ERROR) { + dev_warn(priv->dev, "DMA error\n"); + spin_unlock_bh(&priv->net_lock); + return -EIO; + } + + CBDW_LEN(bdp, skb->len); + CBDW_SC(bdp, BD_MEM_DIRTY); + + if (dirty_idx == PH_RING_SIZE - 1) + bdp = priv->tx_base; + else + bdp++; + + priv->tx_skbs[dirty_idx] = skb; + priv->cur_tx = bdp; + priv->tx_free--; + dev->trans_start = jiffies; + + if (priv->tx_free == 0) + netif_stop_queue(dev); + + if (!wqtstatus_remote_testbit(priv, PCINET_NET_RXINT_OFF)) + IMMR_W32(ODR_OFFSET, NET_RX_PACKET_DBELL); + + spin_unlock_bh(&priv->net_lock); + return NETDEV_TX_OK; +} + +static void wqt_tx_timeout(struct net_device *dev) +{ + struct wqt_dev *priv = netdev_priv(dev); + + dev->stats.tx_errors++; + IMMR_W32(ODR_OFFSET, NET_RX_PACKET_DBELL); +} + +static void wqt_tx_complete(unsigned long data) +{ + struct net_device *dev = (struct net_device *)data; + struct wqt_dev *priv = netdev_priv(dev); + struct sk_buff *skb; + struct circ_buf_desc __iomem *bdp; + int do_wake, dirty_idx; + + spin_lock_bh(&priv->net_lock); + + bdp = priv->dirty_tx; + do_wake = 0; + + while (CBDR_SC(bdp) == BD_MEM_FREE) { + dirty_idx = bdp - priv->tx_base; + + skb = priv->tx_skbs[dirty_idx]; + + BUG_ON(skb == NULL); + + dev->stats.tx_bytes += skb->len; + dev->stats.tx_packets++; + + dev_kfree_skb_irq(skb); + + priv->tx_skbs[dirty_idx] = NULL; + + /* Mark the BDP as ready */ + CBDW_SC(bdp, BD_MEM_READY); + + /* Update the bdp */ + if (dirty_idx == PH_RING_SIZE - 1) + bdp = priv->tx_base; + else + bdp++; + + if (!priv->tx_free++) + do_wake = 1; + } + + priv->dirty_tx = bdp; + + spin_unlock_bh(&priv->net_lock); + + if (do_wake) + netif_wake_queue(dev); +} + +static int wqt_rx_napi(struct napi_struct *napi, int budget) +{ + struct wqt_dev *priv = container_of(napi, struct wqt_dev, napi); + struct net_device *dev = priv->ndev; + int received = 0; + struct sk_buff *skb; + dma_addr_t remote_addr; + dma_cookie_t cookie; + enum dma_status status; + int pkt_len, dirty_idx; + struct circ_buf_desc __iomem *bdp; + + bdp = priv->cur_rx; + + while (CBDR_SC(bdp) == BD_MEM_DIRTY) { + dirty_idx = bdp - priv->rx_base; + + pkt_len = CBDR_LEN(bdp); + remote_addr = CBDR_ADDR(bdp); + + /* Allocate a packet for the data */ + skb = dev_alloc_skb(pkt_len + NET_IP_ALIGN); + + if (skb == NULL) { + dev->stats.rx_dropped++; + goto out_err; + } + + skb_reserve(skb, NET_IP_ALIGN); + + cookie = dma_async_memcpy_raw_to_buf(priv->chan, + skb->data, + (dma_addr_t)(0x80000000 + remote_addr), + pkt_len); + + if (dma_submit_error(cookie)) { + dev_warn(priv->dev, "DMA submit error\n"); + dev_kfree_skb_irq(skb); + dev->stats.rx_dropped++; + goto out_err; + } + + status = dma_sync_wait(priv->chan, cookie); + + if (status == DMA_ERROR) { + dev_warn(priv->dev, "DMA error\n"); + dev_kfree_skb_irq(skb); + dev->stats.rx_dropped++; + goto out_err; + } + + /* Push the packet into the network stack */ + skb_put(skb, pkt_len); + skb->protocol = eth_type_trans(skb, dev); +#ifdef CONFIG_PCINET_DISABLE_CHECKSUM + skb->ip_summed = CHECKSUM_UNNECESSARY; +#else + skb->ip_summed = CHECKSUM_NONE; +#endif + netif_receive_skb(skb); + received++; + dev->stats.rx_bytes += pkt_len; + dev->stats.rx_packets++; + +out_err: + CBDW_SC(bdp, BD_MEM_FREE); + + if (dirty_idx == PH_RING_SIZE - 1) + bdp = priv->rx_base; + else + bdp++; + + if (received >= budget) + break; + } + + priv->cur_rx = bdp; + + /* We have processed all packets that the adapter had, but it + * was less than our budget, stop polling */ + if (received < budget) { + netif_rx_complete(dev, napi); + wqtstatus_clrbit(priv, PCINET_NET_RXINT_OFF); + } + + IMMR_W32(ODR_OFFSET, NET_TX_COMPLETE_DBELL); + + return received; +} + +/*----------------------------------------------------------------------------*/ +/* UART Device Operations */ +/*----------------------------------------------------------------------------*/ + +static unsigned int wqtuart_tx_empty(struct uart_port *port) +{ + return TIOCSER_TEMT; +} + +static void wqtuart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +static unsigned int wqtuart_get_mctrl(struct uart_port *port) +{ + return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS; +} + +static void wqtuart_stop_tx(struct uart_port *port) +{ +} + +static void wqtuart_start_tx(struct uart_port *port) +{ + struct wqt_dev *priv = container_of(port, struct wqt_dev, port); + + queue_work(priv->wq, &priv->uart_tx_work); +} + +static void wqtuart_stop_rx(struct uart_port *port) +{ + struct wqt_dev *priv = container_of(port, struct wqt_dev, port); + + do_disable_uart_handlers(priv); + priv->uart_rx_enabled = false; + wqtstatus_clrbit(priv, PCINET_UART_RX_ENABLED); +} + +static void wqtuart_enable_ms(struct uart_port *port) +{ +} + +static void wqtuart_break_ctl(struct uart_port *port, int break_state) +{ +} + +static int wqtuart_startup(struct uart_port *port) +{ + struct wqt_dev *priv = container_of(port, struct wqt_dev, port); + int ret; + + ret = wqt_request_irq(priv); + + if (ret) + return ret; + + do_enable_uart_handlers(priv); + + /* Mark the transmitter and receiver ready */ + priv->uart_tx_ready = true; + priv->uart_rx_enabled = true; + wqtstatus_setbit(priv, PCINET_UART_RX_ENABLED); + + /* Let the other side know that we are ready to receive chars now */ + IMMR_W32(ODR_OFFSET, UART_TX_EMPTY_DBELL); + priv->uart_open = true; + return 0; +} + +static void wqtuart_shutdown(struct uart_port *port) +{ + struct wqt_dev *priv = container_of(port, struct wqt_dev, port); + + wqt_free_irq(priv); + + /* Make sure the uart_tx_work_fn() exits cleanly */ + priv->uart_open = false; + wake_up(&priv->uart_tx_wait); +} + +static void wqtuart_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ +} + +static const char *wqtuart_type(struct uart_port *port) +{ + return "WQTUART"; +} + +static int wqtuart_request_port(struct uart_port *port) +{ + return 0; +} + +static void wqtuart_config_port(struct uart_port *port, int flags) +{ +} + +static void wqtuart_release_port(struct uart_port *port) +{ +} + +static int wqtuart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + return 0; +} + +static void wqtuart_rx_char(struct uart_port *port, const char ch) +{ + struct wqt_dev *priv = container_of(port, struct wqt_dev, port); + struct tty_struct *tty; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + + if (priv->uart_rx_enabled) { + tty = port->info->port.tty; + tty_insert_flip_char(tty, ch, TTY_NORMAL); + tty_flip_buffer_push(tty); + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +static struct uart_ops wqtuart_ops = { + .tx_empty = wqtuart_tx_empty, + .set_mctrl = wqtuart_set_mctrl, + .get_mctrl = wqtuart_get_mctrl, + .stop_tx = wqtuart_stop_tx, + .start_tx = wqtuart_start_tx, + .stop_rx = wqtuart_stop_rx, + .enable_ms = wqtuart_enable_ms, + .break_ctl = wqtuart_break_ctl, + .startup = wqtuart_startup, + .shutdown = wqtuart_shutdown, + .set_termios = wqtuart_set_termios, + .type = wqtuart_type, + .release_port = wqtuart_release_port, + .request_port = wqtuart_request_port, + .config_port = wqtuart_config_port, + .verify_port = wqtuart_verify_port, +}; + +static struct uart_driver wqtuart_driver = { + .owner = THIS_MODULE, + .driver_name = driver_name, + .dev_name = "ttyPCI", + .major = 240, + .minor = 0, + .nr = 1, +}; + +/*----------------------------------------------------------------------------*/ +/* Network Registers */ +/*----------------------------------------------------------------------------*/ + +static void wqt_free_netregs(struct wqt_dev *priv) +{ + BUG_ON(priv->netregs == NULL); + BUG_ON(priv->netregs_addr == 0x0); + + dma_free_coherent(priv->dev, + PAGE_SIZE, + priv->netregs, + priv->netregs_addr); + + priv->netregs = NULL; + priv->netregs_addr = 0x0; +} + +static int wqt_init_netregs(struct wqt_dev *priv) +{ + u32 val; + + BUG_ON(priv->netregs != NULL); + BUG_ON(priv->netregs_addr != 0x0); + + /* Check the PCI Inbound Window Attributes Register 0 for a 4k window + * This is PCI BAR1, and will be used as network device registers */ + val = IMMR_R32BE(PIWAR0_OFFSET); + val = val & (PIWAR0_ENABLED | PIWAR0_IWS_4K); + + if (val != (PIWAR0_ENABLED | PIWAR0_IWS_4K)) { + dev_dbg(priv->dev, "PIWAR0 set up incorrectly\n"); + return -ENODEV; + } + + priv->netregs = dma_alloc_coherent(priv->dev, + PAGE_SIZE, + &priv->netregs_addr, + GFP_KERNEL); + + if (!priv->netregs) { + dev_dbg(priv->dev, "Unable to allocate netregs\n"); + return -ENOMEM; + } + + /* Write the page address into the address register */ + IMMR_W32BE(PITAR0_OFFSET, priv->netregs_addr >> 12); + return 0; +} + +/*----------------------------------------------------------------------------*/ +/* OpenFirmware Device Subsystem */ +/*----------------------------------------------------------------------------*/ + +static int wqt_probe(struct of_device *op, const struct of_device_id *match) +{ + struct net_device *ndev; + struct wqt_dev *priv; + int ret; + + ndev = alloc_etherdev(sizeof(*priv)); + + if (!ndev) { + ret = -ENOMEM; + goto out_alloc_ndev; + } + + dev_set_drvdata(&op->dev, ndev); + priv = netdev_priv(ndev); + priv->op = op; + priv->dev = &op->dev; + priv->ndev = ndev; + + spin_lock_init(&priv->irq_lock); + mutex_init(&priv->irq_mutex); + + /* Hardware Initialization */ + priv->irq = irq_of_parse_and_map(op->node, 0); + priv->immr = ioremap(get_immrbase(), 0x100000); + + if (!priv->immr) { + ret = -ENOMEM; + goto out_ioremap_immr; + } + + ret = wqt_init_netregs(priv); + + if (ret) + goto out_init_netregs; + + /* NOTE: Yes, this is correct. Everything was written as if this + * NOTE: side *is* a network card. So the place the card is + * NOTE: receiving from is the other side's TX buffers */ + priv->rx_base = priv->netregs + PCINET_TXBD_BASE; + priv->tx_base = priv->netregs + PCINET_RXBD_BASE; + wqtstatus_setbit(priv, PCINET_NET_REGISTERS_VALID); + + /* DMA Client */ + wqtdma_setup_outbound_window(priv); + priv->client.event_callback = dmatest_event; + dma_cap_set(DMA_MEMCPY, priv->client.cap_mask); + dma_async_client_register(&priv->client); + dma_async_client_chan_request(&priv->client); + + /* Initialize private data */ + priv->wq = create_singlethread_workqueue(driver_name); + + if (!priv->wq) { + ret = -ENOMEM; + goto out_create_workqueue; + } + + INIT_WORK(&priv->uart_tx_work, uart_tx_work_fn); + init_waitqueue_head(&priv->uart_tx_wait); + priv->uart_tx_ready = true; + + tasklet_init(&priv->tx_complete_tasklet, wqt_tx_complete, + (unsigned long)ndev); + tasklet_disable(&priv->tx_complete_tasklet); + spin_lock_init(&priv->net_lock); + + mutex_init(&priv->net_mutex); + priv->net_state = NET_STATE_STOPPED; + INIT_WORK(&priv->net_start_work, wqtnet_start_work_fn); + INIT_WORK(&priv->net_stop_work, wqtnet_stop_work_fn); + init_completion(&priv->net_start_completion); + init_completion(&priv->net_stop_completion); + + /* Mask all of the MBOX interrupts */ + IMMR_W32(IMIMR_OFFSET, 0x1 | 0x2); + + /* Network Device */ + random_ether_addr(ndev->dev_addr); + + ndev->open = wqt_open; + ndev->stop = wqt_stop; + ndev->change_mtu = wqt_change_mtu; + ndev->hard_start_xmit = wqt_hard_start_xmit; + ndev->tx_timeout = wqt_tx_timeout; + ndev->watchdog_timeo = HZ/4; + ndev->flags &= ~IFF_MULTICAST; /* No multicast support */ +#ifdef CONFIG_PCINET_DISABLE_CHECKSUM + ndev->features |= NETIF_F_NO_CSUM; /* No checksum needed */ +#endif + ndev->mtu = PH_MAX_MTU; + netif_napi_add(ndev, &priv->napi, wqt_rx_napi, PH_RING_SIZE); + + ret = register_netdev(ndev); + + if (ret) + goto out_register_netdev; + + /* UART Device */ + priv->port.ops = &wqtuart_ops; + priv->port.type = PORT_16550A; + priv->port.dev = &op->dev; + priv->port.line = 0; + spin_lock_init(&priv->port.lock); + + ret = uart_add_one_port(&wqtuart_driver, &priv->port); + + if (ret) + goto out_add_uart_port; + + dev_info(priv->dev, "using ethernet device %s\n", ndev->name); + dev_info(priv->dev, "using serial device %s%d\n", + wqtuart_driver.dev_name, priv->port.line); + return 0; + +out_add_uart_port: + unregister_netdev(ndev); +out_register_netdev: + destroy_workqueue(priv->wq); +out_create_workqueue: + wqt_free_netregs(priv); +out_init_netregs: + iounmap(priv->immr); +out_ioremap_immr: + free_netdev(ndev); +out_alloc_ndev: + return ret; +} + +static int wqt_remove(struct of_device *op) +{ + struct net_device *ndev = dev_get_drvdata(&op->dev); + struct wqt_dev *priv = netdev_priv(ndev); + + uart_remove_one_port(&wqtuart_driver, &priv->port); + unregister_netdev(priv->ndev); + + flush_workqueue(priv->wq); + destroy_workqueue(priv->wq); + + wqtstatus_clrbit(priv, PCINET_NET_REGISTERS_VALID); + wqt_free_netregs(priv); + + dma_async_client_unregister(&priv->client); + + iounmap(priv->immr); + + free_netdev(ndev); + + return 0; +} + +static struct of_device_id wqt_match[] = { + { .compatible = "fsl,mpc8349-mu", }, + {}, +}; + +static struct of_platform_driver wqt_of_driver = { + .owner = THIS_MODULE, + .name = driver_name, + .match_table = wqt_match, + .probe = wqt_probe, + .remove = wqt_remove, +}; + +/*----------------------------------------------------------------------------*/ +/* DMA Client Infrastructure */ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/* Module Init / Exit */ +/*----------------------------------------------------------------------------*/ + +static int __init wqt_init(void) +{ + int ret; + + ret = uart_register_driver(&wqtuart_driver); + + if (ret) + goto out_uart_register_driver; + + ret = of_register_platform_driver(&wqt_of_driver); + + if (ret) + goto out_of_register_platform_driver; + + return 0; + +out_of_register_platform_driver: + uart_unregister_driver(&wqtuart_driver); +out_uart_register_driver: + return ret; +} + +static void __exit wqt_exit(void) +{ + of_unregister_platform_driver(&wqt_of_driver); + uart_unregister_driver(&wqtuart_driver); +} + +MODULE_AUTHOR("Ira W. Snyder "); +MODULE_DESCRIPTION("PCINet/PCISerial Driver for MPC8349EMDS"); +MODULE_LICENSE("GPL"); + +module_init(wqt_init); +module_exit(wqt_exit); diff --git a/drivers/net/pcinet_host.c b/drivers/net/pcinet_host.c new file mode 100644 index 0000000..378ab91 --- /dev/null +++ b/drivers/net/pcinet_host.c @@ -0,0 +1,1383 @@ +/* + * PCINet and PCISerial Driver for Freescale MPC8349EMDS (Host side) + * + * Copyright (c) 2008 Ira W. Snyder + * + * Heavily inspired by the drivers/net/fs_enet driver + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcinet.h" +#include "pcinet_hw.h" + +static const char driver_name[] = "wqt"; + +static void wqtuart_rx_char(struct uart_port *port, const char ch); +static void wqtuart_stop_tx(struct uart_port *port); + +struct wqt_dev; +typedef void (*wqt_irqhandler_t)(struct wqt_dev *); + +struct wqt_irqhandlers { + wqt_irqhandler_t net_start_req_handler; + wqt_irqhandler_t net_start_ack_handler; + wqt_irqhandler_t net_stop_req_handler; + wqt_irqhandler_t net_stop_ack_handler; + wqt_irqhandler_t net_rx_packet_handler; + wqt_irqhandler_t net_tx_complete_handler; + wqt_irqhandler_t uart_rx_ready_handler; + wqt_irqhandler_t uart_tx_empty_handler; +}; + +struct wqt_dev { + /*--------------------------------------------------------------------*/ + /* PCI Infrastructure */ + /*--------------------------------------------------------------------*/ + struct pci_dev *pdev; + struct device *dev; + void __iomem *immr; + + struct mutex irq_mutex; + int interrupt_count; + + spinlock_t irq_lock; + struct wqt_irqhandlers handlers; + + /*--------------------------------------------------------------------*/ + /* UART Device Infrastructure */ + /*--------------------------------------------------------------------*/ + struct uart_port port; + bool uart_rx_enabled; + bool uart_open; + + struct workqueue_struct *wq; + struct work_struct uart_tx_work; + wait_queue_head_t uart_tx_wait; /* sleep for uart_tx_ready */ + bool uart_tx_ready; /* transmitter state */ + + /*--------------------------------------------------------------------*/ + /* Ethernet Device Infrastructure */ + /*--------------------------------------------------------------------*/ + struct net_device *ndev; + void __iomem *netregs; + + /* Outstanding SKB */ + struct sk_buff *rx_skbs[PH_RING_SIZE]; + struct sk_buff *tx_skbs[PH_RING_SIZE]; + dma_addr_t rx_skb_addrs[PH_RING_SIZE]; + dma_addr_t tx_skb_addrs[PH_RING_SIZE]; + + /* Circular Buffer Descriptor base */ + struct circ_buf_desc __iomem *rx_base; + struct circ_buf_desc __iomem *tx_base; + + /* Current SKB index */ + struct circ_buf_desc __iomem *cur_rx; + struct circ_buf_desc __iomem *cur_tx; + struct circ_buf_desc __iomem *dirty_tx; + int tx_free; + + struct tasklet_struct tx_complete_tasklet; + spinlock_t net_lock; + + struct mutex net_mutex; + int net_state; + struct work_struct net_start_work; + struct work_struct net_stop_work; + struct completion net_start_completion; + struct completion net_stop_completion; + struct napi_struct napi; +}; + +/*----------------------------------------------------------------------------*/ +/* Status Register Helper Operations */ +/*----------------------------------------------------------------------------*/ + +static DEFINE_SPINLOCK(status_lock); + +static void wqtstatus_setbit(struct wqt_dev *priv, u32 bit) +{ + unsigned long flags; + + spin_lock_irqsave(&status_lock, flags); + IMMR_W32(IMR1_OFFSET, IMMR_R32(IMR1_OFFSET) | bit); + spin_unlock_irqrestore(&status_lock, flags); +} + +static void wqtstatus_clrbit(struct wqt_dev *priv, u32 bit) +{ + unsigned long flags; + + spin_lock_irqsave(&status_lock, flags); + IMMR_W32(IMR1_OFFSET, IMMR_R32(IMR1_OFFSET) & ~bit); + spin_unlock_irqrestore(&status_lock, flags); +} + +static int wqtstatus_remote_testbit(struct wqt_dev *priv, u32 bit) +{ + return IMMR_R32(OMR1_OFFSET) & bit; +} + +/*----------------------------------------------------------------------------*/ +/* Message Sending and Processing Operations */ +/*----------------------------------------------------------------------------*/ + +static irqreturn_t wqt_interrupt(int irq, void *dev_id) +{ + struct wqt_dev *priv = dev_id; + u32 omisr, odr; + unsigned long flags; + + omisr = IMMR_R32(OMISR_OFFSET); + odr = IMMR_R32(ODR_OFFSET); + + if (!(omisr & 0x8)) + return IRQ_NONE; + + /* Clear all of the interrupt sources, we'll handle them next */ + IMMR_W32(ODR_OFFSET, odr); + + /* Lock over all of the handlers, so they cannot get called when + * the code doesn't expect them to be called */ + spin_lock_irqsave(&priv->irq_lock, flags); + + if (odr & UART_RX_READY_DBELL) + priv->handlers.uart_rx_ready_handler(priv); + + if (odr & UART_TX_EMPTY_DBELL) + priv->handlers.uart_tx_empty_handler(priv); + + if (odr & NET_RX_PACKET_DBELL) + priv->handlers.net_rx_packet_handler(priv); + + if (odr & NET_TX_COMPLETE_DBELL) + priv->handlers.net_tx_complete_handler(priv); + + if (odr & NET_START_REQ_DBELL) + priv->handlers.net_start_req_handler(priv); + + if (odr & NET_START_ACK_DBELL) + priv->handlers.net_start_ack_handler(priv); + + if (odr & NET_STOP_REQ_DBELL) + priv->handlers.net_stop_req_handler(priv); + + if (odr & NET_STOP_ACK_DBELL) + priv->handlers.net_stop_ack_handler(priv); + + spin_unlock_irqrestore(&priv->irq_lock, flags); + + return IRQ_HANDLED; +} + +/* Send a character through the mbox when it becomes available + * Blocking, must not be called with any spinlocks held */ +static int do_send_message(struct wqt_dev *priv, const char ch) +{ + struct uart_port *port = &priv->port; + bool tmp; + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + while (priv->uart_tx_ready != true) { + spin_unlock_irqrestore(&priv->irq_lock, flags); + wait_event_timeout(priv->uart_tx_wait, priv->uart_tx_ready, HZ); + + spin_lock_irqsave(&port->lock, flags); + tmp = priv->uart_open; + spin_unlock_irqrestore(&port->lock, flags); + + if (!tmp) + return -EIO; + + spin_lock_irqsave(&priv->irq_lock, flags); + } + + /* Now the transmitter is free, send the message */ + IMMR_W32(IMR0_OFFSET, ch); + IMMR_W32(IDR_OFFSET, UART_RX_READY_DBELL); + + /* Mark the transmitter busy */ + priv->uart_tx_ready = false; + spin_unlock_irqrestore(&priv->irq_lock, flags); + return 0; +} + +/* Grab a character out of the uart tx buffer and send it */ +static void uart_tx_work_fn(struct work_struct *work) +{ + struct wqt_dev *priv = container_of(work, struct wqt_dev, uart_tx_work); + struct uart_port *port = &priv->port; + struct circ_buf *xmit = &port->info->xmit; + char ch; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + while (true) { + + /* Check for XON/XOFF (high priority) */ + if (port->x_char) { + ch = port->x_char; + port->x_char = 0; + spin_unlock_irqrestore(&port->lock, flags); + + if (do_send_message(priv, ch)) + return; + + spin_lock_irqsave(&port->lock, flags); + continue; + } + + /* If we're out of chars or the port is stopped, we're done */ + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + wqtuart_stop_tx(port); + break; + } + + /* Grab the next char out of the buffer and send it */ + ch = xmit->buf[xmit->tail]; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + spin_unlock_irqrestore(&port->lock, flags); + + if (do_send_message(priv, ch)) + return; + + spin_lock_irqsave(&port->lock, flags); + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (uart_circ_empty(xmit)) + wqtuart_stop_tx(port); + + spin_unlock_irqrestore(&port->lock, flags); +} + +/*----------------------------------------------------------------------------*/ +/* Interrupt Handlers */ +/*----------------------------------------------------------------------------*/ + +/* NOTE: All handlers are called with priv->irq_lock held */ + +static void empty_handler(struct wqt_dev *priv) +{ + /* Intentionally left empty */ +} + +static void net_start_req_handler(struct wqt_dev *priv) +{ + schedule_work(&priv->net_start_work); +} + +static void net_start_ack_handler(struct wqt_dev *priv) +{ + complete(&priv->net_start_completion); +} + +static void net_stop_req_handler(struct wqt_dev *priv) +{ + schedule_work(&priv->net_stop_work); +} + +static void net_stop_ack_handler(struct wqt_dev *priv) +{ + complete(&priv->net_stop_completion); +} + +static void net_tx_complete_handler(struct wqt_dev *priv) +{ + tasklet_schedule(&priv->tx_complete_tasklet); +} + +static void net_rx_packet_handler(struct wqt_dev *priv) +{ + wqtstatus_setbit(priv, PCINET_NET_RXINT_OFF); + netif_rx_schedule(priv->ndev, &priv->napi); +} + +static void uart_rx_ready_handler(struct wqt_dev *priv) +{ + wqtuart_rx_char(&priv->port, IMMR_R32(OMR0_OFFSET) & 0xff); + IMMR_W32(IDR_OFFSET, UART_TX_EMPTY_DBELL); +} + +static void uart_tx_empty_handler(struct wqt_dev *priv) +{ + priv->uart_tx_ready = true; + wake_up(&priv->uart_tx_wait); +} + +/*----------------------------------------------------------------------------*/ +/* Interrupt Request / Free Helpers */ +/*----------------------------------------------------------------------------*/ + +static void do_enable_net_startstop_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.net_start_req_handler = net_start_req_handler; + priv->handlers.net_start_ack_handler = net_start_ack_handler; + priv->handlers.net_stop_req_handler = net_stop_req_handler; + priv->handlers.net_stop_ack_handler = net_stop_ack_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); + + wqtstatus_setbit(priv, PCINET_NET_STATUS_RUNNING); +} + +static void do_disable_net_startstop_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + wqtstatus_clrbit(priv, PCINET_NET_STATUS_RUNNING); + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.net_start_req_handler = empty_handler; + priv->handlers.net_start_ack_handler = empty_handler; + priv->handlers.net_stop_req_handler = empty_handler; + priv->handlers.net_stop_ack_handler = empty_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void do_enable_net_rxtx_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.net_rx_packet_handler = net_rx_packet_handler; + priv->handlers.net_tx_complete_handler = net_tx_complete_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void do_disable_net_rxtx_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.net_rx_packet_handler = empty_handler; + priv->handlers.net_tx_complete_handler = empty_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void do_enable_uart_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.uart_rx_ready_handler = uart_rx_ready_handler; + priv->handlers.uart_tx_empty_handler = uart_tx_empty_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void do_disable_uart_handlers(struct wqt_dev *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + priv->handlers.uart_rx_ready_handler = empty_handler; + priv->handlers.uart_tx_empty_handler = empty_handler; + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static int wqt_request_irq(struct wqt_dev *priv) +{ + int ret = 0; + + mutex_lock(&priv->irq_mutex); + + if (priv->interrupt_count > 0) + goto out_unlock; + + /* Force all handlers to be disabled before attaching the handler */ + do_disable_net_startstop_handlers(priv); + do_disable_net_rxtx_handlers(priv); + do_disable_uart_handlers(priv); + + ret = request_irq(priv->pdev->irq, + wqt_interrupt, + IRQF_SHARED, + priv->ndev->name, + priv); + +out_unlock: + priv->interrupt_count++; + mutex_unlock(&priv->irq_mutex); + + return ret; +} + +static void wqt_free_irq(struct wqt_dev *priv) +{ + mutex_lock(&priv->irq_mutex); + priv->interrupt_count--; + + if (priv->interrupt_count > 0) + goto out_unlock; + + free_irq(priv->pdev->irq, priv); + +out_unlock: + mutex_unlock(&priv->irq_mutex); +} + +/*----------------------------------------------------------------------------*/ +/* Network Startup and Shutdown Helpers */ +/*----------------------------------------------------------------------------*/ + +/* NOTE: All helper functions prefixed with "do" must be called only from + * process context, with priv->net_mutex held. They are expected to sleep */ + +/* NOTE: queues must be stopped before initializing and uninitializing */ + +static void do_net_initialize_board(struct wqt_dev *priv) +{ + int i; + struct circ_buf_desc __iomem *bdp; + + BUG_ON(!wqtstatus_remote_testbit(priv, PCINET_NET_REGISTERS_VALID)); + + /* Fill in RX ring */ + for (i = 0, bdp = priv->rx_base; i < PH_RING_SIZE; bdp++, i++) { + CBDW_SC(bdp, BD_MEM_READY); + CBDW_LEN(bdp, PH_MAX_FRSIZE); + CBDW_ADDR(bdp, priv->rx_skb_addrs[i]); + } + + /* Fill in TX ring */ + for (i = 0, bdp = priv->tx_base; i < PH_RING_SIZE; bdp++, i++) { + CBDW_SC(bdp, BD_MEM_READY); + CBDW_LEN(bdp, 0); + CBDW_ADDR(bdp, 0x0); + } +} + +static void do_net_uninitialize_board(struct wqt_dev *priv) +{ + struct sk_buff *skb; + dma_addr_t skb_addr; + struct circ_buf_desc __iomem *bdp; + int i; + + /* Reset TX ring */ + for (i = 0, bdp = priv->tx_base; i < PH_RING_SIZE; bdp++, i++) { + if (priv->tx_skbs[i]) { + skb = priv->tx_skbs[i]; + skb_addr = priv->tx_skb_addrs[i]; + + dma_unmap_single(priv->dev, + skb_addr, + skb->len, + DMA_TO_DEVICE); + + dev_kfree_skb(skb); + + priv->tx_skbs[i] = NULL; + priv->tx_skb_addrs[i] = 0x0; + } + + CBDW_SC(bdp, BD_MEM_READY); + CBDW_LEN(bdp, 0); + CBDW_ADDR(bdp, 0x0); + } +} + +static void do_net_start_queues(struct wqt_dev *priv) +{ + if (priv->net_state == NET_STATE_RUNNING) + return; + + dev_dbg(priv->dev, "resetting buffer positions\n"); + priv->cur_rx = priv->rx_base; + priv->cur_tx = priv->tx_base; + priv->dirty_tx = priv->tx_base; + priv->tx_free = PH_RING_SIZE; + + dev_dbg(priv->dev, "enabling NAPI queue\n"); + napi_enable(&priv->napi); + + dev_dbg(priv->dev, "enabling tx_complete() tasklet\n"); + tasklet_enable(&priv->tx_complete_tasklet); + + dev_dbg(priv->dev, "enabling TX queue\n"); + netif_start_queue(priv->ndev); + + dev_dbg(priv->dev, "carrier on!\n"); + netif_carrier_on(priv->ndev); + + /* Enable the RX_PACKET and TX_COMPLETE interrupt handlers */ + do_enable_net_rxtx_handlers(priv); + + priv->net_state = NET_STATE_RUNNING; +} + +static void do_net_stop_queues(struct wqt_dev *priv) +{ + if (priv->net_state == NET_STATE_STOPPED) + return; + + /* Disable the RX_PACKET and TX_COMPLETE interrupt handlers */ + do_disable_net_rxtx_handlers(priv); + + dev_dbg(priv->dev, "disabling NAPI queue\n"); + napi_disable(&priv->napi); + + dev_dbg(priv->dev, "disabling tx_complete() tasklet\n"); + tasklet_disable(&priv->tx_complete_tasklet); + + dev_dbg(priv->dev, "disabling TX queue\n"); + netif_tx_disable(priv->ndev); + + dev_dbg(priv->dev, "carrier off!\n"); + netif_carrier_off(priv->ndev); + + priv->net_state = NET_STATE_STOPPED; +} + +/* Called when we get a request to start our queues and acknowledge */ +static void wqtnet_start_work_fn(struct work_struct *work) +{ + struct wqt_dev *priv = container_of(work, struct wqt_dev, + net_start_work); + + mutex_lock(&priv->net_mutex); + + do_net_initialize_board(priv); + do_net_start_queues(priv); + IMMR_W32(IDR_OFFSET, NET_START_ACK_DBELL); + + mutex_unlock(&priv->net_mutex); +} + +/* Called when we get a request to stop our queues and acknowledge */ +static void wqtnet_stop_work_fn(struct work_struct *work) +{ + struct wqt_dev *priv = container_of(work, struct wqt_dev, + net_stop_work); + + mutex_lock(&priv->net_mutex); + + do_net_stop_queues(priv); + do_net_uninitialize_board(priv); + IMMR_W32(IDR_OFFSET, NET_STOP_ACK_DBELL); + + mutex_unlock(&priv->net_mutex); +} + +/*----------------------------------------------------------------------------*/ +/* SKB Allocation Helpers */ +/*----------------------------------------------------------------------------*/ + +static void wqt_cleanup_skbs(struct wqt_dev *priv) +{ + struct sk_buff *skb; + dma_addr_t skb_addr; + int i; + + /* TX ring */ + for (i = 0; i < PH_RING_SIZE; ++i) { + if (priv->tx_skbs[i]) { + skb = priv->tx_skbs[i]; + skb_addr = priv->tx_skb_addrs[i]; + + dma_unmap_single(priv->dev, + skb_addr, + skb->len, + DMA_TO_DEVICE); + + dev_kfree_skb(skb); + + priv->tx_skbs[i] = NULL; + priv->tx_skb_addrs[i] = 0x0; + } + } + + /* RX ring */ + for (i = 0; i < PH_RING_SIZE; ++i) { + if (priv->rx_skbs[i]) { + skb = priv->rx_skbs[i]; + skb_addr = priv->rx_skb_addrs[i]; + + dma_unmap_single(priv->dev, + skb_addr, + PH_MAX_FRSIZE, + DMA_FROM_DEVICE); + + dev_kfree_skb(skb); + + priv->rx_skbs[i] = NULL; + priv->rx_skb_addrs[i] = 0x0; + } + } +} + +static int wqt_alloc_skbs(struct wqt_dev *priv) +{ + struct sk_buff *skb; + dma_addr_t skb_addr; + int i; + + /* RX ring */ + for (i = 0; i < PH_RING_SIZE; ++i) { + /* Paranoia check */ + BUG_ON(priv->rx_skbs[i] != NULL); + BUG_ON(priv->rx_skb_addrs[i] != 0x0); + + /* Allocate the skb */ + skb = dev_alloc_skb(PH_MAX_FRSIZE + NET_IP_ALIGN); + + if (skb == NULL) + goto out_err; + + skb_reserve(skb, NET_IP_ALIGN); + + /* DMA map the skb */ + skb_addr = dma_map_single(priv->dev, + skb->data, + PH_MAX_FRSIZE, + DMA_FROM_DEVICE); + + if (dma_mapping_error(skb_addr)) { + dev_kfree_skb(skb); + goto out_err; + } + + priv->rx_skbs[i] = skb; + priv->rx_skb_addrs[i] = skb_addr; + } + + /* TX ring */ + for (i = 0; i < PH_RING_SIZE; ++i) { + /* Paranoia check */ + BUG_ON(priv->tx_skbs[i] != NULL); + BUG_ON(priv->tx_skb_addrs[i] != 0x0); + } + + /* NOTE: the actual initialization of the board happens + * NOTE: in ph_initialize_board(), once the board has + * NOTE: requested to be initialized */ + + return 0; + +out_err: + wqt_cleanup_skbs(priv); + return -ENOMEM; +} + +/*----------------------------------------------------------------------------*/ +/* Network Device Operations */ +/*----------------------------------------------------------------------------*/ + +static int wqt_open(struct net_device *dev) +{ + struct wqt_dev *priv = netdev_priv(dev); + int ret; + + /* Check that the other side has registers */ + if (!wqtstatus_remote_testbit(priv, PCINET_NET_REGISTERS_VALID)) { + dev_err(priv->dev, "no driver installed at the other end\n"); + dev_err(priv->dev, "cowardly refusing to open\n"); + return -ENOTCONN; /* Transport endpoint is not connected */ + } + + /* Pretend the cable is unplugged until we are up and running */ + netif_carrier_off(dev); + + mutex_lock(&priv->net_mutex); + + ret = wqt_alloc_skbs(priv); + + if (ret) + goto out_err; + + do_net_initialize_board(priv); + + ret = wqt_request_irq(priv); + + if (ret) + goto out_err; + + /* Enable only the network start/stop interrupts */ + do_enable_net_startstop_handlers(priv); + + /* Check if the other side is running, if not, it will start us. + * Without the interrupt handler installed, there's no way it can + * respond to us anyway */ + if (!wqtstatus_remote_testbit(priv, PCINET_NET_STATUS_RUNNING)) + goto out_unlock; + + do_net_initialize_board(priv); + + IMMR_W32(IDR_OFFSET, NET_START_REQ_DBELL); + ret = wait_for_completion_timeout(&priv->net_start_completion, 5*HZ); + + if (!ret) { + /* Our start request timed out, therefore, the other + * side will start us when it comes back up */ + dev_dbg(priv->dev, "start timed out\n"); + } else { + do_net_start_queues(priv); + ret = 0; + } + +out_unlock: + mutex_unlock(&priv->net_mutex); + return 0; + +out_err: + wqt_cleanup_skbs(priv); + mutex_unlock(&priv->net_mutex); + return ret; +} + +static int wqt_stop(struct net_device *dev) +{ + struct wqt_dev *priv = netdev_priv(dev); + int ret; + + mutex_lock(&priv->net_mutex); + + do_net_stop_queues(priv); + + IMMR_W32(IDR_OFFSET, NET_STOP_REQ_DBELL); + ret = wait_for_completion_timeout(&priv->net_stop_completion, 5*HZ); + + if (!ret) + dev_warn(priv->dev, "other side did not stop on time!\n"); + else + ret = 0; + + do_disable_net_startstop_handlers(priv); + wqt_free_irq(priv); + do_net_uninitialize_board(priv); + wqt_cleanup_skbs(priv); + + mutex_unlock(&priv->net_mutex); + return 0; +} + +static int wqt_change_mtu(struct net_device *dev, int new_mtu) +{ + if ((new_mtu < 68) || (new_mtu > PH_MAX_MTU)) + return -EINVAL; + + dev->mtu = new_mtu; + return 0; +} + +static int wqt_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct wqt_dev *priv = netdev_priv(dev); + dma_addr_t skb_addr; + struct circ_buf_desc __iomem *bdp; + int dirty_idx; + + spin_lock_bh(&priv->net_lock); + + bdp = priv->cur_tx; + dirty_idx = bdp - priv->tx_base; + + /* This should not happen, the queue should be stopped */ + if (priv->tx_free == 0 || CBDR_SC(bdp) != BD_MEM_READY) { + netif_stop_queue(dev); + spin_unlock_bh(&priv->net_lock); + return NETDEV_TX_BUSY; + } + + skb_addr = dma_map_single(priv->dev, + skb->data, + skb->len, + DMA_TO_DEVICE); + + if (dma_mapping_error(skb_addr)) { + dev_warn(priv->dev, "DMA mapping error\n"); + spin_unlock_bh(&priv->net_lock); + return -ENOMEM; + } + + BUG_ON(priv->tx_skbs[dirty_idx] != NULL); + BUG_ON(priv->tx_skb_addrs[dirty_idx] != 0x0); + + priv->tx_skbs[dirty_idx] = skb; + priv->tx_skb_addrs[dirty_idx] = skb_addr; + + CBDW_LEN(bdp, skb->len); + CBDW_ADDR(bdp, skb_addr); + CBDW_SC(bdp, BD_MEM_DIRTY); + + if (dirty_idx == PH_RING_SIZE - 1) + bdp = priv->tx_base; + else + bdp++; + + priv->cur_tx = bdp; + priv->tx_free--; + dev->trans_start = jiffies; + + if (priv->tx_free == 0) + netif_stop_queue(dev); + + if (!wqtstatus_remote_testbit(priv, PCINET_NET_RXINT_OFF)) + IMMR_W32(IDR_OFFSET, NET_RX_PACKET_DBELL); + + spin_unlock_bh(&priv->net_lock); + return NETDEV_TX_OK; +} + +static void wqt_tx_timeout(struct net_device *dev) +{ + struct wqt_dev *priv = netdev_priv(dev); + + dev->stats.tx_errors++; + IMMR_W32(IDR_OFFSET, NET_RX_PACKET_DBELL); +} + +static void wqt_tx_complete(unsigned long data) +{ + struct net_device *dev = (struct net_device *)data; + struct wqt_dev *priv = netdev_priv(dev); + struct sk_buff *skb; + dma_addr_t skb_addr; + struct circ_buf_desc __iomem *bdp; + int do_wake, dirty_idx; + + spin_lock_bh(&priv->net_lock); + + bdp = priv->dirty_tx; + do_wake = 0; + + while (CBDR_SC(bdp) == BD_MEM_FREE) { + dirty_idx = bdp - priv->tx_base; + + skb = priv->tx_skbs[dirty_idx]; + skb_addr = priv->tx_skb_addrs[dirty_idx]; + + BUG_ON(skb == NULL); + BUG_ON(skb_addr == 0x0); + + dev->stats.tx_bytes += skb->len; + dev->stats.tx_packets++; + + /* Unmap and free the transmitted skb */ + dma_unmap_single(priv->dev, + skb_addr, + skb->len, + DMA_TO_DEVICE); + dev_kfree_skb_irq(skb); + + priv->tx_skbs[dirty_idx] = NULL; + priv->tx_skb_addrs[dirty_idx] = 0x0; + + /* Invalidate the buffer descriptor */ + CBDW_LEN(bdp, 0); + CBDW_ADDR(bdp, 0x0); + CBDW_SC(bdp, BD_MEM_READY); + + /* Update the bdp */ + if (dirty_idx == PH_RING_SIZE - 1) + bdp = priv->tx_base; + else + bdp++; + + if (!priv->tx_free++) + do_wake = 1; + } + + priv->dirty_tx = bdp; + + spin_unlock_bh(&priv->net_lock); + + if (do_wake) + netif_wake_queue(dev); +} + +static int wqt_rx_napi(struct napi_struct *napi, int budget) +{ + struct wqt_dev *priv = container_of(napi, struct wqt_dev, napi); + struct net_device *dev = priv->ndev; + int received = 0; + struct sk_buff *skb, *skbn; + dma_addr_t skb_addr, skbn_addr; + int pkt_len, dirty_idx; + struct circ_buf_desc __iomem *bdp; + + bdp = priv->cur_rx; + + while (CBDR_SC(bdp) == BD_MEM_DIRTY) { + dirty_idx = bdp - priv->rx_base; + + skb = priv->rx_skbs[dirty_idx]; + skb_addr = priv->rx_skb_addrs[dirty_idx]; + + BUG_ON(skb == NULL); + BUG_ON(skb_addr == 0x0); + + /* Allocate the next rx skb and DMA map it */ + skbn = dev_alloc_skb(PH_MAX_FRSIZE + NET_IP_ALIGN); + + if (skbn == NULL) { + skbn = skb; + skbn_addr = skb_addr; + dev->stats.rx_dropped++; + goto out_noalloc; + } + + skb_reserve(skbn, NET_IP_ALIGN); + + skbn_addr = dma_map_single(priv->dev, + skbn->data, + PH_MAX_FRSIZE, + DMA_FROM_DEVICE); + + if (dma_mapping_error(skbn_addr)) { + dev_kfree_skb_irq(skbn); + skbn = skb; + skbn_addr = skb_addr; + dev->stats.rx_dropped++; + goto out_noalloc; + } + + /* DMA unmap the old skb and pass it up */ + dma_unmap_single(priv->dev, + skb_addr, + PH_MAX_FRSIZE, + DMA_FROM_DEVICE); + + pkt_len = CBDR_LEN(bdp); + skb_put(skb, pkt_len); + skb->protocol = eth_type_trans(skb, dev); +#ifdef CONFIG_PCINET_DISABLE_CHECKSUM + skb->ip_summed = CHECKSUM_UNNECESSARY; +#else + skb->ip_summed = CHECKSUM_NONE; +#endif + netif_receive_skb(skb); + received++; + dev->stats.rx_bytes += pkt_len; + dev->stats.rx_packets++; + +out_noalloc: + /* Write the new skb into the buffer descriptor */ + CBDW_LEN(bdp, PH_MAX_FRSIZE); + CBDW_ADDR(bdp, skbn_addr); + CBDW_SC(bdp, BD_MEM_FREE); + + priv->rx_skbs[dirty_idx] = skbn; + priv->rx_skb_addrs[dirty_idx] = skbn_addr; + + /* Update the bdp */ + if (dirty_idx == PH_RING_SIZE - 1) + bdp = priv->rx_base; + else + bdp++; + + if (received >= budget) + break; + } + + priv->cur_rx = bdp; + + /* We have processed all packets that the adapter had, but it + * was less than our budget, stop polling */ + if (received < budget) { + netif_rx_complete(dev, napi); + wqtstatus_clrbit(priv, PCINET_NET_RXINT_OFF); + } + + IMMR_W32(IDR_OFFSET, NET_TX_COMPLETE_DBELL); + + return received; +} + +/*----------------------------------------------------------------------------*/ +/* UART Device Operations */ +/*----------------------------------------------------------------------------*/ + +static unsigned int wqtuart_tx_empty(struct uart_port *port) +{ + return TIOCSER_TEMT; +} + +static void wqtuart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +static unsigned int wqtuart_get_mctrl(struct uart_port *port) +{ + return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS; +} + +static void wqtuart_stop_tx(struct uart_port *port) +{ +} + +static void wqtuart_start_tx(struct uart_port *port) +{ + struct wqt_dev *priv = container_of(port, struct wqt_dev, port); + + queue_work(priv->wq, &priv->uart_tx_work); +} + +static void wqtuart_stop_rx(struct uart_port *port) +{ + struct wqt_dev *priv = container_of(port, struct wqt_dev, port); + + do_disable_uart_handlers(priv); + priv->uart_rx_enabled = false; + wqtstatus_clrbit(priv, PCINET_UART_RX_ENABLED); +} + +static void wqtuart_enable_ms(struct uart_port *port) +{ +} + +static void wqtuart_break_ctl(struct uart_port *port, int break_state) +{ +} + +static int wqtuart_startup(struct uart_port *port) +{ + struct wqt_dev *priv = container_of(port, struct wqt_dev, port); + int ret; + + ret = wqt_request_irq(priv); + + if (ret) + return ret; + + do_enable_uart_handlers(priv); + + /* Mark the transmitter and receiver ready */ + priv->uart_tx_ready = true; + priv->uart_rx_enabled = true; + wqtstatus_setbit(priv, PCINET_UART_RX_ENABLED); + + /* Let the other side know that we are ready to receive chars now */ + IMMR_W32(IDR_OFFSET, UART_TX_EMPTY_DBELL); + priv->uart_open = true; + return 0; +} + +static void wqtuart_shutdown(struct uart_port *port) +{ + struct wqt_dev *priv = container_of(port, struct wqt_dev, port); + + wqt_free_irq(priv); + + /* Make sure the uart_tx_work_fn() exits cleanly */ + priv->uart_open = false; + wake_up(&priv->uart_tx_wait); +} + +static void wqtuart_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ +} + +static const char *wqtuart_type(struct uart_port *port) +{ + return "WQTUART"; +} + +static int wqtuart_request_port(struct uart_port *port) +{ + return 0; +} + +static void wqtuart_config_port(struct uart_port *port, int flags) +{ +} + +static void wqtuart_release_port(struct uart_port *port) +{ +} + +static int wqtuart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + return 0; +} + +static void wqtuart_rx_char(struct uart_port *port, const char ch) +{ + struct wqt_dev *priv = container_of(port, struct wqt_dev, port); + struct tty_struct *tty; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + + if (priv->uart_rx_enabled) { + tty = port->info->port.tty; + tty_insert_flip_char(tty, ch, TTY_NORMAL); + tty_flip_buffer_push(tty); + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +static struct uart_ops wqtuart_ops = { + .tx_empty = wqtuart_tx_empty, + .set_mctrl = wqtuart_set_mctrl, + .get_mctrl = wqtuart_get_mctrl, + .stop_tx = wqtuart_stop_tx, + .start_tx = wqtuart_start_tx, + .stop_rx = wqtuart_stop_rx, + .enable_ms = wqtuart_enable_ms, + .break_ctl = wqtuart_break_ctl, + .startup = wqtuart_startup, + .shutdown = wqtuart_shutdown, + .set_termios = wqtuart_set_termios, + .type = wqtuart_type, + .release_port = wqtuart_release_port, + .request_port = wqtuart_request_port, + .config_port = wqtuart_config_port, + .verify_port = wqtuart_verify_port, +}; + +static struct uart_driver wqtuart_driver = { + .owner = THIS_MODULE, + .driver_name = driver_name, + .dev_name = "ttyPCI", + .major = 240, + .minor = 0, + .nr = 1, +}; + +/*----------------------------------------------------------------------------*/ +/* PCI Subsystem */ +/*----------------------------------------------------------------------------*/ + +static int wqt_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct net_device *ndev; + struct wqt_dev *priv; + int ret; + + ndev = alloc_etherdev(sizeof(*priv)); + + if (!ndev) { + ret = -ENOMEM; + goto out_alloc_ndev; + } + + pci_set_drvdata(dev, ndev); + priv = netdev_priv(ndev); + priv->pdev = dev; + priv->dev = &dev->dev; + priv->ndev = ndev; + + mutex_init(&priv->irq_mutex); + spin_lock_init(&priv->irq_lock); + + /* Hardware Initialization */ + ret = pci_enable_device(dev); + + if (ret) + goto out_pci_enable_dev; + + pci_set_master(dev); + + ret = pci_request_regions(dev, driver_name); + + if (ret) + goto out_pci_request_regions; + + priv->immr = pci_iomap(dev, 0, 0); + + if (!priv->immr) { + ret = -ENOMEM; + goto out_iomap_immr; + } + + priv->netregs = pci_iomap(dev, 1, 0); + + if (!priv->netregs) { + ret = -ENOMEM; + goto out_iomap_netregs; + } + + priv->rx_base = priv->netregs + PCINET_RXBD_BASE; + priv->tx_base = priv->netregs + PCINET_TXBD_BASE; + + ret = dma_set_mask(&dev->dev, 0xcfffffff); + + if (ret) { + dev_err(&dev->dev, "Unable to set DMA mask\n"); + ret = -ENODEV; + goto out_set_dma_mask; + } + + /* Initialize private data */ + priv->wq = create_singlethread_workqueue(driver_name); + + if (!priv->wq) { + ret = -ENOMEM; + goto out_create_workqueue; + } + + INIT_WORK(&priv->uart_tx_work, uart_tx_work_fn); + init_waitqueue_head(&priv->uart_tx_wait); + priv->uart_tx_ready = true; + + tasklet_init(&priv->tx_complete_tasklet, wqt_tx_complete, + (unsigned long)ndev); + tasklet_disable(&priv->tx_complete_tasklet); + spin_lock_init(&priv->net_lock); + + mutex_init(&priv->net_mutex); + priv->net_state = NET_STATE_STOPPED; + INIT_WORK(&priv->net_start_work, wqtnet_start_work_fn); + INIT_WORK(&priv->net_stop_work, wqtnet_stop_work_fn); + init_completion(&priv->net_start_completion); + init_completion(&priv->net_stop_completion); + + /* Mask all of the MBOX interrupts */ + IMMR_W32(OMIMR_OFFSET, 0x1 | 0x2); + + /* Network Device */ + random_ether_addr(ndev->dev_addr); + + ndev->open = wqt_open; + ndev->stop = wqt_stop; + ndev->change_mtu = wqt_change_mtu; + ndev->hard_start_xmit = wqt_hard_start_xmit; + ndev->tx_timeout = wqt_tx_timeout; + ndev->watchdog_timeo = HZ/4; + ndev->flags &= ~IFF_MULTICAST; /* No multicast support */ +#ifdef CONFIG_PCINET_DISABLE_CHECKSUM + ndev->features |= NETIF_F_NO_CSUM; /* No checksum needed */ +#endif + ndev->mtu = PH_MAX_MTU; + netif_napi_add(ndev, &priv->napi, wqt_rx_napi, PH_RING_SIZE); + + ret = register_netdev(ndev); + + if (ret) + goto out_register_netdev; + + /* UART Device */ + priv->port.ops = &wqtuart_ops; + priv->port.type = PORT_16550A; + priv->port.dev = &dev->dev; + priv->port.line = 0; + spin_lock_init(&priv->port.lock); + + ret = uart_add_one_port(&wqtuart_driver, &priv->port); + + if (ret) + goto out_add_uart_port; + + dev_info(priv->dev, "using ethernet device %s\n", ndev->name); + dev_info(priv->dev, "using serial device %s%d\n", + wqtuart_driver.dev_name, priv->port.line); + return 0; + +out_add_uart_port: + unregister_netdev(ndev); +out_register_netdev: + destroy_workqueue(priv->wq); +out_create_workqueue: +out_set_dma_mask: + pci_iounmap(dev, priv->netregs); +out_iomap_netregs: + pci_iounmap(dev, priv->immr); +out_iomap_immr: + pci_release_regions(dev); +out_pci_request_regions: + pci_disable_device(dev); +out_pci_enable_dev: + free_netdev(ndev); +out_alloc_ndev: + return ret; +} + +static void wqt_remove(struct pci_dev *dev) +{ + struct net_device *ndev = pci_get_drvdata(dev); + struct wqt_dev *priv = netdev_priv(ndev); + + uart_remove_one_port(&wqtuart_driver, &priv->port); + unregister_netdev(priv->ndev); + + flush_workqueue(priv->wq); + destroy_workqueue(priv->wq); + + pci_iounmap(dev, priv->netregs); + pci_iounmap(dev, priv->immr); + pci_release_regions(dev); + pci_disable_device(dev); + + free_netdev(ndev); +} + +#define PCI_DEVID_FSL_MPC8349EMDS 0x0080 + +/* The list of devices that this module will support */ +static struct pci_device_id wqt_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, PCI_DEVID_FSL_MPC8349EMDS), }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, wqt_ids); + +static struct pci_driver wqt_pci_driver = { + .name = (char *)driver_name, + .id_table = wqt_ids, + .probe = wqt_probe, + .remove = wqt_remove, +}; + +/*----------------------------------------------------------------------------*/ +/* Module Init / Exit */ +/*----------------------------------------------------------------------------*/ + +static int __init wqt_init(void) +{ + int ret; + + ret = uart_register_driver(&wqtuart_driver); + + if (ret) + goto out_uart_register_driver; + + ret = pci_register_driver(&wqt_pci_driver); + + if (ret) + goto out_pci_register_driver; + + return 0; + +out_pci_register_driver: + uart_unregister_driver(&wqtuart_driver); +out_uart_register_driver: + return ret; +} + +static void __exit wqt_exit(void) +{ + pci_unregister_driver(&wqt_pci_driver); + uart_unregister_driver(&wqtuart_driver); +} + +MODULE_AUTHOR("Ira W. Snyder "); +MODULE_DESCRIPTION("PCINet/PCISerial Driver for MPC8349EMDS (Host side)"); +MODULE_LICENSE("GPL"); + +module_init(wqt_init); +module_exit(wqt_exit); diff --git a/drivers/net/pcinet_hw.h b/drivers/net/pcinet_hw.h new file mode 100644 index 0000000..499ba61 --- /dev/null +++ b/drivers/net/pcinet_hw.h @@ -0,0 +1,77 @@ +/* + * Register offsets for the MPC8349EMDS Message Unit from the IMMR base address + * + * Copyright (c) 2008 Ira W. Snyder + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef PCINET_HW_H +#define PCINET_HW_H + +/* mpc8349emds message unit register offsets */ +#define OMISR_OFFSET 0x8030 +#define OMIMR_OFFSET 0x8034 +#define IMR0_OFFSET 0x8050 +#define IMR1_OFFSET 0x8054 +#define OMR0_OFFSET 0x8058 +#define OMR1_OFFSET 0x805C +#define ODR_OFFSET 0x8060 +#define IDR_OFFSET 0x8068 +#define IMISR_OFFSET 0x8080 +#define IMIMR_OFFSET 0x8084 + + +/* mpc8349emds pci and local access window register offsets */ +#define LAWAR0_OFFSET 0x0064 +#define LAWAR0_ENABLE (1<<31) + +#define POCMR0_OFFSET 0x8410 +#define POCMR0_ENABLE (1<<31) + +#define POTAR0_OFFSET 0x8400 + +#define LAWAR1_OFFSET 0x006c +#define LAWAR1_ENABLE (1<<31) + +#define POCMR1_OFFSET 0x8428 +#define POCMR1_ENABLE (1<<31) + +#define POTAR1_OFFSET 0x8418 + + +/* mpc8349emds dma controller register offsets */ +#define DMAMR0_OFFSET 0x8100 +#define DMASR0_OFFSET 0x8104 +#define DMASAR0_OFFSET 0x8110 +#define DMADAR0_OFFSET 0x8118 +#define DMABCR0_OFFSET 0x8120 + +#define DMA_CHANNEL_BUSY (1<<2) + +#define DMA_DIRECT_MODE_SNOOP (1<<20) +#define DMA_CHANNEL_MODE_DIRECT (1<<2) +#define DMA_CHANNEL_START (1<<0) + + +/* mpc8349emds pci and local access window register offsets */ +#define LAWAR0_OFFSET 0x0064 +#define LAWAR0_ENABLE (1<<31) + +#define POCMR0_OFFSET 0x8410 +#define POCMR0_ENABLE (1<<31) + +#define POTAR0_OFFSET 0x8400 + + +/* mpc8349emds pci and inbound window register offsets */ +#define PITAR0_OFFSET 0x8568 +#define PIWAR0_OFFSET 0x8578 + +#define PIWAR0_ENABLED (1<<31) +#define PIWAR0_PREFETCH (1<<29) +#define PIWAR0_IWS_4K 0xb + +#endif /* PCINET_HW_H */