All of lore.kernel.org
 help / color / mirror / Atom feed
* Driver for kontron PMC-6L interface card
@ 2010-08-04 12:49 Pavel Machek
  0 siblings, 0 replies; only message in thread
From: Pavel Machek @ 2010-08-04 12:49 UTC (permalink / raw)
  To: linux-serial

Hi!

Here's driver for PMC-6L interface card. It is against 2.6.27
(sorry). It seems to mostly work. It is quite an interesting card, for
example it timestamps incoming characters.

Problems remaining:

1) Due to ringbuffer design, I don't know how to handle input buffer
overrun.

2) stop_rx is currently a nop. I'm not sure if it is required? I guess
I could set RX buffer size to zero....?

I hope I did get the locking right.

Signed-off-by: Pavel Machek <pavel@ucw.cz>

diff -ur clean-27//drivers/serial/Kconfig linux-27//drivers/serial/Kconfig
--- clean-27//drivers/serial/Kconfig	2008-10-10 00:13:53.000000000 +0200
+++ linux-27//drivers/serial/Kconfig	2010-08-04 14:21:53.000000000 +0200
@@ -1343,6 +1343,14 @@
 	  If you have enabled the serial port on the Hilscher NetX SoC
 	  you can make it the console by answering Y to this option.
 
+config SERIAL_PMC6L
+        tristate "Serial ports on PMC-6L interface board"
+	select SERIAL_CORE
+	depends on PCI
+	help
+	  Select this if you have PMC-6L interface board. Up-to 6
+	  serial ports are provided there, based on configuration.
+
 config SERIAL_OF_PLATFORM
 	tristate "Serial port on Open Firmware platform bus"
 	depends on PPC_OF
diff -ur clean-27//drivers/serial/Makefile linux-27//drivers/serial/Makefile
--- clean-27//drivers/serial/Makefile	2008-10-10 00:13:53.000000000 +0200
+++ linux-27//drivers/serial/Makefile	2010-08-04 14:19:31.000000000 +0200
@@ -67,5 +67,6 @@
 obj-$(CONFIG_SERIAL_NETX) += netx-serial.o
 obj-$(CONFIG_SERIAL_OF_PLATFORM) += of_serial.o
 obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o
+obj-$(CONFIG_SERIAL_PMC6L) += pmc6l.o
 obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
 obj-$(CONFIG_SERIAL_QE) += ucc_uart.o
diff -ur clean-27//drivers/serial/pmc6l.c linux-27//drivers/serial/pmc6l.c
--- clean-27//drivers/serial/pmc6l.c	2010-08-04 14:08:35.000000000 +0200
+++ linux-27//drivers/serial/pmc6l.c	2010-08-04 14:08:24.000000000 +0200
@@ -0,0 +1,936 @@
+/*
+
+    Serial driver for PMC-6L.
+
+    Copyright (C) 2008 Michael Buesch <mb@bu3sch.de>
+    Copyright (C) 2010 Pavel Machek <pma@sysgo.com>
+    Copyright (C) 2003 Monta Vista Software, Inc.
+    Copyright (C) 2001 Russell King.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/moduleparam.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/serial_reg.h>
+#include <linux/circ_buf.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_core.h>
+#include <linux/clk.h>
+
+struct pmc_card {
+	void __iomem *mem;
+	void __iomem *io;
+
+	int	nr_active;	/* FIXME: locking of this one is not correct */
+
+	struct uart_pmc_port	*ports[6];
+};
+
+struct uart_pmc_port {
+	struct uart_port        port;
+	struct pmc_card 	*card;
+
+	int	last_rx;	/* Points to place where *last* character was
+				   in the ringbuffer. So we should expect one
+				   at the *next* position. */
+	u32	last_rx_time;
+	int	active;
+	int	rx_enabled, tx_enabled;
+	int	tx_active;
+
+	struct clk		*clk;
+	char			*name;
+};
+
+static void __iomem *reg_conf(struct uart_pmc_port *up)
+{ return up->card->io + 0x84 + 2*up->port.line; }
+static void __iomem *reg_selection(struct uart_pmc_port *up)
+{ return up->card->io + 0x96 + 2*up->port.line; }
+static void __iomem *reg_irq(struct uart_pmc_port *up)
+{ return up->card->io + 0x26; }
+static void __iomem *reg_uart0(struct uart_pmc_port *up)
+{ return up->card->mem + 0x28000; }
+static void __iomem *reg_uart(struct uart_pmc_port *up)
+{ return reg_uart0(up) + 0x320*up->port.line; }
+static void __iomem *reg_rx(struct uart_pmc_port *up)
+{ return reg_uart(up) + 0x0020; }
+static void __iomem *reg_tx(struct uart_pmc_port *up)
+{ return reg_uart(up) + 0x01a0; }
+
+#define RX_BUF_SIZE 0x100
+#define RX_BUF_CHARS (RX_BUF_SIZE/8)
+#define TX_BUF_SIZE 0x100
+#define TX_BUF_POS 0x2000
+#define RX_BUF_POS 0x2110
+void __iomem *reg_tx_buf(struct uart_pmc_port *up)
+{ return reg_uart(up) + TX_BUF_POS; }
+void __iomem *reg_rx_buf(struct uart_pmc_port *up)
+{ return reg_uart(up) + RX_BUF_POS; }
+
+
+/* Yes, all registers seem to be in big-endian. */
+
+#define iowrite(bits, val, adr) iowrite##bits(cpu_to_be##bits(val), adr)
+#define ioread(bits, adr) be##bits##_to_cpu(ioread##bits(adr))
+
+/* Make sure we don't use wrong macros by mistake. */
+
+#undef ioread16
+#undef ioread32
+#undef iowrite16
+#undef iowrite32
+
+//#define dprintk printk
+#define dprintk(a...)
+
+#define SHARED_IRQ	/* FIXME: why do these trigger? */
+
+static unsigned int port_config; 
+module_param(port_config, int, 0644);
+
+static void pmc_enable_ms(struct uart_port *port)
+{
+	/* We only support 3-wire serial with this driver */
+}
+
+static inline void pmc_needs_lock(struct uart_pmc_port *up, char *msg)
+{
+	unsigned long flags;
+	if (spin_trylock_irqsave(&up->port.lock, flags)) {
+		printk("pmc6l: locking problem (%s)\n", msg);
+		panic("pmc6l: code was not called with port.lock held");
+	}
+}
+
+static void dump_status(struct uart_pmc_port *up)
+{
+	int i;
+	printk("pmc6l status (time is %x, watchdog %x) number %d\n", 
+	       ioread(32, up->card->io+0x30), ioread(16, up->card->io+0x10), up->port.line);
+
+	printk("  port config is %x, selection is %x\n",
+	       ioread(16, reg_conf(up)), ioread(16, reg_selection(up)));
+
+	printk("  uart interrupt enable %x, interrupt ident %x, fifo %x\n",
+	       ioread8(reg_uart(up)+0), ioread8(reg_uart(up)+1), 
+	       ioread8(reg_uart(up)+2));	/* FIXME: wtf? reg_uart(up)+2 is 1 when it breaks? */
+	printk("  line ctrl %x, modem ctrl %x, line status %x, modem status %x\n",
+	       ioread8(reg_uart(up)+3), ioread8(reg_uart(up)+4), 
+	       ioread8(reg_uart(up)+5), ioread8(reg_uart(up)+6));
+	       
+	printk("  divisor hi %x, divisor low %x\n",
+	       ioread8(reg_uart(up)+8), ioread8(reg_uart(up)+9));
+	printk("  rx it %x, data @ %x size %x\n",
+	       ioread(16, reg_rx(up) + 2), ioread(32, reg_rx(up) + 4), ioread(16, reg_rx(up)));
+	printk("  tx status %x, data @ %x size %x, tag %x\n",
+	       ioread(16, reg_tx(up)), ioread(32, reg_tx(up) + 8), ioread(16, reg_tx(up) + 2), ioread(32, reg_tx(up) + 4));
+
+
+	printk("  irq config %x, arinc %x, uart %x, hdlc %x, gpio %x\n",
+	       ioread(16, up->card->io+0x20), 
+	       ioread(16, up->card->io+0x24), ioread(16, up->card->io+0x26),
+	       ioread(16, up->card->io+0x28), ioread(16, up->card->io+0x2a));
+
+	printk("  gpio config l %x h %x in %x out %x\n",
+	       ioread(16, up->card->io+0xb0), ioread(16, up->card->io+0xb2),
+	       ioread(16, up->card->io+0xb4), ioread(16, up->card->io+0xb6));
+
+	printk("  sw status: active %d rx_enabled %d last_rx %d tx_enabled %d tx_active %d\n",
+	       up->active, up->rx_enabled, up->last_rx, up->tx_enabled, up->tx_active);
+
+#if 1
+	printk("  rx: ");
+	for (i=0; i<RX_BUF_CHARS; i++) {
+		printk("char %x '%c' line %x time %x, ",
+		       ioread8(reg_rx_buf(up) + 1 + 8*i),
+		       ioread8(reg_rx_buf(up) + 1 + 8*i),
+		       ioread8(reg_rx_buf(up) + 2 + 8*i),
+		       ioread(32, reg_rx_buf(up) + 4 + 8*i));
+	}
+	printk("\n");
+#endif
+}
+
+static unsigned int __pmc_tx_empty(struct uart_pmc_port *up)
+{
+	pmc_needs_lock(up, "__pmc_tx_empty");
+	return !(ioread(16, reg_tx(up)) & 1);
+}
+
+static unsigned int pmc_tx_empty(struct uart_port *port)
+{
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+	unsigned long flags;
+        unsigned int ret;
+
+        spin_lock_irqsave(&up->port.lock, flags);
+        ret = __pmc_tx_empty(up) * TIOCSER_TEMT;
+	spin_unlock_irqrestore(&up->port.lock, flags);
+        return ret;
+}
+
+static void pmc_stop_tx(struct uart_port *port)
+{
+	int i;
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+
+	pmc_needs_lock(up, "core should take it");
+
+	dprintk("pmc6l: stop tx\n");
+
+	if (!__pmc_tx_empty(up)) {
+		printk("  tx status %x, data @ %x size %x, tag %x\n",
+		       ioread(16, reg_tx(up)), ioread(32, reg_tx(up) + 8), ioread(16, reg_tx(up) + 2), ioread(32, reg_tx(up) + 4));
+		printk("pmc6l: stop tx nonempty\n");
+	}
+	for (i=0; i<100; i++) {
+		if (__pmc_tx_empty(up))
+			break;
+		dprintk("wait");
+		mdelay(1000);
+	}
+	if (!__pmc_tx_empty(up)) {
+		printk("  tx status %x, data @ %x size %x, tag %x\n",
+		       ioread(16, reg_tx(up)), ioread(32, reg_tx(up) + 8), ioread(16, reg_tx(up) + 2), ioread(32, reg_tx(up) + 4));
+		panic("pmc6l: stop tx timeout\n");
+	}
+	
+	up->tx_enabled = 0;
+}
+
+static void pmc_stop_rx(struct uart_port *port)
+{
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+
+	pmc_needs_lock(up, "stop_rx: core should take it");
+	/* How do we stop the receive? */
+
+	dprintk("pmc6l: stop rx\n");
+}
+
+static void pmc_flush_buffer(struct uart_port *port)
+{
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+
+	pmc_needs_lock(up, "core should take it");
+
+	dprintk("pmc6l: flush buffer\n");
+	while (!__pmc_tx_empty(up)) {
+		printk("pmc6l: waiting\n"); /* FIXME? neccessary ? */
+		dump_status(up);
+		mdelay(1000);
+	}
+}
+
+static void card_status(struct pmc_card *card)
+{
+	int i;
+	printk("pmc6l card status\n");
+	for (i=0; i<6; i++) {
+		if (card->ports[i]) {
+			printk("  port %d (== %d), active %d\n",
+			       i, card->ports[i]->port.line, card->ports[i]->active);
+		} else  printk("  port %d NULL\n", i);
+	}
+}
+
+static void transmit_chars(struct uart_pmc_port *up)
+{
+	struct circ_buf *xmit = &up->port.info->xmit;
+	int pos, count;
+
+	pmc_needs_lock(up, "transmit_chars");
+	dprintk("pmc6l: transmit chars\n");
+	up->tx_active = 0;
+
+        if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) {
+                pmc_stop_tx(&up->port);
+                return;
+        }
+
+	BUG_ON(up->active != 1);
+	BUG_ON(up->tx_enabled != 1);
+	BUG_ON(!__pmc_tx_empty(up));
+
+	pos = 0;
+        count = TX_BUF_SIZE;
+        do {
+		iowrite8(xmit->buf[xmit->tail], reg_tx_buf(up) + pos);
+		pos++;
+                xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+                up->port.icount.tx++;
+
+                if (uart_circ_empty(xmit))
+                        break;
+        } while (--count > 0);
+
+	BUG_ON(!pos);
+
+	up->tx_active = 1;
+
+	dprintk("pmc6l: wrote %d bytes to the txbuf\n", pos);
+	if (ioread(16, reg_tx(up)) != 0x6)
+		panic("pmc6l: unexpected Tx Status");
+	if (ioread(32, reg_tx(up) + 8) != reg_tx_buf(up) - reg_uart0(up))
+		panic("pmc6l: unexpected write data ptr");
+	iowrite(32, 0, reg_tx(up) + 4); /* Time tag; hw should fill it */
+	iowrite(16, pos, reg_tx(up) + 2); /* Tx Data Length */
+	iowrite(16, 0x7, reg_tx(up)); /* Tx Status */
+
+	if (__pmc_tx_empty(up)) {
+		printk("pmc6l: UHUH, TRANSMIT WAY TOO FAST?\n");
+		printk("  tx status %x, data @ %x size %x, tag %x\n",
+		       ioread(16, reg_tx(up)), ioread(32, reg_tx(up) + 8), ioread(16, reg_tx(up) + 2), ioread(32, reg_tx(up) + 4));
+		panic("pmc6l: too fast transmit\n");
+	}
+
+	dprintk("  tx done? status %x, data @ %x size %x, tag %x\n",
+	       ioread(16, reg_tx(up)), ioread(32, reg_tx(up) + 8), ioread(16, reg_tx(up) + 2), ioread(32, reg_tx(up) + 4));
+
+        if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+                uart_write_wakeup(&up->port);
+}
+
+static void pmc_start_tx(struct uart_port *port)
+{
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+
+	dprintk("pmc6l: start tx\n");
+	pmc_needs_lock(up, "core should take it");
+
+	up->tx_enabled = 1;
+	if (!up->tx_active) {
+		up->tx_active = 1;
+		transmit_chars(up);
+	}
+}
+
+static unsigned int pmc_get_mctrl(struct uart_port *port)
+{
+	/* We only support 3-wire serial with this driver */
+	return 0;
+}
+
+static void pmc_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	/* We only support 3-wire serial with this driver */
+}
+
+static void pmc_break_ctl(struct uart_port *port, int break_state)
+{
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+	unsigned long flags;
+
+	printk("pmc6l: break ctl\n");
+	spin_lock_irqsave(&up->port.lock, flags);
+	/* FIXME: implement break? */
+	spin_unlock_irqrestore(&up->port.lock, flags);
+}
+
+static int check_rx(struct uart_pmc_port *up)
+{
+	struct tty_struct *tty = up->port.info->port.tty;
+	int num = 0;
+	void *last_pos = reg_rx_buf(up) + 8*up->last_rx;
+
+	pmc_needs_lock(up, "check_rx");
+	dprintk("pmc6l: check rx\n");
+
+	while (1) {
+		unsigned int c, flag;
+		void *pos;
+		int rx;
+		u32 time;
+
+		rx = up->last_rx + 1;
+		if (rx == RX_BUF_CHARS)
+			rx = 0;
+		pos = reg_rx_buf(up) + 8*rx;
+
+		if ((ioread(32, last_pos + 0)) ||
+		    (ioread(32, last_pos + 4))) {
+			// dump_status(up);
+			panic("pmc6l: receive ringbuffer overrun\n");
+		}
+
+		if (!(ioread(32, pos + 0) ||
+		      ioread(32, pos + 4)))
+			break;
+
+		dprintk("pmc6l: character received\n");
+		up->last_rx = rx;
+
+		time = ioread(32, pos+4);
+		if (time <= up->last_rx_time)
+			printk("pmc6l: characters in wrong order (%x, %x)\n",
+			       time, up->last_rx_time);
+		up->last_rx_time = time;
+
+		c = ioread8(pos + 1);
+
+		flag = TTY_NORMAL;
+		up->port.icount.rx++;
+		num++;
+
+		uart_insert_char(&up->port, 0, UART_LSR_OE, c, flag);
+
+		iowrite(32, 0, pos + 0);
+		iowrite(32, 0, pos + 4);
+
+		dprintk("pmc6l: CHAR %d '%c'\n", c, c);
+	}
+	if (num > RX_BUF_CHARS/2)
+		printk("pmc6l: received %d characters\n", num);
+
+	tty_flip_buffer_push(tty);
+	return num;
+}
+
+static void __pmc_irq(struct uart_pmc_port *up, int in_irq)
+{
+	int irq_id, my_rx, my_tx, todo;
+
+	pmc_needs_lock(up, "__pmc_irq");
+	dprintk("pmc6l: irq");
+
+	my_rx = 1 << up->port.line*2;
+	my_tx = my_rx << 1;
+
+	irq_id = ioread(16, reg_irq(up));
+	todo = irq_id & (my_rx | my_tx);
+	iowrite(16, 0xffff & ~todo, reg_irq(up));
+
+#ifndef SHARED_IRQ
+	if (in_irq && !todo) {
+		if (up->card->nr_active == 1) {
+			printk("pmc6l: interrupt for other uart? -- %x, me %x\n", 
+			       irq_id, my_rx | my_tx); 
+			card_status(up->card);
+		}
+	}
+#endif
+
+	dprintk(" id %x (%x)\n", todo, ioread(16, reg_irq(up)));
+
+	if (todo & my_rx){
+		int num = 0;
+
+		BUG_ON(!up->rx_enabled);
+		num = check_rx(up);
+		if (!num) {
+			dprintk("pmc6l: receive irq but got nothing\n");
+		}
+	}
+	wake_up_interruptible(&up->port.info->delta_msr_wait);
+	if (todo & my_tx)  {
+		BUG_ON(!__pmc_tx_empty(up));
+		BUG_ON(!up->tx_enabled);
+		transmit_chars(up);
+	}
+}
+
+/*
+ * This handles the interrupt from one port.
+ */
+static inline irqreturn_t pmc_irq(int irq, void *dev_id)
+{
+	struct uart_pmc_port *up = dev_id;
+	int irq_id;
+	unsigned long flags;
+
+	dprintk("pmc_irq\n");
+	spin_lock_irqsave(&up->port.lock, flags);
+	irq_id = ioread(16, reg_irq(up));
+#ifndef SHARED_IRQ
+	if (!irq_id) {
+		if (up->card->nr_active == 1)
+			printk("pmc6l: nothing to do in irq -- shared irq?!\n");
+	}
+#endif
+
+	__pmc_irq(up, 1);
+	spin_unlock_irqrestore(&up->port.lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+static void __pmc_startup(struct uart_pmc_port *up)
+{
+	unsigned int mode;
+
+	dprintk("__pmc_startup\n");
+	pmc_needs_lock(up, "__pmc_startup");
+
+	iowrite(16, 0x00, reg_conf(up));
+	iowrite(16, ((up->port.line+1) << 4) + (up->port.line+1), reg_selection(up));
+
+	dprintk("pmc6l: Setting up ring buffers\n");
+	iowrite(16, 0x001, reg_rx(up) + 2); /* Rx It Param */
+	iowrite(32, reg_rx_buf(up) - reg_uart0(up), reg_rx(up) + 4); /* Rx Data Pointer */
+	iowrite(16, RX_BUF_SIZE, reg_rx(up)); /* Rx Buffer Size */
+
+	BUG_ON(!__pmc_tx_empty(up));
+
+	iowrite(16, 0x6, reg_tx(up)); /* Tx Status */
+	iowrite(16, 0, reg_tx(up) + 2); /* Tx Data Length */
+	iowrite(32, 0, reg_tx(up) + 4); /* Time tag; hw should fill it */
+	iowrite(32, reg_tx_buf(up) - reg_uart0(up), reg_tx(up) + 8); /* Tx Data Pointer */
+
+	dprintk("pmc6l: Powering up transmitters: ");
+	/* EIA-232/423/485 setup goes here */
+
+	mode = ((port_config >> (2*up->port.line)) & 3) << 1;
+	switch (mode) {
+	case 0: dprintk("EIA-232 mode\n"); break;
+	case 2: dprintk("EIA-423 mode\n"); break;
+	case 4: dprintk("EIA-485 mode\n"); break;
+	case 6: dprintk("??? mode\n"); break;
+	}
+	iowrite(16, 0x61 | mode, reg_conf(up));
+	iowrite(16, ((up->port.line+1) << 4) + (up->port.line+1), reg_selection(up));
+
+	/* Wait for voltages to ramp up. 
+	   The delay here is actually neccessary. It works with 10msec wait,
+	   and breaks with no delay; seems to work with 100usec. */
+	udelay(500);
+
+}
+
+static int pmc_startup(struct uart_port *port)
+{
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+	unsigned long flags;
+	int retval;
+	int i;
+
+	dprintk("pmc6l: startup\n");
+
+	spin_lock_irqsave(&up->port.lock, flags);
+	up->active = 2;
+	iowrite8(0, reg_uart(up) + 8);	/* Divisor high */
+	iowrite8(11, reg_uart(up) + 9);  /* Divisor low */
+
+	iowrite8(0x00, reg_uart(up) + 0); /* Interrupt enable */
+	iowrite8(0x00, reg_uart(up) + 1);
+	iowrite8(0x00, reg_uart(up) + 2);
+	iowrite8(0x03, reg_uart(up) + 3); /* Line control: select 8-bit words */
+	iowrite8(0x00, reg_uart(up) + 4);
+	iowrite8(0x00, reg_uart(up) + 5);
+	iowrite8(0x00, reg_uart(up) + 6);
+	iowrite8(0x00, reg_uart(up) + 7);
+	iowrite8(0x00, reg_uart(up) + 0x0a);
+
+	__pmc_startup(up);
+
+	up->tx_enabled = 1;	/* FIXME: should not be neccessary? */
+	up->rx_enabled = 1;
+	up->last_rx = RX_BUF_CHARS-1;
+
+	for (i=0; i<TX_BUF_SIZE; i+=8) {
+		iowrite(32, 0xdeadbeef, reg_tx_buf(up)+i);
+		iowrite(32, 0x11ebabe, reg_tx_buf(up)+i+4);
+	}
+
+	for (i=0; i<RX_BUF_SIZE; i+=8) {
+		iowrite(32, 0, reg_rx_buf(up)+i);
+		iowrite(32, 0, reg_rx_buf(up)+i+4);
+	}
+
+	dprintk("pmc6l: Ring buffers ok\n");
+
+	/*
+	 * Allocate the IRQ
+	 */
+	retval = request_irq(up->port.irq, pmc_irq, IRQF_SHARED, up->name, up);
+	if (retval) {
+		printk("pmc6l: interrupt registration failed\n");
+		return retval;
+	}
+	dprintk("pmc6l: interrupt ok\n");
+
+	up->active = 1;
+	up->card->nr_active++;
+	spin_unlock_irqrestore(&up->port.lock, flags);
+	return 0;
+}
+
+static void pmc_shutdown(struct uart_port *port)
+{
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+	unsigned long flags;
+
+	dprintk("pmc6l: shutdown\n");
+
+	while (1) {
+		spin_lock_irqsave(&up->port.lock, flags);
+		if (!up->tx_active) {
+			break;
+		}
+		spin_unlock_irqrestore(&up->port.lock, flags);
+		printk("pmc6l: waiting for tx to finish\n");
+		mdelay(1000);
+	}
+
+	pmc_stop_tx(port);	/* FIXME: Should we do this here?! */
+	pmc_stop_rx(port);
+
+	if (!up->active) {
+		panic("pmc6l: shutdown of inactive port?\n");
+	}
+	up->active = 3;
+
+	while (!__pmc_tx_empty(up)) {
+		printk("pmc6l: waiting for port to shutdown\n");
+	}
+
+	/* No transmit interrupts */
+	iowrite(16, 0x2, reg_tx(up));
+	/* No receive interrupts */
+	iowrite(16, 0x0, reg_rx(up) + 2); /* Rx It Param */
+	dprintk("pmc6l: port shutdown -- picking up any interrupts\n");
+	__pmc_irq(up, 0);
+	up->rx_enabled = 0;		/* FIXME? */
+
+	iowrite(16, 0x00, reg_conf(up));	/* Turn off interrupt from this channel */
+	up->active = 0;
+	up->card->nr_active--;
+	spin_unlock_irqrestore(&up->port.lock, flags);
+
+	free_irq(up->port.irq, up);
+	dprintk("pmc6l: port shutdown done\n");
+}
+
+static void
+pmc_set_termios(struct uart_port *port, struct ktermios *termios,
+		       struct ktermios *old)
+{
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+        unsigned char cval, fcr = 0;
+        unsigned long flags;
+        unsigned int baud, quot;
+
+	dprintk("pmc6l: set termios\n");
+
+        switch (termios->c_cflag & CSIZE) {
+        case CS5:
+		cval = UART_LCR_WLEN5;
+		break;
+        case CS6:
+		cval = UART_LCR_WLEN6;
+		break;
+        case CS7:
+		cval = UART_LCR_WLEN7;
+		break;
+        default:
+        case CS8:
+		cval = UART_LCR_WLEN8;
+		break;
+        }
+
+        if (termios->c_cflag & CSTOPB)
+                cval |= UART_LCR_STOP;
+        if (termios->c_cflag & PARENB)
+                cval |= UART_LCR_PARITY;
+        if (!(termios->c_cflag & PARODD))
+                cval |= UART_LCR_EPAR;
+#ifdef CMSPAR
+        if (termios->c_cflag & CMSPAR)
+                cval |= UART_LCR_SPAR;
+#endif
+
+        /*
+         * Ask the core to calculate the divisor for us.
+         */
+        baud = uart_get_baud_rate(port, termios, old, 50, 115200);
+        quot = uart_get_divisor(port, baud);
+
+	fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
+
+	while (!pmc_tx_empty(port)) {
+		printk("pmc6l: changing parameters while transmitting?! -- WAIT\n");
+		mdelay(1000);
+	}
+
+        /*
+         * Ok, we're now changing the port state.  Do it with
+         * interrupts disabled.
+         */
+        spin_lock_irqsave(&up->port.lock, flags);
+
+	BUG_ON(!__pmc_tx_empty(up));
+
+	/* FIXME: but we may be receiving, too... */
+
+        /*
+         * Update the per-port timeout.
+         */
+        uart_update_timeout(port, termios->c_cflag, baud);
+
+	iowrite8(0, reg_uart(up)); /* Interrupt enable register */
+
+	dprintk("pmc6l: quot should be %x for baud rate %d\n", quot, baud);
+
+        iowrite8(quot & 0xff, reg_uart(up) + 9);
+        iowrite8((quot >> 8) & 0xff, reg_uart(up) + 8);
+
+	iowrite8(cval, reg_uart(up) + 3);	/* Line control register */
+	/* FIXME: wtf? we are enabling FIFO? */
+	iowrite8(fcr, reg_uart(up) + 2);	/* FIFO control register */
+
+	__pmc_startup(up);	/* FIXME: neccessary? */
+
+	pmc_set_mctrl(&up->port, up->port.mctrl);
+
+	spin_unlock_irqrestore(&up->port.lock, flags);
+}
+
+static void
+pmc_pm(struct uart_port *port, unsigned int state,
+	      unsigned int oldstate)
+{
+	/* FIXME: should we include some kind of power management? */
+	dprintk("pmc6l: pm %d\n", state);
+}
+
+static void pmc_release_port(struct uart_port *port)
+{
+	dprintk("pmc6l: release port\n");
+}
+
+static int pmc_request_port(struct uart_port *port)
+{
+	dprintk("pmc6l: request port\n");
+	return 0;
+}
+
+static void pmc_config_port(struct uart_port *port, int flags)
+{
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+
+	dprintk("pmc6l: config port\n");
+	up->port.type = PORT_PXA;
+}
+
+static int
+pmc_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+	dprintk("pmc6l: verify port\n");
+	/* we don't want the core code to modify any port params */
+	return -EINVAL;
+}
+
+static const char *
+pmc_type(struct uart_port *port)
+{
+	struct uart_pmc_port *up = (struct uart_pmc_port *)port;
+	dprintk("pmc6l: type\n");
+	return up->name;
+}
+
+static struct uart_driver pmc_reg;
+struct uart_ops pmc_pops = {
+	.tx_empty	= pmc_tx_empty,
+	.set_mctrl	= pmc_set_mctrl,
+	.get_mctrl	= pmc_get_mctrl,
+	.stop_tx	= pmc_stop_tx,
+	.start_tx	= pmc_start_tx,
+	.stop_rx	= pmc_stop_rx,
+	.enable_ms	= pmc_enable_ms,
+	.break_ctl	= pmc_break_ctl,
+	.startup	= pmc_startup,
+	.shutdown	= pmc_shutdown,
+	.set_termios	= pmc_set_termios,
+	.pm		= pmc_pm,
+	.type		= pmc_type,
+	.release_port	= pmc_release_port,
+	.request_port	= pmc_request_port,
+	.config_port	= pmc_config_port,
+	.verify_port	= pmc_verify_port,
+	.flush_buffer	= pmc_flush_buffer,
+};
+
+static struct uart_driver pmc_reg = {
+	.owner		= THIS_MODULE,
+	.driver_name	= "PMCserial",
+	.dev_name	= "ttyPMC",
+	.major		= TTY_MAJOR,
+	.minor		= 70,	/* FIXME: not quite traditional */
+	.nr		= 6,
+};
+
+static int pmc_init_port(struct uart_pmc_port *sport)
+{
+	sport->port.type = 0;
+	sport->port.iotype = UPIO_MEM;
+	sport->port.mapbase = (long) reg_uart(sport);
+	sport->port.fifosize = 1; /* FIXME min(TX_BUF_SIZE, RX_BUF_CHARS); */
+	sport->port.ops = &pmc_pops;
+	sport->port.flags = UPF_IOREMAP | UPF_BOOT_AUTOCONF;
+	sport->port.uartclk = 20000000;
+	sport->name = "PMC-6L";
+	return 0;
+}
+
+static int pmc6l_probe(struct pci_dev *dev,
+			const struct pci_device_id *pci_id)
+{
+	struct uart_pmc_port *sport;
+	struct pmc_card *card;
+	int err;
+	int i;
+
+	/* FIXME: check error paths. */
+
+	card = kzalloc(sizeof(struct pmc_card), GFP_KERNEL);
+	if (!card)
+		return -ENOMEM;
+
+	err = pci_enable_device(dev);
+	if (err) {
+		printk(KERN_ERR "pmc6l: Can't enable device.\n");
+		goto err_freebg;
+	}
+
+	pci_set_master(dev);
+
+	card->mem = pci_iomap(dev, 0, 0);
+	card->io = pci_iomap(dev, 1, 0);
+
+	for (i=0; i<0x7ffff; i++) {
+		iowrite8(0, card->mem+i);
+		if (ioread8(card->mem+i) != 0x0)
+			printk("pml6l: strange memory at %x (not 0)", i);
+	}
+
+	/* Reset the card */
+	iowrite(16, 0xffff, card->io+4);
+
+	/* Verify that release is read-only */
+	iowrite(16, 0, card->io);
+	printk("pmc6l: reset, time is %x, FPGA release is %x\n", 
+	       ioread(32, card->io+0x30), ioread(16, card->io+0));
+	printk("pmc6l: mapped memory at %lx, io at %lx\n",
+	       (long) card->mem, (long) card->io);
+
+	/* Enable interrupts */
+	iowrite(16, 0x01, card->io+0x20); /* Interrupt configuration reg */
+
+	pci_set_drvdata(dev, card);
+
+	for (i=0; i<6; i++) {
+		dprintk("pmc6l: allocating port ttyPMC%d\n", i);
+
+		sport = kzalloc(sizeof(struct uart_pmc_port), GFP_KERNEL);
+		if (!sport)
+			return -ENOMEM;
+		sport->card = card;
+		sport->port.dev = &dev->dev;
+		sport->port.line = i;
+		sport->port.irq = dev->irq;
+
+		pmc_init_port(sport);
+
+		sport->port.membase = card->mem;
+		if (!sport->port.membase) {
+			err = -ENOMEM;
+			goto err_disable;
+		}
+
+		err = uart_add_one_port(&pmc_reg, &sport->port);
+		if (err) {
+			printk("pmc6l: Could not add port, got %d\n", err);
+			goto err_disable;
+		}
+		card->ports[i] = sport;
+	}
+
+	return 0;
+
+err_disable:
+	pci_disable_device(dev);
+err_freebg:
+	kfree(card);
+	return err;
+}
+
+static void pmc6l_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct pmc_card *card = pci_get_drvdata(pdev);
+
+	dprintk("pmc6l: remove\n");
+	for (i=0; i<6; i++) {
+		if (card->ports[i])
+			uart_remove_one_port(&pmc_reg, &card->ports[i]->port);
+		card->ports[i] = NULL;
+		kfree(card->ports[i]);
+	}
+	iounmap(card->mem);
+	iounmap(card->io);
+	pci_disable_device(pdev);
+
+	pci_set_drvdata(pdev, NULL);
+	kfree(card);
+}
+
+static struct pci_device_id pmc6l_pci_tbl[] = {
+	{ PCI_DEVICE(0x1269, 0x0200) },
+	{ 0, },
+};
+MODULE_DEVICE_TABLE(pci, pmc6l_pci_tbl);
+
+static struct pci_driver pmc6l_pci_driver = {
+	.name		= "pmc6l",
+	.id_table	= pmc6l_pci_tbl,
+	.probe		= pmc6l_probe,
+	.remove		= pmc6l_remove,
+};
+
+static int pmc6l_init(void)
+{
+	int err;
+
+	dprintk("pmc6l: init\n");
+	err = uart_register_driver(&pmc_reg);
+	if (err) {
+		printk("pmc6l: uart_register returned %d\n", err);
+		return err;
+	}
+
+	/* FIXME: leaks serial structure */
+	return pci_register_driver(&pmc6l_pci_driver);
+}
+module_init(pmc6l_init)
+
+static void pmc6l_exit(void)
+{
+	pci_unregister_driver(&pmc6l_pci_driver);
+	uart_unregister_driver(&pmc_reg);
+}
+module_exit(pmc6l_exit)
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pavel Machek");
+MODULE_DESCRIPTION("Driver for pmc6l serial card.");

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2010-08-04 12:58 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-08-04 12:49 Driver for kontron PMC-6L interface card Pavel Machek

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.