On Fri, Oct 31, 2008 at 01:25:14PM +0100, Jean-Christophe PLAGNIOL-VILLARD wrote: > On 08:06 Fri 31 Oct , Jerry Van Baren wrote: > > Wolfgang Denk wrote: > > > Dear rajeev s, > > > > > > In message <[EMAIL PROTECTED]> you wrote: > > >> We have a custom board based on coldfire (MCF5484) Similar to MCF5484 > > >> Kitlite > > >> The board runs Coldfire as PCI agent , 64MB of SDRAM, 4 MB of Bootflash > > >> and the PCI bus as a slave (no serial > > >> port->no console) > > >> I can flash U-boot using the JTAG . Can you let know how can i pass the > > >> Commands over PCI . > > >> I do not have a Console to do that ?
[snip] > > > > I have not googled, I'm sure many people have done this before. Maybe > > you will get lucky and someone will have published source under a GPL > > (compatible) license. > IIRC I've seen this in the Prism54 driver > I'm currently working on a custom board based on MPC8349EMDS. It works in PCI agent mode. I have written a Linux driver that implements both ethernet and a uart over the PCI bus. I don't think it would be hard to port to your board. Search the linuxppc-dev or LKML archives for the patch. The subject is "net: add PCINet driver". I'm currently trying to get the driver merged into Linux. I have also written U-Boot drivers that offer the same functionality. I can "see" the U-Boot prompt over PCI, and tftp + nfs boot over PCI. It is very convenient. I was going to post up the U-Boot drivers after I get the Linux driver merged, just in case there are changes. I've inlined the patches below. The first adds ethernet, the second adds a serial port. Please note that they are MPC8349EMDS specific right now, but they might provide inspiration. Showing your support of the PCINet driver on LKML might help get it reviewed. Noone has taken much interest. Ira >From 356297a295bb2a5ee7c0bc736b838f144939376d Mon Sep 17 00:00:00 2001 From: Ira W. Snyder <[EMAIL PROTECTED]> Date: Mon, 18 Aug 2008 15:40:40 -0700 Subject: [PATCH] Add PCINet Driver This adds a virtual network interface that uses the PCI bus for communication. This is extremely useful for boards in PCISLAVE mode, so you do not need extra cables to communicate with them. There is a corresponding Linux driver to be run on the host system. Signed-off-by: Ira W. Snyder <[EMAIL PROTECTED]> --- cpu/mpc83xx/cpu.c | 4 +- drivers/net/Makefile | 1 + drivers/net/pcinet.c | 503 ++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/net/pcinet.h | 73 ++++++++ 4 files changed, 580 insertions(+), 1 deletions(-) create mode 100644 drivers/net/pcinet.c create mode 100644 drivers/net/pcinet.h diff --git a/cpu/mpc83xx/cpu.c b/cpu/mpc83xx/cpu.c index aa9b18d..79c0363 100644 --- a/cpu/mpc83xx/cpu.c +++ b/cpu/mpc83xx/cpu.c @@ -364,6 +364,8 @@ int cpu_eth_init(bd_t *bis) #if defined(CONFIG_TSEC_ENET) tsec_standard_init(bis); #endif - +#ifdef CONFIG_PCINET_ETHERNET + pcinet_initialize(bis, 0, "PCINET"); +#endif return 0; } diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 439c354..2435e63 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -52,6 +52,7 @@ COBJS-$(CONFIG_NETCONSOLE) += netconsole.o COBJS-$(CONFIG_DRIVER_NS7520_ETHERNET) += ns7520_eth.o COBJS-$(CONFIG_NS8382X) += ns8382x.o COBJS-$(CONFIG_DRIVER_NS9750_ETHERNET) += ns9750_eth.o +COBJS-$(CONFIG_PCINET_ETHERNET) += pcinet.o COBJS-$(CONFIG_PCNET) += pcnet.o COBJS-$(CONFIG_PLB2800_ETHER) += plb2800_eth.o COBJS-$(CONFIG_DRIVER_RTL8019) += rtl8019.o diff --git a/drivers/net/pcinet.c b/drivers/net/pcinet.c new file mode 100644 index 0000000..b86383f --- /dev/null +++ b/drivers/net/pcinet.c @@ -0,0 +1,503 @@ +/* + * PCINet Virtual Ethernet over PCI driver + * + * This software may be used and distributed according to the + * terms of the GNU General Public License, Version 2, incorporated + * herein by reference. + * + * Copyright (c) 2008, Ira W. Snyder <[EMAIL PROTECTED]> + */ + +#include <config.h> +#include <common.h> +#include <malloc.h> +#include <net.h> +#include <command.h> +#include <asm/io.h> +#include <asm/errno.h> + +#include <asm/mmu.h> +#include <mpc83xx.h> +#include <asm/mpc8349_pci.h> + +#include "pcinet.h" + +struct wqt_dev; +typedef void (*wqt_irqhandler_t)(struct wqt_dev *); + +struct wqt_dev { + wqt_irqhandler_t net_rx_packet_handler; + wqt_irqhandler_t net_tx_complete_handler; + int state; + + void *netregs; + + cbd_t *rx_base; + cbd_t *tx_base; + + cbd_t *cur_rx; + cbd_t *cur_tx; + cbd_t *dirty_tx; + int tx_free; +}; + +#define W32(addr, b) out_le32((volatile u32 *)(addr), (b)) +#define R32(addr) in_le32((volatile u32 *)(addr)) + +#define W32BE(addr, b) out_be32((volatile u32 *)(addr), (b)) +#define R32BE(addr) in_be32((volatile u32 *)(addr)) + +/*----------------------------------------------------------------------------*/ +/* Status Bits */ +/*----------------------------------------------------------------------------*/ + +static void status_setbit(u32 bit) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + + W32(&dma->omr1, R32(&dma->omr1) | bit); +} + +static void status_clrbit(u32 bit) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + + W32(&dma->omr1, R32(&dma->omr1) & ~bit); +} + +static u32 status_remote_testbit(u32 bit) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + + return R32(&dma->imr1) & bit; +} + +/*----------------------------------------------------------------------------*/ +/* Doorbell */ +/*----------------------------------------------------------------------------*/ + +static void doorbell_set(u32 bit) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + + W32(&dma->odr, bit); +} + +/*----------------------------------------------------------------------------*/ +/* DMA */ +/*----------------------------------------------------------------------------*/ + +/* + * Set up a 1GB outbound window starting at PCI address 0x0 + */ +static void setup_outbound_window(void) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile law83xx_t *pci_law = immr->sysconf.pcilaw; + volatile pot83xx_t *pci_pot = immr->ios.pot; + + W32BE(&pci_law[0].ar, LAWAR_EN | LAWAR_SIZE_1G); + W32BE(&pci_pot[0].pocmr, POCMR_EN | POCMR_PREFETCH_EN + | (POCMR_CM_1G & POCMR_CM_MASK)); + W32BE(&pci_pot[0].potar, 0x0); +} + +static void dma_pci_to_buf(u32 dst, u32 src, size_t len) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + + /* Wait for the DMA controller to become free */ + while (R32(&dma->dmasr0) & DMA_CHANNEL_BUSY) + udelay(100); + + /* Program the DMA controller */ + W32(&dma->dmasar0, 0x80000000 + src); + W32(&dma->dmadar0, dst); + W32(&dma->dmabcr0, len); + + /* Start the transfer */ + W32(&dma->dmamr0, DMA_CHANNEL_TRANSFER_MODE_DIRECT | DMA_CHANNEL_SNOOP); + W32(&dma->dmamr0, DMA_CHANNEL_TRANSFER_MODE_DIRECT | DMA_CHANNEL_SNOOP + | DMA_CHANNEL_START); + + while (R32(&dma->dmasr0) & DMA_CHANNEL_BUSY) + udelay(100); +} + +static void dma_buf_to_pci(u32 dst, u32 src, size_t len) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + + /* Wait for the DMA controller to become free */ + while (R32(&dma->dmasr0) & DMA_CHANNEL_BUSY) + udelay(100); + + /* Program the DMA controller */ + W32(&dma->dmasar0, src); + W32(&dma->dmadar0, 0x80000000 + dst); + W32(&dma->dmabcr0, len); + + /* Start the transfer */ + W32(&dma->dmamr0, DMA_CHANNEL_TRANSFER_MODE_DIRECT | DMA_CHANNEL_SNOOP); + W32(&dma->dmamr0, DMA_CHANNEL_TRANSFER_MODE_DIRECT | DMA_CHANNEL_SNOOP + | DMA_CHANNEL_START); + + while (R32(&dma->dmasr0) & DMA_CHANNEL_BUSY) + udelay(100); +} + +/*----------------------------------------------------------------------------*/ +/* Helper Functions */ +/*----------------------------------------------------------------------------*/ + +static int pcinet_init_netregs(struct wqt_dev *priv) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile pcictrl83xx_t *pci_ctrl = &immr->pci_ctrl[0]; + u32 val; + + /* 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 = R32BE(&pci_ctrl->piwar0) & (PIWAR_EN | PIWAR_IWS_4K); + + if (val != (PIWAR_EN | PIWAR_IWS_4K)) { + printf("PIWAR0 set up incorrectly (was %x should be %x)\n", + val, (PIWAR_EN | PIWAR_IWS_4K)); + return -ENODEV; + } + + /* Allocate the 4k of registers, 4k aligned */ + priv->netregs = memalign(4096, 4096); + + if (!priv->netregs) + return -ENOMEM; + + memset(priv->netregs, 0, 4096); + + /* Write the address into the inbound window address register */ + W32BE(&pci_ctrl->pitar0, (u32)priv->netregs >> 12); + + return 0; +} + +/*----------------------------------------------------------------------------*/ +/* Interrupt Handling */ +/*----------------------------------------------------------------------------*/ + +static void net_rx_packet_handler(struct wqt_dev *priv); +static void net_tx_complete_handler(struct wqt_dev *priv); +static void empty_handler(struct wqt_dev *priv); + +static void do_reinitialize_ring_pointers(struct wqt_dev *priv) +{ + priv->cur_rx = priv->rx_base; + priv->cur_tx = priv->tx_base; + priv->dirty_tx = priv->tx_base; + priv->tx_free = PH_RING_SIZE; +} + +static void do_start_queues(struct wqt_dev *priv) +{ + priv->net_rx_packet_handler = net_rx_packet_handler; + priv->net_tx_complete_handler = net_tx_complete_handler; +} + +static void do_stop_queues(struct wqt_dev *priv) +{ + priv->net_rx_packet_handler = empty_handler; + priv->net_tx_complete_handler = empty_handler; +} + +static void empty_handler(struct wqt_dev *priv) +{ +} + +static void net_start_handler(struct wqt_dev *priv) +{ + if (priv->state == NET_STATE_RUNNING) + return; + + do_reinitialize_ring_pointers(priv); + do_start_queues(priv); + + priv->state = NET_STATE_RUNNING; +} + +static void net_stop_handler(struct wqt_dev *priv) +{ + if (priv->state == NET_STATE_STOPPED) + return; + + do_stop_queues(priv); + + priv->state = NET_STATE_STOPPED; +} + +static void net_rx_packet_handler(struct wqt_dev *priv) +{ + cbd_t *bdp; + int dirty_idx; + u32 pkt_len, src_addr; + uchar *pkt; + + bdp = priv->cur_rx; + + while (CBDR_SC(bdp) == BD_MEM_DIRTY) { + dirty_idx = bdp - priv->rx_base; + + pkt_len = CBDR_LEN(bdp); + src_addr = CBDR_ADDR(bdp); + + pkt = malloc(pkt_len); + + if (pkt != NULL) { + dma_pci_to_buf((u32)pkt, src_addr, pkt_len); + NetReceive(pkt, pkt_len); + free(pkt); + } + + CBDW_SC(bdp, BD_MEM_FREE); + + if (dirty_idx == PH_RING_SIZE - 1) + bdp = priv->rx_base; + else + bdp++; + } + + priv->cur_rx = bdp; + + doorbell_set(NET_TX_COMPLETE_DBELL); +} + +static void net_tx_complete_handler(struct wqt_dev *priv) +{ + cbd_t *bdp; + int dirty_idx; + + bdp = priv->dirty_tx; + + while (CBDR_SC(bdp) == BD_MEM_FREE) { + dirty_idx = bdp - priv->tx_base; + + priv->tx_free++; + CBDW_SC(bdp, BD_MEM_READY); + + if (dirty_idx == PH_RING_SIZE - 1) + bdp = priv->tx_base; + else + bdp++; + } + + priv->dirty_tx = bdp; +} + +static void pcinet_interrupt(struct wqt_dev *priv) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + u32 idr; + + idr = R32(&dma->idr); + + /* Clear all of the network doorbells */ + W32(&dma->idr, NET_START_REQ_DBELL | NET_START_ACK_DBELL + | NET_STOP_REQ_DBELL | NET_STOP_ACK_DBELL + | NET_RX_PACKET_DBELL | NET_TX_COMPLETE_DBELL); + + if (idr & NET_START_REQ_DBELL) { + net_start_handler(priv); + doorbell_set(NET_START_ACK_DBELL); + } + + if (idr & NET_START_ACK_DBELL) + net_start_handler(priv); + + if (idr & NET_STOP_REQ_DBELL) { + net_stop_handler(priv); + doorbell_set(NET_STOP_ACK_DBELL); + } + + if (idr & NET_STOP_ACK_DBELL) + net_stop_handler(priv); + + if (idr & NET_RX_PACKET_DBELL) + priv->net_rx_packet_handler(priv); + + if (idr & NET_TX_COMPLETE_DBELL) + priv->net_tx_complete_handler(priv); + + /* This slows down the interrupt routine a bit, since we don't need + * to be running it as absolutely fast as the CPU can handle */ + udelay(250); +} + +/*----------------------------------------------------------------------------*/ +/* Ethernet Device Functions */ +/*----------------------------------------------------------------------------*/ + +/* Initializes data structures and registers for the controller, + * and brings the interface up. Returns the link status, meaning: + * SUCCESS if the link is up + * FAILURE otherwise + * + * This allows u-boot to find the first active controller + */ +static int pcinet_open(struct eth_device *dev, bd_t *bd) +{ + struct wqt_dev *priv = dev->priv; + int i; + + status_setbit(PCINET_NET_STATUS_RUNNING); + do_stop_queues(priv); + + /* If the other side is not running, there isn't much we can do. It's not like we + * can just magically start it */ + if (!status_remote_testbit(PCINET_NET_STATUS_RUNNING)) + return 0; + + /* Notify the other side to start */ + doorbell_set(NET_START_REQ_DBELL); + + /* Wait for 5 seconds to start */ + for (i=0; priv->state == NET_STATE_STOPPED && i<5000; ++i) { + pcinet_interrupt(priv); + udelay(1000); + } + + if (priv->state == NET_STATE_RUNNING) + return 1; + + return 0; +} + +/* Stop the interface completely */ +static void pcinet_stop(struct eth_device *dev) +{ + struct wqt_dev *priv = dev->priv; + int i; + + status_clrbit(PCINET_NET_STATUS_RUNNING); + do_stop_queues(priv); + + if (priv->state == NET_STATE_RUNNING) { + doorbell_set(NET_STOP_REQ_DBELL); + + /* Wait 5 seconds to stop */ + for (i=0; priv->state == NET_STATE_RUNNING && i<5000; ++i) { + pcinet_interrupt(priv); + udelay(1000); + } + } +} + +static int pcinet_xmit(struct eth_device *dev, volatile void *packet, int length) +{ + struct wqt_dev *priv = dev->priv; + u32 dst_addr; + int dirty_idx; + cbd_t *bdp; + + bdp = priv->cur_tx; + dirty_idx = bdp - priv->tx_base; + + if (priv->state == NET_STATE_STOPPED) + return 1; + + /* Check for no free TX slots */ + if (priv->tx_free == 0 || CBDR_SC(bdp) != BD_MEM_READY) + return 1; + + dst_addr = CBDR_ADDR(bdp); + + /* DMA the packet into the remote memory */ + dma_buf_to_pci(dst_addr, (u32)packet, length); + + CBDW_LEN(bdp, length); + 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--; + + if (!status_remote_testbit(PCINET_NET_RXINT_OFF)) + doorbell_set(NET_RX_PACKET_DBELL); + + return 0; +} + +static int pcinet_recv(struct eth_device *dev) +{ + struct wqt_dev *priv = dev->priv; + pcinet_interrupt(priv); + return -1; +} + +/* Called from net/eth.c to start the ethernet controller */ +int pcinet_initialize(bd_t *bis, int index, char *devname) +{ + struct eth_device *dev; + struct wqt_dev *priv; + int ret; + + /* Allocate the ethernet device and driver-private data */ + dev = malloc(sizeof(*dev)); + + if (!dev) + goto out_alloc_eth_device; + + priv = malloc(sizeof(*priv)); + + if (!priv) + goto out_alloc_priv; + + memset(dev, 0, sizeof(*dev)); + memset(priv, 0, sizeof(*priv)); + + /* Allocate and setup network registers */ + ret = pcinet_init_netregs(priv); + + if (ret) + goto out_init_netregs; + + status_setbit(PCINET_NET_REGISTERS_VALID); + priv->rx_base = priv->netregs + PCINET_TXBD_BASE; + priv->tx_base = priv->netregs + PCINET_RXBD_BASE; + + /* Set up the outbound window */ + setup_outbound_window(); + + /* Setup ethernet device */ + sprintf (dev->name, devname); + dev->iobase = 0; + dev->priv = priv; + dev->init = pcinet_open; + dev->halt = pcinet_stop; + dev->send = pcinet_xmit; + dev->recv = pcinet_recv; + + /* Tell u-boot to get the addr from the env */ + memset(dev->enetaddr, 0, 6); + + eth_register (dev); + + return 1; + +out_init_netregs: + free(priv); +out_alloc_priv: + free(dev); +out_alloc_eth_device: + return 0; +} + +/* vim: set ts=8 sts=8 sw=8 noet tw=92: */ diff --git a/drivers/net/pcinet.h b/drivers/net/pcinet.h new file mode 100644 index 0000000..b1851fe --- /dev/null +++ b/drivers/net/pcinet.h @@ -0,0 +1,73 @@ +/* + * ONE-LINE DESCRIPTION + * + * Copyright (c) 2008 Ira W. Snyder <[EMAIL PROTECTED]> + * + * 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 + +//#define PH_MAX_MTU (1508) +//#define PH_MAX_MTU (4082) +//#define PH_MAX_MTU (8178) +//#define PH_MAX_MTU (16370) +//#define PH_MAX_MTU (32754) +#define PH_MAX_MTU (65522) +//#define PH_MAX_MTU (131058) +#define PH_MAX_FRSIZE (PH_MAX_MTU+ETHER_HDR_SIZE) + +#define PH_RING_SIZE 64 + +struct circ_buf_desc { + u32 sc; + u32 len; + u32 addr; +} __attribute__((__packed__)); +typedef struct circ_buf_desc cbd_t; + +/* Buffer Descriptor Accessors */ +#define CBDW_SC(_cbd, _sc) out_le32((volatile u32 *)&(_cbd)->sc, (_sc)) +#define CBDW_LEN(_cbd, _len) out_le32((volatile u32 *)&(_cbd)->len, (_len)) +#define CBDW_ADDR(_cbd, _addr) out_le32((volatile u32 *)&(_cbd)->addr, (_addr)) + +#define CBDR_SC(_cbd) in_le32((volatile u32 *)&(_cbd)->sc) +#define CBDR_LEN(_cbd) in_le32((volatile u32 *)&(_cbd)->len) +#define CBDR_ADDR(_cbd) in_le32((volatile u32 *)&(_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 + +#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 */ + +/* vim: set ts=8 sts=8 sw=8 noet tw=92: */ -- 1.5.4.3 >From 343a1511912ddd998c25f549a8ae67522601e762 Mon Sep 17 00:00:00 2001 From: Ira W. Snyder <[EMAIL PROTECTED]> Date: Mon, 18 Aug 2008 10:56:33 -0700 Subject: [PATCH] Add PCISerial Driver This adds a very simple driver which emulates a serial port over the PCI bus. This is extremely useful for boards in PCISLAVE mode, so you do not need extra cables to communicate with them. There is a corresponding Linux driver. Signed-off-by: Ira W. Snyder <[EMAIL PROTECTED]> --- drivers/serial/Makefile | 3 +- drivers/serial/pciserial.c | 230 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 1 deletions(-) create mode 100644 drivers/serial/pciserial.c diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index eafe543..652371d 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -31,13 +31,14 @@ COBJS-$(CONFIG_NS9750_UART) += ns9750_serial.o COBJS-y += ns16550.o COBJS-$(CONFIG_DRIVER_S3C4510_UART) += s3c4510b_uart.o COBJS-$(CONFIG_S3C64XX) += s3c64xx.o -COBJS-y += serial.o +COBJS-$(CFG_NS16550_SERIAL) += serial.o COBJS-$(CONFIG_MAX3100_SERIAL) += serial_max3100.o COBJS-$(CONFIG_PL010_SERIAL) += serial_pl01x.o COBJS-$(CONFIG_PL011_SERIAL) += serial_pl01x.o COBJS-$(CONFIG_XILINX_UARTLITE) += serial_xuartlite.o COBJS-$(CONFIG_SCIF_CONSOLE) += serial_sh.o COBJS-$(CONFIG_USB_TTY) += usbtty.o +COBJS-$(CONFIG_PCISERIAL) += pciserial.o COBJS := $(sort $(COBJS-y)) SRCS := $(COBJS:.o=.c) diff --git a/drivers/serial/pciserial.c b/drivers/serial/pciserial.c new file mode 100644 index 0000000..ae5b8c4 --- /dev/null +++ b/drivers/serial/pciserial.c @@ -0,0 +1,230 @@ +/* + * ONE-LINE DESCRIPTION + * + * Copyright (c) 2008 Ira W. Snyder <[EMAIL PROTECTED]> + * + * 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 <config.h> +#include <common.h> +#include <asm/io.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Access helpers */ +#define W32(addr, b) out_le32((volatile u32 *)(addr), (b)) +#define R32(addr) in_le32((volatile u32 *)(addr)) +#define W32BE(addr, b) out_be32((volatile u32 *)(addr), (b)) +#define R32BE(addr) in_be32((volatile u32 *)(addr)) + +/* Doorbell Definitions */ +#define UART_RX_READY_DBELL (1<<0) +#define UART_TX_EMPTY_DBELL (1<<1) + +/* Important status bits */ +#define PCINET_UART_RX_ENABLED (1<<0) + +/* DEBUGGING NOTES: + * + * The easiest way to debug this driver is to hook up an actual serial + * port to your board, and just print messages out it. + * + * I did it during this driver's development by piggy-backing these + * routines in the drivers/serial/serial.c routines (bypassing the + * CONFIG_PCISERIAL_SERIAL routines), inserting them immediately before + * the NS16550_*() routines are called, omitting returns, etc. + * + * I also recommend doing just one part at a time. For example, just + * run the putc() code first, omitting everything else. Then you should + * get a mirror of the regular serial output when you connect with + * this driver. + * + * I have added a debugging option to this driver. It prints all data it + * receives to the 16550 COM1 port. To enable this, you should: + * #undef CONFIG_NS16550_SERIAL + * #define CONFIG_PCISERIAL_DEBUG + */ + +#ifdef CONFIG_PCISERIAL_DEBUG + +#include <ns16550.h> + +/* Check for CONFIG_SYS_NS16550_COM1 */ +#ifndef CONFIG_SYS_NS16550_COM1 +#error "CONFIG_SYS_NS16550_COM1 is needed for CONFIG_PCISERIAL_DEBUG to operate" +#endif + +/* Set up an easy way to change where the output will be mirrored to */ +#define PCISERIAL_DEBUG_OUTPUT CONFIG_SYS_NS16550_COM1 + +static int calc_divisor (NS16550_t port) +{ +#ifdef CONFIG_OMAP1510 + /* If can't cleanly clock 115200 set div to 1 */ + if ((CONFIG_SYS_NS16550_CLK == 12000000) && (gd->baudrate == 115200)) { + port->osc_12m_sel = OSC_12M_SEL; /* enable 6.5 * divisor */ + return (1); /* return 1 for base divisor */ + } + port->osc_12m_sel = 0; /* clear if previsouly set */ +#endif +#ifdef CONFIG_OMAP1610 + /* If can't cleanly clock 115200 set div to 1 */ + if ((CONFIG_SYS_NS16550_CLK == 48000000) && (gd->baudrate == 115200)) { + return (26); /* return 26 for base divisor */ + } +#endif + +#ifdef CONFIG_APTIX +#define MODE_X_DIV 13 +#else +#define MODE_X_DIV 16 +#endif + + /* Compute divisor value. Normally, we should simply return: + * CONFIG_SYS_NS16550_CLK) / MODE_X_DIV / gd->baudrate + * but we need to round that value by adding 0.5. + * Rounding is especially important at high baud rates. + */ + return (CONFIG_SYS_NS16550_CLK + (gd->baudrate * (MODE_X_DIV / 2))) / + (MODE_X_DIV * gd->baudrate); +} +#endif + +static void status_setbit(u32 bit) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + + W32(&dma->omr1, R32(&dma->omr1) | bit); +} + +static u32 status_remote_testbit(u32 bit) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + + return R32(&dma->imr1) & bit; +} + +static void pciserial_init(void) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + +#ifdef CONFIG_PCISERIAL_DEBUG + int clock_divisor; + + clock_divisor = calc_divisor(PCISERIAL_DEBUG_OUTPUT); + NS16550_init(PCISERIAL_DEBUG_OUTPUT, clock_divisor); +#endif + + /* Mask all MBOX interrupts */ + W32(&dma->imimr, 0x1 | 0x2); + + /* Let the other side know we are listening */ + status_setbit(PCINET_UART_RX_ENABLED); +} + +static void pciserial_putc(char c) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + +#ifdef CONFIG_PCISERIAL_DEBUG + NS16550_putc(PCISERIAL_DEBUG_OUTPUT, c); +#endif + + /* We just drop all characters until the other side is ready + * so that we do not stall the boot process */ + if (status_remote_testbit(PCINET_UART_RX_ENABLED)) { + W32(&dma->omr0, c); + W32(&dma->odr, UART_RX_READY_DBELL); + + /* Wait for other side to set TX_EMPTY */ + while (!(R32(&dma->idr) & UART_TX_EMPTY_DBELL)) + ; + + /* Clear TX_EMPTY interrupt */ + W32(&dma->idr, UART_TX_EMPTY_DBELL); + } +} + +static char pciserial_getc(void) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + char ch; + + /* Wait for the remote side of the uart to enable */ + while (!status_remote_testbit(PCINET_UART_RX_ENABLED)) + ; + + /* Wait for an RX_READY bit */ + while (!(R32(&dma->idr) & UART_RX_READY_DBELL)) + ; + + ch = R32(&dma->imr0) & 0xff; + + W32(&dma->idr, UART_RX_READY_DBELL); + W32(&dma->odr, UART_TX_EMPTY_DBELL); + + return ch; +} + +static int pciserial_tstc(void) +{ + volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR; + volatile dma83xx_t *dma = &immr->dma; + + return R32(&dma->idr) & UART_RX_READY_DBELL; +} + +#ifdef CONFIG_SERIAL_MULTI +#error "No support for CONFIG_SERIAL_MULTI in the pciserial driver" +#endif + +int serial_init(void) +{ + pciserial_init (); + return 0; +} + + +void serial_putc(const char c) +{ + if (c == '\n') + pciserial_putc('\r'); + + pciserial_putc(c); +} + +void serial_putc_raw(const char c) +{ + pciserial_putc(c); +} + +void serial_puts(const char *s) +{ + while (*s) + serial_putc(*s++); +} + +int serial_getc(void) +{ + return pciserial_getc(); +} + +int serial_tstc(void) +{ + return pciserial_tstc(); +} + +void serial_setbrg(void) +{ + pciserial_init(); +} + +/* vim: set ts=8 sts=8 sw=8 noet tw=92: */ -- 1.5.4.3 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot