dz.c: Resource management
authorMaciej W. Rozycki <macro@linux-mips.org>
Thu, 7 Feb 2008 08:15:15 +0000 (00:15 -0800)
committerLinus Torvalds <torvalds@woody.linux-foundation.org>
Thu, 7 Feb 2008 16:42:24 +0000 (08:42 -0800)
This is a set of changes to implement proper resource management in the
driver, including iomem space reservation and operating on physical
addresses ioremap()ped appropriately using accessory functions rather than
unportable direct assignments.

Some adjustments to code are made to reflect the architecture of the
interface, which is a centrally controlled multiport (or, as referred to
from DEC documentation, a serial line multiplexer, going up to 8 lines
originally) rather than a bundle of separate ports.

Types are changed, where applicable, to specify the width of hardware
registers explicitly.  The interrupt handler is now managed in the
->startup() and ->shutdown() calls for consistency with other drivers and
also in preparation to handle the handover from the initial firmware-based
console gracefully.

Signed-off-by: Maciej W. Rozycki <macro@linux-mips.org>
Cc: Ralf Baechle <ralf@linux-mips.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
drivers/serial/dz.c

index c054c1bd8b24fa27a1542087fdca317b87ecc205..116211fcd36fc196ff12de4aeb36ee8e6194031f 100644 (file)
@@ -39,6 +39,7 @@
 #include <linux/errno.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
+#include <linux/ioport.h>
 #include <linux/kernel.h>
 #include <linux/major.h>
 #include <linux/module.h>
@@ -47,7 +48,9 @@
 #include <linux/sysrq.h>
 #include <linux/tty.h>
 
+#include <asm/atomic.h>
 #include <asm/bootinfo.h>
+#include <asm/io.h>
 #include <asm/system.h>
 
 #include <asm/dec/interrupts.h>
 #include <asm/dec/kn02.h>
 #include <asm/dec/machtype.h>
 #include <asm/dec/prom.h>
+#include <asm/dec/system.h>
 
 #include "dz.h"
 
-static char *dz_name = "DECstation DZ serial driver version ";
-static char *dz_version = "1.03";
+
+MODULE_DESCRIPTION("DECstation DZ serial driver");
+MODULE_LICENSE("GPL");
+
+
+static char dz_name[] __initdata = "DECstation DZ serial driver version ";
+static char dz_version[] __initdata = "1.04";
 
 struct dz_port {
+       struct dz_mux           *mux;
        struct uart_port        port;
        unsigned int            cflag;
 };
 
-static struct dz_port dz_ports[DZ_NB_PORT];
+struct dz_mux {
+       struct dz_port          dport[DZ_NB_PORT];
+       atomic_t                map_guard;
+       atomic_t                irq_guard;
+       int                     initialised;
+};
+
+static struct dz_mux dz_mux;
 
 static inline struct dz_port *to_dport(struct uart_port *uport)
 {
@@ -82,21 +99,18 @@ static inline struct dz_port *to_dport(struct uart_port *uport)
  * ------------------------------------------------------------
  */
 
-static inline unsigned short dz_in(struct dz_port *dport, unsigned offset)
+static u16 dz_in(struct dz_port *dport, unsigned offset)
 {
-       volatile unsigned short *addr =
-               (volatile unsigned short *) (dport->port.membase + offset);
+       void __iomem *addr = dport->port.membase + offset;
 
-       return *addr;
+       return readw(addr);
 }
 
-static inline void dz_out(struct dz_port *dport, unsigned offset,
-                          unsigned short value)
+static void dz_out(struct dz_port *dport, unsigned offset, u16 value)
 {
-       volatile unsigned short *addr =
-               (volatile unsigned short *) (dport->port.membase + offset);
+       void __iomem *addr = dport->port.membase + offset;
 
-       *addr = value;
+       writew(value, addr);
 }
 
 /*
@@ -112,7 +126,7 @@ static inline void dz_out(struct dz_port *dport, unsigned offset,
 static void dz_stop_tx(struct uart_port *uport)
 {
        struct dz_port *dport = to_dport(uport);
-       unsigned short tmp, mask = 1 << dport->port.line;
+       u16 tmp, mask = 1 << dport->port.line;
 
        tmp = dz_in(dport, DZ_TCR);     /* read the TX flag */
        tmp &= ~mask;                   /* clear the TX flag */
@@ -122,7 +136,7 @@ static void dz_stop_tx(struct uart_port *uport)
 static void dz_start_tx(struct uart_port *uport)
 {
        struct dz_port *dport = to_dport(uport);
-       unsigned short tmp, mask = 1 << dport->port.line;
+       u16 tmp, mask = 1 << dport->port.line;
 
        tmp = dz_in(dport, DZ_TCR);     /* read the TX flag */
        tmp |= mask;                    /* set the TX flag */
@@ -137,7 +151,7 @@ static void dz_stop_rx(struct uart_port *uport)
        dz_out(dport, DZ_LPR, dport->cflag);
 }
 
-static void dz_enable_ms(struct uart_port *port)
+static void dz_enable_ms(struct uart_port *uport)
 {
        /* nothing to do */
 }
@@ -169,19 +183,19 @@ static void dz_enable_ms(struct uart_port *port)
  * This routine deals with inputs from any lines.
  * ------------------------------------------------------------
  */
-static inline void dz_receive_chars(struct dz_port *dport_in)
+static inline void dz_receive_chars(struct dz_mux *mux)
 {
        struct uart_port *uport;
-       struct dz_port *dport;
+       struct dz_port *dport = &mux->dport[0];
        struct tty_struct *tty = NULL;
        struct uart_icount *icount;
        int lines_rx[DZ_NB_PORT] = { [0 ... DZ_NB_PORT - 1] = 0 };
-       unsigned short status;
        unsigned char ch, flag;
+       u16 status;
        int i;
 
-       while ((status = dz_in(dport_in, DZ_RBUF)) & DZ_DVAL) {
-               dport = &dz_ports[LINE(status)];
+       while ((status = dz_in(dport, DZ_RBUF)) & DZ_DVAL) {
+               dport = &mux->dport[LINE(status)];
                uport = &dport->port;
                tty = uport->info->tty;         /* point to the proper dev */
 
@@ -235,7 +249,7 @@ static inline void dz_receive_chars(struct dz_port *dport_in)
        }
        for (i = 0; i < DZ_NB_PORT; i++)
                if (lines_rx[i])
-                       tty_flip_buffer_push(dz_ports[i].port.info->tty);
+                       tty_flip_buffer_push(mux->dport[i].port.info->tty);
 }
 
 /*
@@ -245,15 +259,15 @@ static inline void dz_receive_chars(struct dz_port *dport_in)
  * This routine deals with outputs to any lines.
  * ------------------------------------------------------------
  */
-static inline void dz_transmit_chars(struct dz_port *dport_in)
+static inline void dz_transmit_chars(struct dz_mux *mux)
 {
-       struct dz_port *dport;
+       struct dz_port *dport = &mux->dport[0];
        struct circ_buf *xmit;
-       unsigned short status;
        unsigned char tmp;
+       u16 status;
 
-       status = dz_in(dport_in, DZ_CSR);
-       dport = &dz_ports[LINE(status)];
+       status = dz_in(dport, DZ_CSR);
+       dport = &mux->dport[LINE(status)];
        xmit = &dport->port.info->xmit;
 
        if (dport->port.x_char) {               /* XON/XOFF chars */
@@ -305,7 +319,7 @@ static inline void check_modem_status(struct dz_port *dport)
         * 1. No status change interrupt; use a timer.
         * 2. Handle the 3100/5000 as appropriate. --macro
         */
-       unsigned short status;
+       u16 status;
 
        /* If not the modem line just return.  */
        if (dport->port.line != DZ_MODEM)
@@ -326,19 +340,20 @@ static inline void check_modem_status(struct dz_port *dport)
  * It deals with the multiple ports.
  * ------------------------------------------------------------
  */
-static irqreturn_t dz_interrupt(int irq, void *dev)
+static irqreturn_t dz_interrupt(int irq, void *dev_id)
 {
-       struct dz_port *dport = dev;
-       unsigned short status;
+       struct dz_mux *mux = dev_id;
+       struct dz_port *dport = &mux->dport[0];
+       u16 status;
 
        /* get the reason why we just got an irq */
        status = dz_in(dport, DZ_CSR);
 
        if ((status & (DZ_RDONE | DZ_RIE)) == (DZ_RDONE | DZ_RIE))
-               dz_receive_chars(dport);
+               dz_receive_chars(mux);
 
        if ((status & (DZ_TRDY | DZ_TIE)) == (DZ_TRDY | DZ_TIE))
-               dz_transmit_chars(dport);
+               dz_transmit_chars(mux);
 
        return IRQ_HANDLED;
 }
@@ -371,7 +386,7 @@ static void dz_set_mctrl(struct uart_port *uport, unsigned int mctrl)
         * FIXME: Handle the 3100/5000 as appropriate. --macro
         */
        struct dz_port *dport = to_dport(uport);
-       unsigned short tmp;
+       u16 tmp;
 
        if (dport->port.line == DZ_MODEM) {
                tmp = dz_in(dport, DZ_TCR);
@@ -393,14 +408,29 @@ static void dz_set_mctrl(struct uart_port *uport, unsigned int mctrl)
 static int dz_startup(struct uart_port *uport)
 {
        struct dz_port *dport = to_dport(uport);
+       struct dz_mux *mux = dport->mux;
        unsigned long flags;
-       unsigned short tmp;
+       int irq_guard;
+       int ret;
+       u16 tmp;
+
+       irq_guard = atomic_add_return(1, &mux->irq_guard);
+       if (irq_guard != 1)
+               return 0;
+
+       ret = request_irq(dport->port.irq, dz_interrupt,
+                         IRQF_SHARED, "dz", mux);
+       if (ret) {
+               atomic_add(-1, &mux->irq_guard);
+               printk(KERN_ERR "dz: Cannot get IRQ %d!\n", dport->port.irq);
+               return ret;
+       }
 
        spin_lock_irqsave(&dport->port.lock, flags);
 
-       /* enable the interrupt and the scanning */
+       /* Enable interrupts.  */
        tmp = dz_in(dport, DZ_CSR);
-       tmp |= DZ_RIE | DZ_TIE | DZ_MSE;
+       tmp |= DZ_RIE | DZ_TIE;
        dz_out(dport, DZ_CSR, tmp);
 
        spin_unlock_irqrestore(&dport->port.lock, flags);
@@ -419,11 +449,24 @@ static int dz_startup(struct uart_port *uport)
 static void dz_shutdown(struct uart_port *uport)
 {
        struct dz_port *dport = to_dport(uport);
+       struct dz_mux *mux = dport->mux;
        unsigned long flags;
+       int irq_guard;
+       u16 tmp;
 
        spin_lock_irqsave(&dport->port.lock, flags);
        dz_stop_tx(&dport->port);
        spin_unlock_irqrestore(&dport->port.lock, flags);
+
+       irq_guard = atomic_add_return(-1, &mux->irq_guard);
+       if (!irq_guard) {
+               /* Disable interrupts.  */
+               tmp = dz_in(dport, DZ_CSR);
+               tmp &= ~(DZ_RIE | DZ_TIE);
+               dz_out(dport, DZ_CSR, tmp);
+
+               free_irq(dport->port.irq, mux);
+       }
 }
 
 /*
@@ -507,6 +550,24 @@ static int dz_encode_baud_rate(unsigned int baud)
        }
 }
 
+
+static void dz_reset(struct dz_port *dport)
+{
+       struct dz_mux *mux = dport->mux;
+
+       if (mux->initialised)
+               return;
+
+       dz_out(dport, DZ_CSR, DZ_CLR);
+       while (dz_in(dport, DZ_CSR) & DZ_CLR);
+       iob();
+
+       /* Enable scanning.  */
+       dz_out(dport, DZ_CSR, DZ_MSE);
+
+       mux->initialised = 1;
+}
+
 static void dz_set_termios(struct uart_port *uport, struct ktermios *termios,
                           struct ktermios *old_termios)
 {
@@ -581,36 +642,86 @@ static void dz_set_termios(struct uart_port *uport, struct ktermios *termios,
        spin_unlock_irqrestore(&dport->port.lock, flags);
 }
 
-static const char *dz_type(struct uart_port *port)
+static const char *dz_type(struct uart_port *uport)
 {
        return "DZ";
 }
 
-static void dz_release_port(struct uart_port *port)
+static void dz_release_port(struct uart_port *uport)
 {
-       /* nothing to do */
+       struct dz_mux *mux = to_dport(uport)->mux;
+       int map_guard;
+
+       iounmap(uport->membase);
+       uport->membase = NULL;
+
+       map_guard = atomic_add_return(-1, &mux->map_guard);
+       if (!map_guard)
+               release_mem_region(uport->mapbase, dec_kn_slot_size);
 }
 
-static int dz_request_port(struct uart_port *port)
+static int dz_map_port(struct uart_port *uport)
 {
+       if (!uport->membase)
+               uport->membase = ioremap_nocache(uport->mapbase,
+                                                dec_kn_slot_size);
+       if (!uport->membase) {
+               printk(KERN_ERR "dz: Cannot map MMIO\n");
+               return -ENOMEM;
+       }
        return 0;
 }
 
-static void dz_config_port(struct uart_port *port, int flags)
+static int dz_request_port(struct uart_port *uport)
 {
-       if (flags & UART_CONFIG_TYPE)
-               port->type = PORT_DZ;
+       struct dz_mux *mux = to_dport(uport)->mux;
+       int map_guard;
+       int ret;
+
+       map_guard = atomic_add_return(1, &mux->map_guard);
+       if (map_guard == 1) {
+               if (!request_mem_region(uport->mapbase, dec_kn_slot_size,
+                                       "dz")) {
+                       atomic_add(-1, &mux->map_guard);
+                       printk(KERN_ERR
+                              "dz: Unable to reserve MMIO resource\n");
+                       return -EBUSY;
+               }
+       }
+       ret = dz_map_port(uport);
+       if (ret) {
+               map_guard = atomic_add_return(-1, &mux->map_guard);
+               if (!map_guard)
+                       release_mem_region(uport->mapbase, dec_kn_slot_size);
+               return ret;
+       }
+       return 0;
+}
+
+static void dz_config_port(struct uart_port *uport, int flags)
+{
+       struct dz_port *dport = to_dport(uport);
+
+       if (flags & UART_CONFIG_TYPE) {
+               if (dz_request_port(uport))
+                       return;
+
+               uport->type = PORT_DZ;
+
+               dz_reset(dport);
+       }
 }
 
 /*
- * verify the new serial_struct (for TIOCSSERIAL).
+ * Verify the new serial_struct (for TIOCSSERIAL).
  */
-static int dz_verify_port(struct uart_port *port, struct serial_struct *ser)
+static int dz_verify_port(struct uart_port *uport, struct serial_struct *ser)
 {
        int ret = 0;
+
        if (ser->type != PORT_UNKNOWN && ser->type != PORT_DZ)
                ret = -EINVAL;
-       if (ser->irq != port->irq)
+       if (ser->irq != uport->irq)
                ret = -EINVAL;
        return ret;
 }
@@ -637,40 +748,32 @@ static struct uart_ops dz_ops = {
 static void __init dz_init_ports(void)
 {
        static int first = 1;
-       struct dz_port *dport;
        unsigned long base;
-       int i;
+       int line;
 
        if (!first)
                return;
        first = 0;
 
-       if (mips_machtype == MACH_DS23100 ||
-           mips_machtype == MACH_DS5100)
-               base = CKSEG1ADDR(KN01_SLOT_BASE + KN01_DZ11);
+       if (mips_machtype == MACH_DS23100 || mips_machtype == MACH_DS5100)
+               base = dec_kn_slot_base + KN01_DZ11;
        else
-               base = CKSEG1ADDR(KN02_SLOT_BASE + KN02_DZ11);
-
-       for (i = 0, dport = dz_ports; i < DZ_NB_PORT; i++, dport++) {
-               spin_lock_init(&dport->port.lock);
-               dport->port.membase     = (char *) base;
-               dport->port.iotype      = UPIO_MEM;
-               dport->port.irq         = dec_interrupt[DEC_IRQ_DZ11];
-               dport->port.line        = i;
-               dport->port.fifosize    = 1;
-               dport->port.ops         = &dz_ops;
-               dport->port.flags       = UPF_BOOT_AUTOCONF;
-       }
-}
+               base = dec_kn_slot_base + KN02_DZ11;
 
-static void dz_reset(struct dz_port *dport)
-{
-       dz_out(dport, DZ_CSR, DZ_CLR);
-       while (dz_in(dport, DZ_CSR) & DZ_CLR);
-       iob();
+       for (line = 0; line < DZ_NB_PORT; line++) {
+               struct dz_port *dport = &dz_mux.dport[line];
+               struct uart_port *uport = &dport->port;
 
-       /* enable scanning */
-       dz_out(dport, DZ_CSR, DZ_MSE);
+               dport->mux      = &dz_mux;
+
+               uport->irq      = dec_interrupt[DEC_IRQ_DZ11];
+               uport->fifosize = 1;
+               uport->iotype   = UPIO_MEM;
+               uport->flags    = UPF_BOOT_AUTOCONF;
+               uport->ops      = &dz_ops;
+               uport->line     = line;
+               uport->mapbase  = base;
+       }
 }
 
 #ifdef CONFIG_SERIAL_DZ_CONSOLE
@@ -737,7 +840,7 @@ static void dz_console_print(struct console *co,
                             const char *str,
                             unsigned int count)
 {
-       struct dz_port *dport = &dz_ports[co->index];
+       struct dz_port *dport = &dz_mux.dport[co->index];
 #ifdef DEBUG_DZ
        prom_printf((char *) str);
 #endif
@@ -746,17 +849,23 @@ static void dz_console_print(struct console *co,
 
 static int __init dz_console_setup(struct console *co, char *options)
 {
-       struct dz_port *dport = &dz_ports[co->index];
+       struct dz_port *dport = &dz_mux.dport[co->index];
+       struct uart_port *uport = &dport->port;
        int baud = 9600;
        int bits = 8;
        int parity = 'n';
        int flow = 'n';
+       int ret;
 
-       if (options)
-               uart_parse_options(options, &baud, &parity, &bits, &flow);
+       ret = dz_map_port(uport);
+       if (ret)
+               return ret;
 
        dz_reset(dport);
 
+       if (options)
+               uart_parse_options(options, &baud, &parity, &bits, &flow);
+
        return uart_set_options(&dport->port, co, baud, parity, bits, flow);
 }
 
@@ -809,36 +918,14 @@ static int __init dz_init(void)
 
        dz_init_ports();
 
-#ifndef CONFIG_SERIAL_DZ_CONSOLE
-       /* reset the chip */
-       dz_reset(&dz_ports[0]);
-#endif
-
        ret = uart_register_driver(&dz_reg);
-       if (ret != 0)
-               goto out;
-
-       ret = request_irq(dz_ports[0].port.irq, dz_interrupt, IRQF_DISABLED,
-                         "DZ", &dz_ports[0]);
-       if (ret != 0) {
-               printk(KERN_ERR "dz: Cannot get IRQ %d!\n",
-                      dz_ports[0].port.irq);
-               goto out_unregister;
-       }
+       if (ret)
+               return ret;
 
        for (i = 0; i < DZ_NB_PORT; i++)
-               uart_add_one_port(&dz_reg, &dz_ports[i].port);
-
-       return ret;
+               uart_add_one_port(&dz_reg, &dz_mux.dport[i].port);
 
-out_unregister:
-       uart_unregister_driver(&dz_reg);
-
-out:
-       return ret;
+       return 0;
 }
 
 module_init(dz_init);
-
-MODULE_DESCRIPTION("DECstation DZ serial driver");
-MODULE_LICENSE("GPL");