| /* |
| * SC268xx.c: Serial driver for Philiphs SC2681/SC2692 devices. |
| * |
| * Copyright (C) 2006,2007 Thomas Bogendörfer (tsbogend@alpha.franken.de) |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/tty.h> |
| #include <linux/tty_flip.h> |
| #include <linux/major.h> |
| #include <linux/circ_buf.h> |
| #include <linux/serial.h> |
| #include <linux/sysrq.h> |
| #include <linux/console.h> |
| #include <linux/spinlock.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/init.h> |
| #include <linux/platform_device.h> |
| #include <linux/irq.h> |
| #include <linux/io.h> |
| |
| #warning "Please try migrate to use new driver SCCNXP and report the status" \ |
| "in the linux-serial mailing list." |
| |
| #if defined(CONFIG_MAGIC_SYSRQ) |
| #define SUPPORT_SYSRQ |
| #endif |
| |
| #include <linux/serial_core.h> |
| |
| #define SC26XX_MAJOR 204 |
| #define SC26XX_MINOR_START 205 |
| #define SC26XX_NR 2 |
| |
| struct uart_sc26xx_port { |
| struct uart_port port[2]; |
| u8 dsr_mask[2]; |
| u8 cts_mask[2]; |
| u8 dcd_mask[2]; |
| u8 ri_mask[2]; |
| u8 dtr_mask[2]; |
| u8 rts_mask[2]; |
| u8 imr; |
| }; |
| |
| /* register common to both ports */ |
| #define RD_ISR 0x14 |
| #define RD_IPR 0x34 |
| |
| #define WR_ACR 0x10 |
| #define WR_IMR 0x14 |
| #define WR_OPCR 0x34 |
| #define WR_OPR_SET 0x38 |
| #define WR_OPR_CLR 0x3C |
| |
| /* access common register */ |
| #define READ_SC(p, r) readb((p)->membase + RD_##r) |
| #define WRITE_SC(p, r, v) writeb((v), (p)->membase + WR_##r) |
| |
| /* register per port */ |
| #define RD_PORT_MRx 0x00 |
| #define RD_PORT_SR 0x04 |
| #define RD_PORT_RHR 0x0c |
| |
| #define WR_PORT_MRx 0x00 |
| #define WR_PORT_CSR 0x04 |
| #define WR_PORT_CR 0x08 |
| #define WR_PORT_THR 0x0c |
| |
| /* SR bits */ |
| #define SR_BREAK (1 << 7) |
| #define SR_FRAME (1 << 6) |
| #define SR_PARITY (1 << 5) |
| #define SR_OVERRUN (1 << 4) |
| #define SR_TXRDY (1 << 2) |
| #define SR_RXRDY (1 << 0) |
| |
| #define CR_RES_MR (1 << 4) |
| #define CR_RES_RX (2 << 4) |
| #define CR_RES_TX (3 << 4) |
| #define CR_STRT_BRK (6 << 4) |
| #define CR_STOP_BRK (7 << 4) |
| #define CR_DIS_TX (1 << 3) |
| #define CR_ENA_TX (1 << 2) |
| #define CR_DIS_RX (1 << 1) |
| #define CR_ENA_RX (1 << 0) |
| |
| /* ISR bits */ |
| #define ISR_RXRDYB (1 << 5) |
| #define ISR_TXRDYB (1 << 4) |
| #define ISR_RXRDYA (1 << 1) |
| #define ISR_TXRDYA (1 << 0) |
| |
| /* IMR bits */ |
| #define IMR_RXRDY (1 << 1) |
| #define IMR_TXRDY (1 << 0) |
| |
| /* access port register */ |
| static inline u8 read_sc_port(struct uart_port *p, u8 reg) |
| { |
| return readb(p->membase + p->line * 0x20 + reg); |
| } |
| |
| static inline void write_sc_port(struct uart_port *p, u8 reg, u8 val) |
| { |
| writeb(val, p->membase + p->line * 0x20 + reg); |
| } |
| |
| #define READ_SC_PORT(p, r) read_sc_port(p, RD_PORT_##r) |
| #define WRITE_SC_PORT(p, r, v) write_sc_port(p, WR_PORT_##r, v) |
| |
| static void sc26xx_enable_irq(struct uart_port *port, int mask) |
| { |
| struct uart_sc26xx_port *up; |
| int line = port->line; |
| |
| port -= line; |
| up = container_of(port, struct uart_sc26xx_port, port[0]); |
| |
| up->imr |= mask << (line * 4); |
| WRITE_SC(port, IMR, up->imr); |
| } |
| |
| static void sc26xx_disable_irq(struct uart_port *port, int mask) |
| { |
| struct uart_sc26xx_port *up; |
| int line = port->line; |
| |
| port -= line; |
| up = container_of(port, struct uart_sc26xx_port, port[0]); |
| |
| up->imr &= ~(mask << (line * 4)); |
| WRITE_SC(port, IMR, up->imr); |
| } |
| |
| static bool receive_chars(struct uart_port *port) |
| { |
| struct tty_port *tport = NULL; |
| int limit = 10000; |
| unsigned char ch; |
| char flag; |
| u8 status; |
| |
| /* FIXME what is this trying to achieve? */ |
| if (port->state != NULL) /* Unopened serial console */ |
| tport = &port->state->port; |
| |
| while (limit-- > 0) { |
| status = READ_SC_PORT(port, SR); |
| if (!(status & SR_RXRDY)) |
| break; |
| ch = READ_SC_PORT(port, RHR); |
| |
| flag = TTY_NORMAL; |
| port->icount.rx++; |
| |
| if (unlikely(status & (SR_BREAK | SR_FRAME | |
| SR_PARITY | SR_OVERRUN))) { |
| if (status & SR_BREAK) { |
| status &= ~(SR_PARITY | SR_FRAME); |
| port->icount.brk++; |
| if (uart_handle_break(port)) |
| continue; |
| } else if (status & SR_PARITY) |
| port->icount.parity++; |
| else if (status & SR_FRAME) |
| port->icount.frame++; |
| if (status & SR_OVERRUN) |
| port->icount.overrun++; |
| |
| status &= port->read_status_mask; |
| if (status & SR_BREAK) |
| flag = TTY_BREAK; |
| else if (status & SR_PARITY) |
| flag = TTY_PARITY; |
| else if (status & SR_FRAME) |
| flag = TTY_FRAME; |
| } |
| |
| if (uart_handle_sysrq_char(port, ch)) |
| continue; |
| |
| if (status & port->ignore_status_mask) |
| continue; |
| |
| tty_insert_flip_char(tport, ch, flag); |
| } |
| return !!tport; |
| } |
| |
| static void transmit_chars(struct uart_port *port) |
| { |
| struct circ_buf *xmit; |
| |
| if (!port->state) |
| return; |
| |
| xmit = &port->state->xmit; |
| if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { |
| sc26xx_disable_irq(port, IMR_TXRDY); |
| return; |
| } |
| while (!uart_circ_empty(xmit)) { |
| if (!(READ_SC_PORT(port, SR) & SR_TXRDY)) |
| break; |
| |
| WRITE_SC_PORT(port, THR, xmit->buf[xmit->tail]); |
| xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); |
| port->icount.tx++; |
| } |
| if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) |
| uart_write_wakeup(port); |
| } |
| |
| static irqreturn_t sc26xx_interrupt(int irq, void *dev_id) |
| { |
| struct uart_sc26xx_port *up = dev_id; |
| unsigned long flags; |
| bool push; |
| u8 isr; |
| |
| spin_lock_irqsave(&up->port[0].lock, flags); |
| |
| push = false; |
| isr = READ_SC(&up->port[0], ISR); |
| if (isr & ISR_TXRDYA) |
| transmit_chars(&up->port[0]); |
| if (isr & ISR_RXRDYA) |
| push = receive_chars(&up->port[0]); |
| |
| spin_unlock(&up->port[0].lock); |
| |
| if (push) |
| tty_flip_buffer_push(&up->port[0].state->port); |
| |
| spin_lock(&up->port[1].lock); |
| |
| push = false; |
| if (isr & ISR_TXRDYB) |
| transmit_chars(&up->port[1]); |
| if (isr & ISR_RXRDYB) |
| push = receive_chars(&up->port[1]); |
| |
| spin_unlock_irqrestore(&up->port[1].lock, flags); |
| |
| if (push) |
| tty_flip_buffer_push(&up->port[1].state->port); |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* port->lock is not held. */ |
| static unsigned int sc26xx_tx_empty(struct uart_port *port) |
| { |
| return (READ_SC_PORT(port, SR) & SR_TXRDY) ? TIOCSER_TEMT : 0; |
| } |
| |
| /* port->lock held by caller. */ |
| static void sc26xx_set_mctrl(struct uart_port *port, unsigned int mctrl) |
| { |
| struct uart_sc26xx_port *up; |
| int line = port->line; |
| |
| port -= line; |
| up = container_of(port, struct uart_sc26xx_port, port[0]); |
| |
| if (up->dtr_mask[line]) { |
| if (mctrl & TIOCM_DTR) |
| WRITE_SC(port, OPR_SET, up->dtr_mask[line]); |
| else |
| WRITE_SC(port, OPR_CLR, up->dtr_mask[line]); |
| } |
| if (up->rts_mask[line]) { |
| if (mctrl & TIOCM_RTS) |
| WRITE_SC(port, OPR_SET, up->rts_mask[line]); |
| else |
| WRITE_SC(port, OPR_CLR, up->rts_mask[line]); |
| } |
| } |
| |
| /* port->lock is held by caller and interrupts are disabled. */ |
| static unsigned int sc26xx_get_mctrl(struct uart_port *port) |
| { |
| struct uart_sc26xx_port *up; |
| int line = port->line; |
| unsigned int mctrl = TIOCM_DSR | TIOCM_CTS | TIOCM_CAR; |
| u8 ipr; |
| |
| port -= line; |
| up = container_of(port, struct uart_sc26xx_port, port[0]); |
| ipr = READ_SC(port, IPR) ^ 0xff; |
| |
| if (up->dsr_mask[line]) { |
| mctrl &= ~TIOCM_DSR; |
| mctrl |= ipr & up->dsr_mask[line] ? TIOCM_DSR : 0; |
| } |
| if (up->cts_mask[line]) { |
| mctrl &= ~TIOCM_CTS; |
| mctrl |= ipr & up->cts_mask[line] ? TIOCM_CTS : 0; |
| } |
| if (up->dcd_mask[line]) { |
| mctrl &= ~TIOCM_CAR; |
| mctrl |= ipr & up->dcd_mask[line] ? TIOCM_CAR : 0; |
| } |
| if (up->ri_mask[line]) { |
| mctrl &= ~TIOCM_RNG; |
| mctrl |= ipr & up->ri_mask[line] ? TIOCM_RNG : 0; |
| } |
| return mctrl; |
| } |
| |
| /* port->lock held by caller. */ |
| static void sc26xx_stop_tx(struct uart_port *port) |
| { |
| return; |
| } |
| |
| /* port->lock held by caller. */ |
| static void sc26xx_start_tx(struct uart_port *port) |
| { |
| struct circ_buf *xmit = &port->state->xmit; |
| |
| while (!uart_circ_empty(xmit)) { |
| if (!(READ_SC_PORT(port, SR) & SR_TXRDY)) { |
| sc26xx_enable_irq(port, IMR_TXRDY); |
| break; |
| } |
| WRITE_SC_PORT(port, THR, xmit->buf[xmit->tail]); |
| xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); |
| port->icount.tx++; |
| } |
| } |
| |
| /* port->lock held by caller. */ |
| static void sc26xx_stop_rx(struct uart_port *port) |
| { |
| } |
| |
| /* port->lock held by caller. */ |
| static void sc26xx_enable_ms(struct uart_port *port) |
| { |
| } |
| |
| /* port->lock is not held. */ |
| static void sc26xx_break_ctl(struct uart_port *port, int break_state) |
| { |
| if (break_state == -1) |
| WRITE_SC_PORT(port, CR, CR_STRT_BRK); |
| else |
| WRITE_SC_PORT(port, CR, CR_STOP_BRK); |
| } |
| |
| /* port->lock is not held. */ |
| static int sc26xx_startup(struct uart_port *port) |
| { |
| sc26xx_disable_irq(port, IMR_TXRDY | IMR_RXRDY); |
| WRITE_SC(port, OPCR, 0); |
| |
| /* reset tx and rx */ |
| WRITE_SC_PORT(port, CR, CR_RES_RX); |
| WRITE_SC_PORT(port, CR, CR_RES_TX); |
| |
| /* start rx/tx */ |
| WRITE_SC_PORT(port, CR, CR_ENA_TX | CR_ENA_RX); |
| |
| /* enable irqs */ |
| sc26xx_enable_irq(port, IMR_RXRDY); |
| return 0; |
| } |
| |
| /* port->lock is not held. */ |
| static void sc26xx_shutdown(struct uart_port *port) |
| { |
| /* disable interrupst */ |
| sc26xx_disable_irq(port, IMR_TXRDY | IMR_RXRDY); |
| |
| /* stop tx/rx */ |
| WRITE_SC_PORT(port, CR, CR_DIS_TX | CR_DIS_RX); |
| } |
| |
| /* port->lock is not held. */ |
| static void sc26xx_set_termios(struct uart_port *port, struct ktermios *termios, |
| struct ktermios *old) |
| { |
| unsigned int baud = uart_get_baud_rate(port, termios, old, 0, 4000000); |
| unsigned int quot = uart_get_divisor(port, baud); |
| unsigned int iflag, cflag; |
| unsigned long flags; |
| u8 mr1, mr2, csr; |
| |
| spin_lock_irqsave(&port->lock, flags); |
| |
| while ((READ_SC_PORT(port, SR) & ((1 << 3) | (1 << 2))) != 0xc) |
| udelay(2); |
| |
| WRITE_SC_PORT(port, CR, CR_DIS_TX | CR_DIS_RX); |
| |
| iflag = termios->c_iflag; |
| cflag = termios->c_cflag; |
| |
| port->read_status_mask = SR_OVERRUN; |
| if (iflag & INPCK) |
| port->read_status_mask |= SR_PARITY | SR_FRAME; |
| if (iflag & (BRKINT | PARMRK)) |
| port->read_status_mask |= SR_BREAK; |
| |
| port->ignore_status_mask = 0; |
| if (iflag & IGNBRK) |
| port->ignore_status_mask |= SR_BREAK; |
| if ((cflag & CREAD) == 0) |
| port->ignore_status_mask |= SR_BREAK | SR_FRAME | |
| SR_PARITY | SR_OVERRUN; |
| |
| switch (cflag & CSIZE) { |
| case CS5: |
| mr1 = 0x00; |
| break; |
| case CS6: |
| mr1 = 0x01; |
| break; |
| case CS7: |
| mr1 = 0x02; |
| break; |
| default: |
| case CS8: |
| mr1 = 0x03; |
| break; |
| } |
| mr2 = 0x07; |
| if (cflag & CSTOPB) |
| mr2 = 0x0f; |
| if (cflag & PARENB) { |
| if (cflag & PARODD) |
| mr1 |= (1 << 2); |
| } else |
| mr1 |= (2 << 3); |
| |
| switch (baud) { |
| case 50: |
| csr = 0x00; |
| break; |
| case 110: |
| csr = 0x11; |
| break; |
| case 134: |
| csr = 0x22; |
| break; |
| case 200: |
| csr = 0x33; |
| break; |
| case 300: |
| csr = 0x44; |
| break; |
| case 600: |
| csr = 0x55; |
| break; |
| case 1200: |
| csr = 0x66; |
| break; |
| case 2400: |
| csr = 0x88; |
| break; |
| case 4800: |
| csr = 0x99; |
| break; |
| default: |
| case 9600: |
| csr = 0xbb; |
| break; |
| case 19200: |
| csr = 0xcc; |
| break; |
| } |
| |
| WRITE_SC_PORT(port, CR, CR_RES_MR); |
| WRITE_SC_PORT(port, MRx, mr1); |
| WRITE_SC_PORT(port, MRx, mr2); |
| |
| WRITE_SC(port, ACR, 0x80); |
| WRITE_SC_PORT(port, CSR, csr); |
| |
| /* reset tx and rx */ |
| WRITE_SC_PORT(port, CR, CR_RES_RX); |
| WRITE_SC_PORT(port, CR, CR_RES_TX); |
| |
| WRITE_SC_PORT(port, CR, CR_ENA_TX | CR_ENA_RX); |
| while ((READ_SC_PORT(port, SR) & ((1 << 3) | (1 << 2))) != 0xc) |
| udelay(2); |
| |
| /* XXX */ |
| uart_update_timeout(port, cflag, |
| (port->uartclk / (16 * quot))); |
| |
| spin_unlock_irqrestore(&port->lock, flags); |
| } |
| |
| static const char *sc26xx_type(struct uart_port *port) |
| { |
| return "SC26XX"; |
| } |
| |
| static void sc26xx_release_port(struct uart_port *port) |
| { |
| } |
| |
| static int sc26xx_request_port(struct uart_port *port) |
| { |
| return 0; |
| } |
| |
| static void sc26xx_config_port(struct uart_port *port, int flags) |
| { |
| } |
| |
| static int sc26xx_verify_port(struct uart_port *port, struct serial_struct *ser) |
| { |
| return -EINVAL; |
| } |
| |
| static struct uart_ops sc26xx_ops = { |
| .tx_empty = sc26xx_tx_empty, |
| .set_mctrl = sc26xx_set_mctrl, |
| .get_mctrl = sc26xx_get_mctrl, |
| .stop_tx = sc26xx_stop_tx, |
| .start_tx = sc26xx_start_tx, |
| .stop_rx = sc26xx_stop_rx, |
| .enable_ms = sc26xx_enable_ms, |
| .break_ctl = sc26xx_break_ctl, |
| .startup = sc26xx_startup, |
| .shutdown = sc26xx_shutdown, |
| .set_termios = sc26xx_set_termios, |
| .type = sc26xx_type, |
| .release_port = sc26xx_release_port, |
| .request_port = sc26xx_request_port, |
| .config_port = sc26xx_config_port, |
| .verify_port = sc26xx_verify_port, |
| }; |
| |
| static struct uart_port *sc26xx_port; |
| |
| #ifdef CONFIG_SERIAL_SC26XX_CONSOLE |
| static void sc26xx_console_putchar(struct uart_port *port, char c) |
| { |
| unsigned long flags; |
| int limit = 1000000; |
| |
| spin_lock_irqsave(&port->lock, flags); |
| |
| while (limit-- > 0) { |
| if (READ_SC_PORT(port, SR) & SR_TXRDY) { |
| WRITE_SC_PORT(port, THR, c); |
| break; |
| } |
| udelay(2); |
| } |
| |
| spin_unlock_irqrestore(&port->lock, flags); |
| } |
| |
| static void sc26xx_console_write(struct console *con, const char *s, unsigned n) |
| { |
| struct uart_port *port = sc26xx_port; |
| int i; |
| |
| for (i = 0; i < n; i++) { |
| if (*s == '\n') |
| sc26xx_console_putchar(port, '\r'); |
| sc26xx_console_putchar(port, *s++); |
| } |
| } |
| |
| static int __init sc26xx_console_setup(struct console *con, char *options) |
| { |
| struct uart_port *port = sc26xx_port; |
| int baud = 9600; |
| int bits = 8; |
| int parity = 'n'; |
| int flow = 'n'; |
| |
| if (port->type != PORT_SC26XX) |
| return -1; |
| |
| printk(KERN_INFO "Console: ttySC%d (SC26XX)\n", con->index); |
| if (options) |
| uart_parse_options(options, &baud, &parity, &bits, &flow); |
| |
| return uart_set_options(port, con, baud, parity, bits, flow); |
| } |
| |
| static struct uart_driver sc26xx_reg; |
| static struct console sc26xx_console = { |
| .name = "ttySC", |
| .write = sc26xx_console_write, |
| .device = uart_console_device, |
| .setup = sc26xx_console_setup, |
| .flags = CON_PRINTBUFFER, |
| .index = -1, |
| .data = &sc26xx_reg, |
| }; |
| #define SC26XX_CONSOLE &sc26xx_console |
| #else |
| #define SC26XX_CONSOLE NULL |
| #endif |
| |
| static struct uart_driver sc26xx_reg = { |
| .owner = THIS_MODULE, |
| .driver_name = "SC26xx", |
| .dev_name = "ttySC", |
| .major = SC26XX_MAJOR, |
| .minor = SC26XX_MINOR_START, |
| .nr = SC26XX_NR, |
| .cons = SC26XX_CONSOLE, |
| }; |
| |
| static u8 sc26xx_flags2mask(unsigned int flags, unsigned int bitpos) |
| { |
| unsigned int bit = (flags >> bitpos) & 15; |
| |
| return bit ? (1 << (bit - 1)) : 0; |
| } |
| |
| static void sc26xx_init_masks(struct uart_sc26xx_port *up, |
| int line, unsigned int data) |
| { |
| up->dtr_mask[line] = sc26xx_flags2mask(data, 0); |
| up->rts_mask[line] = sc26xx_flags2mask(data, 4); |
| up->dsr_mask[line] = sc26xx_flags2mask(data, 8); |
| up->cts_mask[line] = sc26xx_flags2mask(data, 12); |
| up->dcd_mask[line] = sc26xx_flags2mask(data, 16); |
| up->ri_mask[line] = sc26xx_flags2mask(data, 20); |
| } |
| |
| static int sc26xx_probe(struct platform_device *dev) |
| { |
| struct resource *res; |
| struct uart_sc26xx_port *up; |
| unsigned int *sc26xx_data = dev->dev.platform_data; |
| int err; |
| |
| res = platform_get_resource(dev, IORESOURCE_MEM, 0); |
| if (!res) |
| return -ENODEV; |
| |
| up = kzalloc(sizeof *up, GFP_KERNEL); |
| if (unlikely(!up)) |
| return -ENOMEM; |
| |
| up->port[0].line = 0; |
| up->port[0].ops = &sc26xx_ops; |
| up->port[0].type = PORT_SC26XX; |
| up->port[0].uartclk = (29491200 / 16); /* arbitrary */ |
| |
| up->port[0].mapbase = res->start; |
| up->port[0].membase = ioremap_nocache(up->port[0].mapbase, 0x40); |
| up->port[0].iotype = UPIO_MEM; |
| up->port[0].irq = platform_get_irq(dev, 0); |
| |
| up->port[0].dev = &dev->dev; |
| |
| sc26xx_init_masks(up, 0, sc26xx_data[0]); |
| |
| sc26xx_port = &up->port[0]; |
| |
| up->port[1].line = 1; |
| up->port[1].ops = &sc26xx_ops; |
| up->port[1].type = PORT_SC26XX; |
| up->port[1].uartclk = (29491200 / 16); /* arbitrary */ |
| |
| up->port[1].mapbase = up->port[0].mapbase; |
| up->port[1].membase = up->port[0].membase; |
| up->port[1].iotype = UPIO_MEM; |
| up->port[1].irq = up->port[0].irq; |
| |
| up->port[1].dev = &dev->dev; |
| |
| sc26xx_init_masks(up, 1, sc26xx_data[1]); |
| |
| err = uart_register_driver(&sc26xx_reg); |
| if (err) |
| goto out_free_port; |
| |
| sc26xx_reg.tty_driver->name_base = sc26xx_reg.minor; |
| |
| err = uart_add_one_port(&sc26xx_reg, &up->port[0]); |
| if (err) |
| goto out_unregister_driver; |
| |
| err = uart_add_one_port(&sc26xx_reg, &up->port[1]); |
| if (err) |
| goto out_remove_port0; |
| |
| err = request_irq(up->port[0].irq, sc26xx_interrupt, 0, "sc26xx", up); |
| if (err) |
| goto out_remove_ports; |
| |
| platform_set_drvdata(dev, up); |
| return 0; |
| |
| out_remove_ports: |
| uart_remove_one_port(&sc26xx_reg, &up->port[1]); |
| out_remove_port0: |
| uart_remove_one_port(&sc26xx_reg, &up->port[0]); |
| |
| out_unregister_driver: |
| uart_unregister_driver(&sc26xx_reg); |
| |
| out_free_port: |
| kfree(up); |
| sc26xx_port = NULL; |
| return err; |
| } |
| |
| |
| static int __exit sc26xx_driver_remove(struct platform_device *dev) |
| { |
| struct uart_sc26xx_port *up = platform_get_drvdata(dev); |
| |
| free_irq(up->port[0].irq, up); |
| |
| uart_remove_one_port(&sc26xx_reg, &up->port[0]); |
| uart_remove_one_port(&sc26xx_reg, &up->port[1]); |
| |
| uart_unregister_driver(&sc26xx_reg); |
| |
| kfree(up); |
| sc26xx_port = NULL; |
| |
| return 0; |
| } |
| |
| static struct platform_driver sc26xx_driver = { |
| .probe = sc26xx_probe, |
| .remove = sc26xx_driver_remove, |
| .driver = { |
| .name = "SC26xx", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| module_platform_driver(sc26xx_driver); |
| |
| MODULE_AUTHOR("Thomas Bogendörfer"); |
| MODULE_DESCRIPTION("SC681/SC2692 serial driver"); |
| MODULE_VERSION("1.0"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:SC26xx"); |