/* This file contains the driver for the GRLIB APBUART serial port. The driver * is implemented by using the cons.c console layer. Interrupt/Polling/Task * driven mode can be configured using driver resources: * * - mode (0=Polling, 1=Interrupt, 2=Task-Driven-Interrupt Mode) * - syscon (0=Force not Ssystem Console, 1=Suggest System Console) * * The BSP define APBUART_INFO_AVAIL in order to add the info routine * used for debugging. * * COPYRIGHT (c) 2010. * Cobham Gaisler AB. * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rtems.org/license/LICENSE. */ /******************* Driver manager interface ***********************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*#define DEBUG 1 */ #ifdef DEBUG #define DBG(x...) printk(x) #else #define DBG(x...) #endif /* LEON3 Low level transmit/receive functions provided by debug-uart code */ extern void apbuart_outbyte_polled( struct apbuart_regs *regs, unsigned char ch, int do_cr_on_newline, int wait_sent); extern int apbuart_inbyte_nonblocking(struct apbuart_regs *regs); extern struct apbuart_regs *dbg_uart; /* The debug UART */ /* Probed hardware capabilities */ enum { CAP_FIFO = 0x01, /* FIFO available */ CAP_DI = 0x02, /* RX delayed interrupt available */ }; struct apbuart_priv { struct console_dev condev; struct drvmgr_dev *dev; struct apbuart_regs *regs; struct rtems_termios_tty *tty; char devName[32]; volatile int sending; int mode; int cap; }; /* Getters for different interfaces. It happens to be just casting which we do * in one place to avoid getting cast away. */ static struct console_dev *base_get_condev(rtems_termios_device_context *base) { return (struct console_dev *) base; } static struct apbuart_priv *condev_get_priv(struct console_dev *condev) { return (struct apbuart_priv *) condev; } static struct apbuart_priv *base_get_priv(rtems_termios_device_context *base) { return condev_get_priv(base_get_condev(base)); } /* TERMIOS Layer Callback functions */ static bool first_open( rtems_termios_tty *tty, rtems_termios_device_context *base, struct termios *term, rtems_libio_open_close_args_t *args ); static void last_close( rtems_termios_tty *tty, rtems_termios_device_context *base, rtems_libio_open_close_args_t *args ); static void write_interrupt( rtems_termios_device_context *base, const char *buf, size_t len ); static bool set_attributes( rtems_termios_device_context *base, const struct termios *t ); static void get_attributes( rtems_termios_device_context *base, struct termios *t ); static int read_polled(rtems_termios_device_context *base); static int read_task(rtems_termios_device_context *base); static void write_polled( rtems_termios_device_context *base, const char *buf, size_t len ); static void apbuart_cons_isr(void *arg); int apbuart_get_baud(struct apbuart_priv *uart); int apbuart_init1(struct drvmgr_dev *dev); #ifdef APBUART_INFO_AVAIL static int apbuart_info( struct drvmgr_dev *dev, void (*print_line)(void *p, char *str), void *p, int, char *argv[]); #define APBUART_INFO_FUNC apbuart_info #else #define APBUART_INFO_FUNC NULL #endif struct drvmgr_drv_ops apbuart_ops = { .init = {apbuart_init1, NULL, NULL, NULL}, .remove = NULL, .info = APBUART_INFO_FUNC }; static struct amba_dev_id apbuart_ids[] = { {VENDOR_GAISLER, GAISLER_APBUART}, {0, 0} /* Mark end of table */ }; static struct amba_drv_info apbuart_drv_info = { { DRVMGR_OBJ_DRV, /* Driver */ NULL, /* Next driver */ NULL, /* Device list */ DRIVER_AMBAPP_GAISLER_APBUART_ID, /* Driver ID */ "APBUART_DRV", /* Driver Name */ DRVMGR_BUS_TYPE_AMBAPP, /* Bus Type */ &apbuart_ops, NULL, /* Funcs */ 0, /* No devices yet */ sizeof(struct apbuart_priv), /*DrvMgr alloc private*/ }, &apbuart_ids[0] }; void apbuart_cons_register_drv (void) { DBG("Registering APBUART Console driver\n"); drvmgr_drv_register(&apbuart_drv_info.general); } static const rtems_termios_device_handler handler_interrupt = { .first_open = first_open, .last_close = last_close, .write = write_interrupt, .set_attributes = set_attributes, .mode = TERMIOS_IRQ_DRIVEN }; static const rtems_termios_device_handler handler_task = { .first_open = first_open, .last_close = last_close, .poll_read = read_task, .write = write_interrupt, .set_attributes = set_attributes, .mode = TERMIOS_TASK_DRIVEN }; static const rtems_termios_device_handler handler_polled = { .first_open = first_open, .last_close = last_close, .poll_read = read_polled, .write = write_polled, .set_attributes = set_attributes, .mode = TERMIOS_POLLED }; /* * APBUART hardware instantiation is flexible. Probe features here and driver * can select appropriate routines for the hardware. probecap() return value * is a CAP_ bitmask. */ static int probecap(struct apbuart_regs *regs) { int cap = 0; /* Probe FIFO */ if (regs->ctrl & APBUART_CTRL_FA) { cap |= CAP_FIFO; /* Probe RX delayed interrupt */ regs->ctrl |= APBUART_CTRL_DI; if (regs->ctrl & APBUART_CTRL_DI) { regs->ctrl &= ~APBUART_CTRL_DI; cap |= CAP_DI; } } return cap; } int apbuart_init1(struct drvmgr_dev *dev) { struct apbuart_priv *priv; struct amba_dev_info *ambadev; struct ambapp_core *pnpinfo; union drvmgr_key_value *value; char prefix[32]; unsigned int db; static int first_uart = 1; /* The default operation in AMP is to use APBUART[0] for CPU[0], * APBUART[1] for CPU[1] and so on. The remaining UARTs is not used * since we don't know how many CPU-cores there are. Note this only * affects the on-chip amba bus (the root bus). The user can override * the default resource sharing by defining driver resources for the * APBUART devices on each AMP OS instance. */ #if defined(RTEMS_MULTIPROCESSING) && defined(LEON3) if (drvmgr_on_rootbus(dev) && dev->minor_drv != LEON3_Cpu_Index && drvmgr_keys_get(dev, NULL) != 0) { /* User hasn't configured on-chip APBUART, leave it untouched */ return DRVMGR_EBUSY; } #endif DBG("APBUART[%d] on bus %s\n", dev->minor_drv, dev->parent->dev->name); /* Private data was allocated and zeroed by driver manager */ priv = dev->priv; if (!priv) return DRVMGR_NOMEM; priv->dev = dev; /* Get device information from AMBA PnP information */ ambadev = (struct amba_dev_info *)priv->dev->businfo; if (ambadev == NULL) return -1; pnpinfo = &ambadev->info; priv->regs = (struct apbuart_regs *)pnpinfo->apb_slv->start; /* Clear HW regs, leave baudrate register as it is */ priv->regs->status = 0; /* leave Transmitter/receiver if this is the RTEMS debug UART (assume * it has been setup by boot loader). */ db = 0; #ifdef LEON3 if (priv->regs == dbg_uart) { db = priv->regs->ctrl & (LEON_REG_UART_CTRL_RE | LEON_REG_UART_CTRL_TE | LEON_REG_UART_CTRL_PE | LEON_REG_UART_CTRL_PS); } #endif /* Let UART debug tunnelling be untouched if Flow-control is set. * * With old APBUARTs debug is enabled by setting LB and FL, since LB or * DB are not reset we can not trust them. However since FL is reset we * guess that we are debugging if FL is already set, the debugger set * either LB or DB depending on UART capabilities. */ if (priv->regs->ctrl & LEON_REG_UART_CTRL_FL) { db |= priv->regs->ctrl & (LEON_REG_UART_CTRL_DB | LEON_REG_UART_CTRL_LB | LEON_REG_UART_CTRL_FL); } priv->regs->ctrl = db; priv->cap = probecap(priv->regs); /* The system console and Debug console may depend on this device, so * initialize it straight away. * * We default to have System Console on first APBUART, user may override * this behaviour by setting the syscon option to 0. */ if (drvmgr_on_rootbus(dev) && first_uart) { priv->condev.flags = CONSOLE_FLAG_SYSCON; first_uart = 0; } else { priv->condev.flags = 0; } value = drvmgr_dev_key_get(priv->dev, "syscon", DRVMGR_KT_INT); if (value) { if (value->i) priv->condev.flags |= CONSOLE_FLAG_SYSCON; else priv->condev.flags &= ~CONSOLE_FLAG_SYSCON; } /* Select 0=Polled, 1=IRQ, 2=Task-Driven UART Mode */ value = drvmgr_dev_key_get(priv->dev, "mode", DRVMGR_KT_INT); if (value) priv->mode = value->i; else priv->mode = TERMIOS_POLLED; /* TERMIOS device handlers */ if (priv->mode == TERMIOS_IRQ_DRIVEN) { priv->condev.handler = &handler_interrupt; } else if (priv->mode == TERMIOS_TASK_DRIVEN) { priv->condev.handler = &handler_task; } else { priv->condev.handler = &handler_polled; } priv->condev.fsname = NULL; /* Get Filesystem name prefix */ prefix[0] = '\0'; if (drvmgr_get_dev_prefix(dev, prefix)) { /* Got special prefix, this means we have a bus prefix * And we should use our "bus minor" */ sprintf(priv->devName, "/dev/%sapbuart%d", prefix, dev->minor_bus); priv->condev.fsname = priv->devName; } else { sprintf(priv->devName, "/dev/apbuart%d", dev->minor_drv); } /* Register it as a console device, the console driver will register * a termios device as well */ console_dev_register(&priv->condev); return DRVMGR_OK; } #ifdef APBUART_INFO_AVAIL static int apbuart_info( struct drvmgr_dev *dev, void (*print_line)(void *p, char *str), void *p, int argc, char *argv[]) { struct apbuart_priv *priv = dev->priv; char *str1; char buf[64]; if (dev->priv == NULL) return -DRVMGR_EINVAL; if (priv->mode == TERMIOS_POLLED) str1 = "TERMIOS_POLLED"; else if (priv->mode == TERMIOS_IRQ_DRIVEN) str1 = "TERMIOS_IRQ_DRIVEN"; else if (priv->mode == TERMIOS_TASK_DRIVEN) str1 = "TERMIOS_TASK_DRIVEN"; else str1 = "BAD MODE"; sprintf(buf, "UART Mode: %s", str1); print_line(p, buf); if (priv->condev.fsname) { sprintf(buf, "FS Name: %s", priv->condev.fsname); print_line(p, buf); } sprintf(buf, "STATUS REG: 0x%x", priv->regs->status); print_line(p, buf); sprintf(buf, "CTRL REG: 0x%x", priv->regs->ctrl); print_line(p, buf); sprintf(buf, "SCALER REG: 0x%x baud rate %d", priv->regs->scaler, apbuart_get_baud(priv)); print_line(p, buf); return DRVMGR_OK; } #endif #ifndef LEON3 /* This routine transmits a character, it will busy-wait until on character * fits in the APBUART Transmit FIFO */ void apbuart_outbyte_polled( struct apbuart_regs *regs, unsigned char ch, int do_cr_on_newline, int wait_sent) { send: while ((regs->status & LEON_REG_UART_STATUS_THE) == 0) { /* Lower bus utilization while waiting for UART */ asm volatile ("nop"::); asm volatile ("nop"::); asm volatile ("nop"::); asm volatile ("nop"::); asm volatile ("nop"::); asm volatile ("nop"::); asm volatile ("nop"::); asm volatile ("nop"::); } regs->data = (unsigned int) ch; if ((ch == '\n') && do_cr_on_newline) { ch = '\r'; goto send; } /* Wait until the character has been sent? */ if (wait_sent) { while ((regs->status & LEON_REG_UART_STATUS_THE) == 0) ; } } /* This routine polls for one character, return EOF if no character is available */ int apbuart_inbyte_nonblocking(struct apbuart_regs *regs) { if (regs->status & LEON_REG_UART_STATUS_ERR) { regs->status = ~LEON_REG_UART_STATUS_ERR; } if ((regs->status & LEON_REG_UART_STATUS_DR) == 0) return EOF; return (int)regs->data; } #endif static bool first_open( rtems_termios_tty *tty, rtems_termios_device_context *base, struct termios *term, rtems_libio_open_close_args_t *args ) { struct apbuart_priv *uart = base_get_priv(base); uart->tty = tty; /* Inherit UART hardware parameters from bootloader on system console */ if (uart->condev.flags & CONSOLE_FLAG_SYSCON_GRANT) { get_attributes(base, term); term->c_oflag |= ONLCR; set_attributes(base, term); } /* Enable TX/RX */ uart->regs->ctrl |= APBUART_CTRL_RE | APBUART_CTRL_TE; if (uart->mode != TERMIOS_POLLED) { int ret; uint32_t ctrl; /* Register interrupt and enable it */ ret = drvmgr_interrupt_register( uart->dev, 0, uart->devName, apbuart_cons_isr, tty ); if (ret) { return false; } uart->sending = 0; /* Turn on RX interrupts */ ctrl = uart->regs->ctrl; ctrl |= APBUART_CTRL_RI; if (uart->cap & CAP_DI) { /* Use RX FIFO interrupt only if delayed interrupt available. */ ctrl |= (APBUART_CTRL_DI | APBUART_CTRL_RF); } uart->regs->ctrl = ctrl; } return true; } static void last_close( rtems_termios_tty *tty, rtems_termios_device_context *base, rtems_libio_open_close_args_t *args ) { struct apbuart_priv *uart = base_get_priv(base); rtems_interrupt_lock_context lock_context; if (uart->mode != TERMIOS_POLLED) { /* Turn off RX interrupts */ rtems_termios_device_lock_acquire(base, &lock_context); uart->regs->ctrl &= ~(APBUART_CTRL_DI | APBUART_CTRL_RI | APBUART_CTRL_RF); rtems_termios_device_lock_release(base, &lock_context); /**** Flush device ****/ while (uart->sending) { /* Wait until all data has been sent */ } while ( (uart->regs->ctrl & APBUART_CTRL_TE) && !(uart->regs->status & APBUART_STATUS_TS) ) { /* Wait until all data has left shift register */ } /* Disable and unregister interrupt handler */ drvmgr_interrupt_unregister(uart->dev, 0, apbuart_cons_isr, tty); } #ifdef LEON3 /* Disable TX/RX if not used for DEBUG */ if (uart->regs != dbg_uart) uart->regs->ctrl &= ~(APBUART_CTRL_RE | APBUART_CTRL_TE); #endif } static int read_polled(rtems_termios_device_context *base) { struct apbuart_priv *uart = base_get_priv(base); return apbuart_inbyte_nonblocking(uart->regs); } /* This function is called from TERMIOS rxdaemon task without device lock. */ static int read_task(rtems_termios_device_context *base) { rtems_interrupt_lock_context lock_context; struct apbuart_priv *uart = base_get_priv(base); struct apbuart_regs *regs = uart->regs; int cnt; char buf[33]; struct rtems_termios_tty *tty; uint32_t ctrl_add; ctrl_add = APBUART_CTRL_RI; if (uart->cap & CAP_DI) { ctrl_add |= (APBUART_CTRL_DI | APBUART_CTRL_RF); } tty = uart->tty; do { cnt = 0; while ( (regs->status & APBUART_STATUS_DR) && (cnt < sizeof(buf)) ) { buf[cnt] = regs->data; cnt++; } if (0 < cnt) { /* Tell termios layer about new characters */ rtems_termios_enqueue_raw_characters(tty, &buf[0], cnt); } /* * Turn on RX interrupts. A new character in FIFO now may not * cause interrupt so we must check data ready again * afterwards. */ rtems_termios_device_lock_acquire(base, &lock_context); regs->ctrl |= ctrl_add; rtems_termios_device_lock_release(base, &lock_context); } while (regs->status & APBUART_STATUS_DR); return EOF; } struct apbuart_baud { unsigned int num; unsigned int baud; }; static struct apbuart_baud apbuart_baud_table[] = { {B50, 50}, {B75, 75}, {B110, 110}, {B134, 134}, {B150, 150}, {B200, 200}, {B300, 300}, {B600, 600}, {B1200, 1200}, {B1800, 1800}, {B2400, 2400}, {B4800, 4800}, {B9600, 9600}, {B19200, 19200}, {B38400, 38400}, {B57600, 57600}, {B115200, 115200}, {B230400, 230400}, {B460800, 460800}, }; #define BAUD_NUM (sizeof(apbuart_baud_table)/sizeof(struct apbuart_baud)) static int apbuart_baud_num2baud(unsigned int num) { int i; for(i=0; iregs->scaler; /* Get APBUART core frequency */ drvmgr_freq_get(uart->dev, DEV_APB_SLV, &core_clk_hz); /* Calculate baud rate from generator "scaler" number */ return core_clk_hz / ((scaler + 1) * 8); } static struct apbuart_baud *apbuart_get_baud_closest(struct apbuart_priv *uart) { return apbuart_baud_find_closest(apbuart_get_baud(uart)); } static bool set_attributes( rtems_termios_device_context *base, const struct termios *t ) { unsigned int core_clk_hz; unsigned int scaler; unsigned int ctrl; int baud; struct apbuart_priv *uart = base_get_priv(base); rtems_interrupt_lock_context lock_context; switch(t->c_cflag & CSIZE) { default: case CS5: case CS6: case CS7: /* Hardware doesn't support other than CS8 */ return false; case CS8: break; } rtems_termios_device_lock_acquire(base, &lock_context); /* Read out current value */ ctrl = uart->regs->ctrl; switch(t->c_cflag & (PARENB|PARODD)){ case (PARENB|PARODD): /* Odd parity */ ctrl |= LEON_REG_UART_CTRL_PE|LEON_REG_UART_CTRL_PS; break; case PARENB: /* Even parity */ ctrl &= ~LEON_REG_UART_CTRL_PS; ctrl |= LEON_REG_UART_CTRL_PE; break; default: case 0: case PARODD: /* No Parity */ ctrl &= ~(LEON_REG_UART_CTRL_PS|LEON_REG_UART_CTRL_PE); } if (!(t->c_cflag & CLOCAL)) ctrl |= LEON_REG_UART_CTRL_FL; else ctrl &= ~LEON_REG_UART_CTRL_FL; /* Update new settings */ uart->regs->ctrl = ctrl; rtems_termios_device_lock_release(base, &lock_context); /* Baud rate */ baud = apbuart_baud_num2baud(t->c_ospeed); if (baud > 0){ /* Get APBUART core frequency */ drvmgr_freq_get(uart->dev, DEV_APB_SLV, &core_clk_hz); /* Calculate Baud rate generator "scaler" number */ scaler = (((core_clk_hz*10)/(baud*8))-5)/10; /* Set new baud rate by setting scaler */ uart->regs->scaler = scaler; } return true; } static void get_attributes( rtems_termios_device_context *base, struct termios *t ) { struct apbuart_priv *uart = base_get_priv(base); unsigned int ctrl; struct apbuart_baud *baud; t->c_cflag = t->c_cflag & ~(CSIZE|PARENB|PARODD|CLOCAL); /* Hardware support only CS8 */ t->c_cflag |= CS8; /* Read out current parity */ ctrl = uart->regs->ctrl; if (ctrl & LEON_REG_UART_CTRL_PE) { if (ctrl & LEON_REG_UART_CTRL_PS) t->c_cflag |= PARENB|PARODD; /* Odd parity */ else t->c_cflag |= PARENB; /* Even parity */ } if ((ctrl & LEON_REG_UART_CTRL_FL) == 0) t->c_cflag |= CLOCAL; baud = apbuart_get_baud_closest(uart); t->c_cflag |= baud->num; } static void write_polled( rtems_termios_device_context *base, const char *buf, size_t len ) { struct apbuart_priv *uart = base_get_priv(base); int nwrite = 0; while (nwrite < len) { apbuart_outbyte_polled(uart->regs, *buf++, 0, 0); nwrite++; } } static void write_interrupt( rtems_termios_device_context *base, const char *buf, size_t len ) { struct apbuart_priv *uart = base_get_priv(base); struct apbuart_regs *regs = uart->regs; int sending; unsigned int ctrl; ctrl = regs->ctrl; if (len > 0) { /* * sending is used to remember how much we have outstanding so * we can tell termios later. */ /* Enable TX interrupt (interrupt is edge-triggered) */ regs->ctrl = ctrl | APBUART_CTRL_TI; if (ctrl & APBUART_CTRL_FA) { /* APBUART with FIFO.. Fill as many as FIFO allows */ sending = 0; while ( ((regs->status & APBUART_STATUS_TF) == 0) && (sending < len) ) { regs->data = *buf; buf++; sending++; } } else { /* start UART TX, this will result in an interrupt when done */ regs->data = *buf; sending = 1; } } else { /* No more to send, disable TX interrupts */ regs->ctrl = ctrl & ~APBUART_CTRL_TI; /* Tell close that we sent everything */ sending = 0; } uart->sending = sending; } /* Handle UART interrupts */ static void apbuart_cons_isr(void *arg) { rtems_termios_tty *tty = arg; rtems_termios_device_context *base; struct console_dev *condev = rtems_termios_get_device_context(tty); struct apbuart_priv *uart = condev_get_priv(condev); struct apbuart_regs *regs = uart->regs; unsigned int status; char buf[33]; int cnt; if (uart->mode == TERMIOS_TASK_DRIVEN) { if ((status = regs->status) & APBUART_STATUS_DR) { rtems_interrupt_lock_context lock_context; /* Turn off RX interrupts */ base = rtems_termios_get_device_context(tty); rtems_termios_device_lock_acquire(base, &lock_context); regs->ctrl &= ~(APBUART_CTRL_DI | APBUART_CTRL_RI | APBUART_CTRL_RF); rtems_termios_device_lock_release(base, &lock_context); /* Activate termios RX daemon task */ rtems_termios_rxirq_occured(tty); } } else { /* * Get all new characters from APBUART RX (FIFO) and store them * on the stack. Then tell termios about the new characters. * Maximum APBUART RX FIFO size is 32 characters. */ cnt = 0; while ( ((status=regs->status) & APBUART_STATUS_DR) && (cnt < sizeof(buf)) ) { buf[cnt] = regs->data; cnt++; } if (0 < cnt) { /* Tell termios layer about new characters */ rtems_termios_enqueue_raw_characters(tty, &buf[0], cnt); } } if (uart->sending && (status & APBUART_STATUS_TE)) { /* Tell close that we sent everything */ cnt = uart->sending; /* * Tell termios how much we have sent. dequeue() may call * write_interrupt() to refill the transmitter. * write_interrupt() will eventually be called with 0 len to * disable TX interrupts. */ rtems_termios_dequeue_characters(tty, cnt); } }