diff options
Diffstat (limited to 'drivers/serial/stmp-app.c')
-rw-r--r-- | drivers/serial/stmp-app.c | 1064 |
1 files changed, 1064 insertions, 0 deletions
diff --git a/drivers/serial/stmp-app.c b/drivers/serial/stmp-app.c new file mode 100644 index 000000000000..6a56c444cf20 --- /dev/null +++ b/drivers/serial/stmp-app.c @@ -0,0 +1,1064 @@ +/* + * Freescale STMP37XX/STMP378X Application UART driver + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/uaccess.h> + +#include <asm/cacheflush.h> +#include <mach/hardware.h> +#include <mach/regs-apbx.h> +#include <mach/regs-uartapp.h> +#include <mach/regs-pinctrl.h> +#include <mach/stmp3xxx.h> + +#include <asm/mach-types.h> + +#include "stmp-app.h" + +static int pio_mode /* = 0 */; /* PIO mode = 1, DMA mode = 0 */ + +static struct platform_driver stmp_appuart_driver = { + .probe = stmp_appuart_probe, + .remove = __devexit_p(stmp_appuart_remove), + .suspend = stmp_appuart_suspend, + .resume = stmp_appuart_resume, + .driver = { + .name = "stmp37xx-appuart", + .owner = THIS_MODULE, + }, +}; + +static struct uart_driver stmp_appuart_uart = { + .owner = THIS_MODULE, + .driver_name = "appuart", + .dev_name = "ttySP", + .major = 242, + .minor = 0, + .nr = 1, +}; + +static inline struct stmp_appuart_port *to_appuart(struct uart_port *u) +{ + return container_of(u, struct stmp_appuart_port, port); +} + +static struct uart_ops stmp_appuart_ops = { + .tx_empty = stmp_appuart_tx_empty, + .start_tx = stmp_appuart_start_tx, + .stop_tx = stmp_appuart_stop_tx, + .stop_rx = stmp_appuart_stop_rx, + .enable_ms = stmp_appuart_enable_ms, + .break_ctl = stmp_appuart_break_ctl, + .set_mctrl = stmp_appuart_set_mctrl, + .get_mctrl = stmp_appuart_get_mctrl, + .startup = stmp_appuart_startup, + .shutdown = stmp_appuart_shutdown, + .set_termios = stmp_appuart_settermios, + .type = stmp_appuart_type, + .release_port = stmp_appuart_release_port, + .request_port = stmp_appuart_request_port, + .config_port = stmp_appuart_config_port, + .verify_port = stmp_appuart_verify_port, +}; + +static inline int chr(int c) +{ + if (c < 0x20 || c > 0x7F) + return '#'; + return c; +} + +/* Allocate and initialize rx and tx DMA chains */ +static inline int stmp_appuart_dma_init(struct stmp_appuart_port *s) +{ + int err = 0; + struct stmp3xxx_dma_descriptor *t = &s->tx_desc; +#ifndef RX_CHAIN + struct stmp3xxx_dma_descriptor *r = &s->rx_desc; +#else + int i; +#endif + + err = stmp3xxx_dma_request(s->dma_rx, s->dev, s->dev->bus_id); + if (err) + goto out; + err = stmp3xxx_dma_request(s->dma_tx, s->dev, s->dev->bus_id); + if (err) + goto out1; + +#ifndef RX_CHAIN + err = stmp3xxx_dma_allocate_command(s->dma_rx, r); + if (err) + goto out2; +#endif + err = stmp3xxx_dma_allocate_command(s->dma_tx, t); + if (err) + goto out3; + t->virtual_buf_ptr = dma_alloc_coherent(s->dev, + TX_BUFFER_SIZE, + &t->command->buf_ptr, + GFP_DMA); + if (!t->virtual_buf_ptr) + goto out4; +#ifdef DEBUG + memset(t->virtual_buf_ptr, 0x4B, TX_BUFFER_SIZE); +#endif + +#ifndef RX_CHAIN + r->virtual_buf_ptr = dma_alloc_coherent(s->dev, + RX_BUFFER_SIZE, + &r->command->buf_ptr, + GFP_DMA); + if (!r->virtual_buf_ptr) + goto out5; +#ifdef DEBUG + memset(r->virtual_buf_ptr, 0x4C, RX_BUFFER_SIZE); +#endif +#else + stmp3xxx_dma_make_chain(s->dma_rx, &s->rx_chain, s->rxd, RX_CHAIN); + for (i = 0; i < RX_CHAIN; i++) { + struct stmp3xxx_dma_descriptor *r = s->rxd + i; + + r->command->cmd = + BF_APBX_CHn_CMD_XFER_COUNT(RX_BUFFER_SIZE) | + BF_APBX_CHn_CMD_CMDWORDS(1) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_SEMAPHORE | + BM_APBX_CHn_CMD_IRQONCMPLT | + BM_APBX_CHn_CMD_CHAIN | + BF_APBX_CHn_CMD_COMMAND( + BV_APBX_CHn_CMD_COMMAND__DMA_WRITE); + r->virtual_buf_ptr = dma_alloc_coherent(s->dev, + RX_BUFFER_SIZE, + &r->command->buf_ptr, + GFP_DMA); + r->command->pio_words[0] = /* BM_UARTAPP_CTRL0_RUN | */ + BF_UARTAPP_CTRL0_XFER_COUNT(RX_BUFFER_SIZE)| + BM_UARTAPP_CTRL0_RXTO_ENABLE | + BF_UARTAPP_CTRL0_RXTIMEOUT(3); + } +#endif + return 0; + + /* + * would be necessary on other error paths + + dma_free_coherent( s->dev, RX_BUFFER_SIZE, r->virtual_buf_ptr, + r->command->buf_ptr); + */ +out5: + dma_free_coherent(s->dev, TX_BUFFER_SIZE, t->virtual_buf_ptr, + t->command->buf_ptr); +out4: + stmp3xxx_dma_free_command(s->dma_tx, t); +out3: +#ifndef RX_CHAIN + stmp3xxx_dma_free_command(s->dma_rx, r); +#endif +out2: + stmp3xxx_dma_release(s->dma_tx); +out1: + stmp3xxx_dma_release(s->dma_rx); +out: + WARN_ON(err); + return err; +} + + +static void stmp_appuart_on(struct platform_device *dev) +{ + struct stmp_appuart_port *s = platform_get_drvdata(dev); + + if (!pio_mode) { + /* + Tell DMA to select UART. + Both DMA channels are shared between app UART and IrDA. + Target id of 0 means UART, 1 means IrDA + */ + stmp3xxx_dma_set_alt_target(s->dma_rx, 0); + stmp3xxx_dma_set_alt_target(s->dma_tx, 0); + /* + Reset DMA channels + */ + stmp3xxx_dma_reset_channel(s->dma_rx); + stmp3xxx_dma_reset_channel(s->dma_tx); + stmp3xxx_dma_enable_interrupt(s->dma_rx); + stmp3xxx_dma_enable_interrupt(s->dma_tx); + } +} + +#ifdef CONFIG_CPU_FREQ +static int stmp_appuart_updateclk(struct device *dev, void *clkdata) +{ + struct stmp_appuart_port *s = dev_get_drvdata(dev); + + if (s) { + s->port.uartclk = clk_get_rate(s->clk) * 1000; + /* FIXME: perform actual update */ + } + return 0; +} + +static int stmp_appuart_notifier(struct notifier_block *self, + unsigned long phase, void *p) +{ + int r = 0; + + if ((phase == CPUFREQ_POSTCHANGE) || + (phase == CPUFREQ_RESUMECHANGE)) { + /* get new uartclock and setspeed */ + r = driver_for_each_device( + &stmp_appuart_driver.driver, + NULL, p, + stmp_appuart_updateclk); + } + return (r == 0) ? NOTIFY_OK : NOTIFY_DONE; +} + +static struct notifier_block stmp_appuart_nb = { + .notifier_call = &stmp_appuart_notifier, +}; +#endif /* CONFIG_CPU_FREQ */ + +static int __devinit stmp_appuart_probe(struct platform_device *device) +{ + struct stmp_appuart_port *s; + int err = 0; + struct resource *r; + int i; + u32 version; + int (*pinctl)(int req, int id); + + s = kzalloc(sizeof(struct stmp_appuart_port), GFP_KERNEL); + if (!s) { + err = -ENOMEM; + goto out; + } + + spin_lock_init(&s->lock); + + s->clk = clk_get(NULL, "uart"); + if (IS_ERR(s->clk)) { + err = PTR_ERR(s->clk); + goto out_free; + } + clk_enable(s->clk); + r = platform_get_resource(device, IORESOURCE_MEM, 0); + if (!r) { + err = -ENXIO; + goto out_free_clk; + } + s->port.mapbase = r->start; + s->port.irq = platform_get_irq(device, 0); + s->port.ops = &stmp_appuart_ops; + s->port.iotype = UPIO_MEM; + s->port.line = device->id < 0 ? 0 : device->id; + s->port.fifosize = 16; + s->port.timeout = HZ/10; + s->port.uartclk = clk_get_rate(s->clk) * 1000; + s->port.type = PORT_STMP37xx; + s->port.dev = s->dev = get_device(&device->dev); + s->ctrl = 0; + s->keep_irq = 0; + + r = platform_get_resource(device, IORESOURCE_MEM, 0); + if (!r) { + err = -ENXIO; + goto out_free_clk; + } + + dev_dbg(s->dev, "%s\n", __func__); + for (i = 0; i < ARRAY_SIZE(s->irq); i++) { + s->irq[i] = platform_get_irq(device, i); + dev_dbg(s->dev, "Resources: irq[%d] = %d\n", i, s->irq[i]); + if (s->irq[i] < 0) { + err = s->irq[i]; + goto out_free_clk; + } + } + + r = platform_get_resource(device, IORESOURCE_DMA, 0); + if (!r) { + err = -ENXIO; + goto out_free; + } + s->dma_rx = r->start; + + r = platform_get_resource(device, IORESOURCE_DMA, 1); + if (!r) { + err = -ENXIO; + goto out_free; + } + s->dma_tx = r->start; + + r = platform_get_resource(device, IORESOURCE_MEM, 0); + if (!r) { + err = -ENXIO; + goto out_free; + } + s->mem = r->start; + s->memsize = r->end - r->start; + + +#ifdef CONFIG_CPU_FREQ + cpufreq_register_notifier(&stmp_appuart_nb, + CPUFREQ_TRANSITION_NOTIFIER); +#endif + platform_set_drvdata(device, s); + + device_init_wakeup(&device->dev, 1); + + stmp_appuart_dma_init(s); + stmp_appuart_on(device); + + pinctl = device->dev.platform_data; + if (pinctl) { + err = pinctl(1, device->id); + if (err) + goto out_free_clk; + } + + err = uart_add_one_port(&stmp_appuart_uart, &s->port); + if (err) + goto out_free_pins; + + version = HW_UARTAPP_VERSION_RD(); + printk(KERN_INFO"Found APPUART %d.%d.%d\n", + (version >> 24) & 0xFF, + (version >> 16) & 0xFF, + version & 0xFFFF); + return 0; + +out_free_pins: + if (pinctl) + pinctl(0, device->id); +out_free_clk: + clk_put(s->clk); +out_free: + platform_set_drvdata(device, NULL); + kfree(s); +out: + return err; +} + +static int __devexit stmp_appuart_remove(struct platform_device *device) +{ + struct stmp_appuart_port *s; + void (*pinctl)(int req, int id); + + s = platform_get_drvdata(device); + if (s) { + pinctl = device->dev.platform_data; + put_device(s->dev); + clk_disable(s->clk); + clk_put(s->clk); + uart_remove_one_port(&stmp_appuart_uart, &s->port); + if (pinctl) + pinctl(0, device->id); + kfree(s); + platform_set_drvdata(device, NULL); + } + + return 0; +} + +static int stmp_appuart_suspend(struct platform_device *device, + pm_message_t state) +{ +#ifdef CONFIG_PM + struct stmp_appuart_port *s = platform_get_drvdata(device); + + if (!s) + return 0; + s->keep_irq = device_may_wakeup(&device->dev); + uart_suspend_port(&stmp_appuart_uart, &s->port); + if (!s->keep_irq) + clk_disable(s->clk); +#endif + return 0; +} + +static int stmp_appuart_resume(struct platform_device *device) +{ +#ifdef CONFIG_PM + struct stmp_appuart_port *s = platform_get_drvdata(device); + + if (!s) + return 0; + + if (!s->keep_irq) + clk_enable(s->clk); + stmp_appuart_on(device); + uart_resume_port(&stmp_appuart_uart, &s->port); + s->keep_irq = 0; +#endif + return 0; +} + +static int __init stmp_appuart_init() +{ + int r; + + r = uart_register_driver(&stmp_appuart_uart); + if (r) + goto out; + r = platform_driver_register(&stmp_appuart_driver); + if (r) + goto out_err; + return 0; +out_err: + uart_unregister_driver(&stmp_appuart_uart); +out: + return r; +} + +static void __exit stmp_appuart_exit() +{ + platform_driver_unregister(&stmp_appuart_driver); + uart_unregister_driver(&stmp_appuart_uart); +} + +module_init(stmp_appuart_init) +module_exit(stmp_appuart_exit) + +static void stmp_appuart_stop_rx(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); + HW_UARTAPP_CTRL2_CLR_NB(s->mem, BM_UARTAPP_CTRL2_RXE); +} + +static void stmp_appuart_break_ctl(struct uart_port *u, int ctl) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s: break = %s\n", __func__, ctl ? "on" : "off"); + if (ctl) + HW_UARTAPP_LINECTRL_SET_NB(s->mem, BM_UARTAPP_LINECTRL_BRK); + else + HW_UARTAPP_LINECTRL_CLR_NB(s->mem, BM_UARTAPP_LINECTRL_BRK); +} + +static void stmp_appuart_enable_ms(struct uart_port *port) +{ + /* just empty */ +} + +static void stmp_appuart_set_mctrl(struct uart_port *u, unsigned mctrl) +{ + struct stmp_appuart_port *s = to_appuart(u); + + u32 ctrl = HW_UARTAPP_CTRL2_RD_NB(s->mem); + + dev_dbg(s->dev, "%s (%x)\n", __func__, mctrl); + ctrl &= ~BM_UARTAPP_CTRL2_RTS; + if (mctrl & TIOCM_RTS) { + dev_dbg(s->dev, "...RTS\n"); + ctrl |= BM_UARTAPP_CTRL2_RTS; + } + s->ctrl = mctrl; + dev_dbg(s->dev, "...%x; ctrl = %x\n", s->ctrl, ctrl); + HW_UARTAPP_CTRL2_WR_NB(s->mem, ctrl); +} + +static u32 stmp_appuart_get_mctrl(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + u32 stat = HW_UARTAPP_STAT_RD_NB(s->mem); + int ctrl2 = HW_UARTAPP_CTRL2_RD_NB(s->mem); + u32 mctrl = s->ctrl; + + dev_dbg(s->dev, "%s:\n", __func__); + mctrl &= ~TIOCM_CTS; + if (stat & BM_UARTAPP_STAT_CTS) { + dev_dbg(s->dev, "CTS"); + mctrl |= TIOCM_CTS; + } + if (ctrl2 & BM_UARTAPP_CTRL2_RTS) { + dev_dbg(s->dev, "RTS"); + mctrl |= TIOCM_RTS; + } + dev_dbg(s->dev, "...%x\n", mctrl); + return mctrl; +} + +static int stmp_appuart_request_port(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + int err = 0; + + if (!request_mem_region(s->mem, s->memsize, s->dev->bus_id)) + err = -ENXIO; + return err; + +} + +static void stmp_appuart_release_port(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + release_mem_region(s->mem, s->memsize); +} + +static int stmp_appuart_verify_port(struct uart_port *u, + struct serial_struct *ser) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); + return 0; +} + +static void stmp_appuart_config_port(struct uart_port *u, int flags) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); +} + + +static const char *stmp_appuart_type(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); + return s->dev->bus_id; +} + +static void stmp_appuart_settermios(struct uart_port *u, + struct ktermios *nw, struct ktermios *old) +{ + static struct ktermios saved; + struct stmp_appuart_port *s = to_appuart(u); + unsigned int cflag; + u32 bm, ctrl, ctrl2, div; + int err = 0; + unsigned baud; + + dev_dbg(s->dev, "%s\n", __func__); + + if (nw) + memcpy(&saved, nw, sizeof *nw); + else + nw = old = &saved; + + cflag = nw->c_cflag; + + ctrl = BM_UARTAPP_LINECTRL_FEN; + ctrl2 = HW_UARTAPP_CTRL2_RD_NB(s->mem); + + /* byte size */ + switch (cflag & CSIZE) { + case CS5: bm = 0; break; + case CS6: bm = 1; break; + case CS7: bm = 2; break; + case CS8: bm = 3; break; + default: err = -EINVAL; break; + } + if (err) + goto out; + + dev_dbg(s->dev, "Byte size %d bytes, mask %x\n", + bm + 5, BF_UARTAPP_LINECTRL_WLEN(bm)); + ctrl |= BF_UARTAPP_LINECTRL_WLEN(bm); + + /* parity */ + if (cflag & PARENB) { + dev_dbg(s->dev, "Parity check enabled\n"); + ctrl |= BM_UARTAPP_LINECTRL_PEN | BM_UARTAPP_LINECTRL_SPS; + if ((cflag & PARODD) == 0) { + dev_dbg(s->dev, "(Even) mask = %x\n", + BM_UARTAPP_LINECTRL_PEN | + BM_UARTAPP_LINECTRL_SPS | + BM_UARTAPP_LINECTRL_EPS); + ctrl |= BM_UARTAPP_LINECTRL_EPS; + } else + dev_dbg(s->dev, "(Odd) mask = %x\n", + BM_UARTAPP_LINECTRL_PEN | + BM_UARTAPP_LINECTRL_SPS); + } else + dev_dbg(s->dev, "Parity check disabled.\n"); + + /* figure out the stop bits requested */ + if (cflag & CSTOPB) { + dev_dbg(s->dev, "Stop bits, mask = %x\n", + BM_UARTAPP_LINECTRL_STP2); + ctrl |= BM_UARTAPP_LINECTRL_STP2; + } else + dev_dbg(s->dev, "No stop bits\n"); + + /* figure out the hardware flow control settings */ + if (cflag & CRTSCTS) { + dev_dbg(s->dev, "RTS/CTS flow control\n"); + ctrl2 |= BM_UARTAPP_CTRL2_CTSEN /* | BM_UARTAPP_CTRL2_RTSEN */; + } else { + dev_dbg(s->dev, "RTS/CTS disabled\n"); + ctrl2 &= ~BM_UARTAPP_CTRL2_CTSEN; + } + + /* set baud rate */ + baud = uart_get_baud_rate(u, nw, old, 0, u->uartclk); + dev_dbg(s->dev, "Baud rate requested: %d (clk = %d)\n", + baud, u->uartclk); + div = u->uartclk * 32 / baud; + ctrl |= BF_UARTAPP_LINECTRL_BAUD_DIVFRAC(div & 0x3F); + ctrl |= BF_UARTAPP_LINECTRL_BAUD_DIVINT(div >> 6); + + if ((cflag & CREAD) != 0) { + dev_dbg(s->dev, "RX started\n"); + ctrl2 |= BM_UARTAPP_CTRL2_RXE | BM_UARTAPP_CTRL2_RXDMAE; + } + + if (!err) { + dev_dbg(s->dev, "CTRLS = %x + %x\n", ctrl, ctrl2); + HW_UARTAPP_LINECTRL_WR_NB(s->mem, ctrl); + HW_UARTAPP_CTRL2_WR_NB(s->mem, ctrl2); + } +out: + return /* err */; +} + +static int stmp_appuart_free_irqs(struct stmp_appuart_port *s) +{ + int irqn = 0; + + if (s->keep_irq) { + dev_dbg(s->dev, "keep_irq != 0, ignoring\n"); + return 0; + } + for (irqn = 0; irqn < ARRAY_SIZE(s->irq); irqn++) + free_irq(s->irq[irqn], s); + return 0; +} + +void stmp_appuart_rx(struct stmp_appuart_port *s, u8 *rx_buffer, int count) +{ + u8 c; + int flag; + struct tty_struct *tty = s->port.info->port.tty; + u32 stat; + + spin_lock(&s->lock); + stat = HW_UARTAPP_STAT_RD_NB(s->mem); + + if (count < 0) { + count = HW_UARTAPP_STAT_RD_NB(s->mem) & BM_UARTAPP_STAT_RXCOUNT; + dev_dbg(s->dev, "count = %d\n", count); + } + + for (;;) { + if (!rx_buffer) { + if (stat & BM_UARTAPP_STAT_RXFE) + break; + c = HW_UARTAPP_DATA_RD_NB(s->mem) & 0xFF; + } else { + if (count-- <= 0) + break; + c = *rx_buffer++; + dev_dbg(s->dev, "Received: %x(%c)\n", c, chr(c)); + } + + flag = TTY_NORMAL; + if (stat & BM_UARTAPP_STAT_BERR) { + stat &= ~BM_UARTAPP_STAT_BERR; + s->port.icount.brk++; + if (uart_handle_break(&s->port)) + goto ignore; + flag = TTY_BREAK; + } else if (stat & BM_UARTAPP_STAT_PERR) { + stat &= ~BM_UARTAPP_STAT_PERR; + s->port.icount.parity++; + flag = TTY_PARITY; + } else if (stat & BM_UARTAPP_STAT_FERR) { + stat &= ~BM_UARTAPP_STAT_FERR; + s->port.icount.frame++; + flag = TTY_FRAME; + } + + if (stat & BM_UARTAPP_STAT_OERR) + s->port.icount.overrun++; + + if (uart_handle_sysrq_char(&s->port, c)) + goto ignore; + + uart_insert_char(&s->port, stat, + BM_UARTAPP_STAT_OERR, c, flag); +ignore: + if (pio_mode) { + HW_UARTAPP_STAT_WR_NB(s->mem, stat); + stat = HW_UARTAPP_STAT_RD_NB(s->mem); + } + } + + HW_UARTAPP_STAT_WR_NB(s->mem, stat); + tty_flip_buffer_push(tty); + spin_unlock(&s->lock); +} + +static inline void stmp_appuart_submit_rx(struct stmp_appuart_port *s) +{ +#ifndef RX_CHAIN + struct stmp3xxx_dma_descriptor *r = &s->rx_desc; + + dev_dbg(s->dev, "Submitting RX DMA request\n"); + r->command->cmd = + BM_APBX_CHn_CMD_HALTONTERMINATE | + BF_APBX_CHn_CMD_XFER_COUNT(RX_BUFFER_SIZE) | + BF_APBX_CHn_CMD_CMDWORDS(1) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_SEMAPHORE | + BM_APBX_CHn_CMD_IRQONCMPLT | + BF_APBX_CHn_CMD_COMMAND( + BV_APBX_CHn_CMD_COMMAND__DMA_WRITE); + r->command->pio_words[0] = HW_UARTAPP_CTRL0_RD() | + BF_UARTAPP_CTRL0_XFER_COUNT(RX_BUFFER_SIZE)| + BM_UARTAPP_CTRL0_RXTO_ENABLE | + BF_UARTAPP_CTRL0_RXTIMEOUT(3); + r->command->pio_words[0] &= ~BM_UARTAPP_CTRL0_RUN; + + stmp3xxx_dma_reset_channel(s->dma_rx); + stmp3xxx_dma_go(s->dma_rx, r, 1); +#endif +} + +static irqreturn_t stmp_appuart_irq_int(int irq, void *context) +{ + u32 istatus; + struct stmp_appuart_port *s = context; + u32 stat = HW_UARTAPP_STAT_RD_NB(s->mem); + + istatus = HW_UARTAPP_INTR_RD_NB(s->mem); + dev_dbg(s->dev, "IRQ: int(%d), status = %08X\n", irq, istatus); + + if (istatus & BM_UARTAPP_INTR_CTSMIS) { + uart_handle_cts_change(&s->port, stat & BM_UARTAPP_STAT_CTS); + dev_dbg(s->dev, "CTS change: %x\n", stat & BM_UARTAPP_STAT_CTS); + HW_UARTAPP_INTR_CLR_NB(s->mem, BM_UARTAPP_INTR_CTSMIS); + } + + else if (istatus & BM_UARTAPP_INTR_RTIS) { + dev_dbg(s->dev, "RX timeout, draining out\n"); + stmp_appuart_submit_rx(s); + } + + else + dev_info(s->dev, "Unhandled status %x\n", istatus); + + HW_UARTAPP_INTR_CLR_NB(s->mem, istatus & 0xFFFF); + + return IRQ_HANDLED; +} + + +static irqreturn_t stmp_appuart_irq_rx(int irq, void *context) +{ + struct stmp_appuart_port *s = context; + int count = -1; + + stmp3xxx_dma_clear_interrupt(s->dma_rx); + dev_dbg(s->dev, "%s(%d), count = %d\n", __func__, irq, count); + +#ifndef RX_CHAIN + stmp_appuart_rx(s, s->rx_desc.virtual_buf_ptr, count); + stmp_appuart_submit_rx(s); +#else + if (circ_advance_cooked(&s->rx_chain) == 0) { + BUG(); + return IRQ_HANDLED; + } + + circ_advance_active(&s->rx_chain, 1); + while (s->rx_chain.cooked_count) { + stmp_appuart_rx(s, + stmp3xxx_dma_circ_get_cooked_head(&s->rx_chain)-> + virtual_buf_ptr, -1); + circ_advance_free(&s->rx_chain, 1); + } +#endif + return IRQ_HANDLED; +} + +static void stmp_appuart_submit_tx(struct stmp_appuart_port *s, int size) +{ + struct stmp3xxx_dma_descriptor *d = &s->tx_desc; + + dev_dbg(s->dev, "Submitting TX DMA request, %d bytes\n", size); + d->command->pio_words[0] = + /* BM_UARTAPP_CTRL1_RUN | */ BF_UARTAPP_CTRL1_XFER_COUNT(size); + d->command->cmd = BF_APBX_CHn_CMD_XFER_COUNT(size) | + BF_APBX_CHn_CMD_CMDWORDS(1) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_SEMAPHORE | + BM_APBX_CHn_CMD_IRQONCMPLT | + BF_APBX_CHn_CMD_COMMAND(BV_APBX_CHn_CMD_COMMAND__DMA_READ); + stmp3xxx_dma_go(s->dma_tx, d, 1); +} + +static irqreturn_t stmp_appuart_irq_tx(int irq, void *context) +{ + struct stmp_appuart_port *s = context; + struct uart_port *u = &s->port; + int bytes; + + stmp3xxx_dma_clear_interrupt(s->dma_tx); + dev_dbg(s->dev, "%s(%d)\n", __func__, irq); + + bytes = stmp_appuart_copy_tx(u, s->tx_desc.virtual_buf_ptr, + TX_BUFFER_SIZE); + if (bytes > 0) { + dev_dbg(s->dev, "Sending %d bytes\n", bytes); + stmp_appuart_submit_tx(s, bytes); + } + return IRQ_HANDLED; +} + +static int stmp_appuart_request_irqs(struct stmp_appuart_port *s) +{ + int err = 0; + + /* + * order counts. resources should be listed in the same order + */ + irq_handler_t handlers[] = { + stmp_appuart_irq_int, + stmp_appuart_irq_rx, + stmp_appuart_irq_tx, + }; + char *handlers_names[] = { + "appuart internal", + "appuart rx", + "appuart tx", + }; + int irqn; + + if (s->keep_irq) { + dev_dbg(s->dev, "keep_irq is set, skipping request_irq"); + return 0; + } + for (irqn = 0; irqn < ARRAY_SIZE(handlers); irqn++) { + err = request_irq(s->irq[irqn], handlers[irqn], + 0, handlers_names[irqn], s); + dev_dbg(s->dev, "Requested IRQ %d with status %d\n", + s->irq[irqn], err); + if (err) + goto out; + } + return 0; +out: + stmp_appuart_free_irqs(s); + return err; +} + +static struct timer_list timer_task; + +static void stmp_appuart_check_rx(unsigned long data) +{ + stmp_appuart_rx((struct stmp_appuart_port *)data, NULL, -1); + mod_timer(&timer_task, jiffies + 2 * HZ); +} + +static int stmp_appuart_startup(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + int err; + + dev_dbg(s->dev, "%s\n", __func__); + + s->tx_buffer_index = 0; + + err = stmp_appuart_request_irqs(s); + if (err) + goto out; + + if (!s->keep_irq) + /* Release the block from reset and start the clocks. */ + stmp3xxx_reset_block(s->mem, 0); + + HW_UARTAPP_CTRL2_SET_NB(s->mem, BM_UARTAPP_CTRL2_UARTEN); + /* Enable the Application UART DMA bits. */ + if (!pio_mode) { + HW_UARTAPP_CTRL2_SET_NB(s->mem, + BM_UARTAPP_CTRL2_TXDMAE | BM_UARTAPP_CTRL2_RXDMAE | + BM_UARTAPP_CTRL2_DMAONERR); + /* clear any pending interrupts */ + HW_UARTAPP_INTR_WR_NB(s->mem, 0); + + /* reset all dma channels */ + stmp3xxx_dma_reset_channel(s->dma_tx); + stmp3xxx_dma_reset_channel(s->dma_rx); + } else { + HW_UARTAPP_INTR_WR_NB(s->mem, BM_UARTAPP_INTR_RXIEN | + BM_UARTAPP_INTR_RTIEN); + } + HW_UARTAPP_INTR_SET_NB(s->mem, BM_UARTAPP_INTR_CTSMIEN); + + /* + * Enable fifo so all four bytes of a DMA word are written to + * output (otherwise, only the LSB is written, ie. 1 in 4 bytes) + */ + HW_UARTAPP_LINECTRL_SET_NB(s->mem, BM_UARTAPP_LINECTRL_FEN); + + if (!pio_mode) { +#ifndef RX_CHAIN + stmp_appuart_submit_rx(s); +#else + circ_clear_chain(&s->rx_chain); + stmp3xxx_dma_go(s->dma_rx, &s->rxd[0], 0); + circ_advance_active(&s->rx_chain, 1); +#endif + } else { + init_timer(&timer_task); + timer_task.function = stmp_appuart_check_rx; + timer_task.expires = jiffies + HZ; + timer_task.data = (unsigned long)s; + add_timer(&timer_task); + } + +out: + return err; +} + +static void stmp_appuart_shutdown(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); + + if (!s->keep_irq) + /* set the IP block to RESET; this should disable clock too. */ + HW_UARTAPP_CTRL0_SET_NB(s->mem, BM_UARTAPP_CTRL0_SFTRST); + + if (!pio_mode) { + /* reset all dma channels */ + stmp3xxx_dma_reset_channel(s->dma_tx); + stmp3xxx_dma_reset_channel(s->dma_rx); + } else { + del_timer(&timer_task); + } + stmp_appuart_free_irqs(s); +} + +static unsigned int stmp_appuart_tx_empty(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + if (pio_mode) + if (HW_UARTAPP_STAT_RD_NB(s->mem) & BM_UARTAPP_STAT_TXFE) + return TIOCSER_TEMT; + else + return 0; + else + return stmp3xxx_dma_running(s->dma_tx) ? 0: TIOCSER_TEMT; +} + +static void stmp_appuart_start_tx(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + int bytes; + + dev_dbg(s->dev, "%s\n", __func__); + + /* enable transmitter */ + HW_UARTAPP_CTRL2_SET_NB(s->mem, BM_UARTAPP_CTRL2_TXE); + + if (!pio_mode) { + if (stmp3xxx_dma_running(s->dma_tx)) + return; + bytes = stmp_appuart_copy_tx(u, s->tx_desc.virtual_buf_ptr, + TX_BUFFER_SIZE); + if (bytes <= 0) + return; + + dev_dbg(s->dev, "Started DMA transfer with descriptor %p, " + "command %p, %d bytes long\n", + &s->tx_desc, s->tx_desc.command, bytes); + stmp_appuart_submit_tx(s, bytes); + } else { + int count = 0; + u8 c; + + while (!(HW_UARTAPP_STAT_RD_NB(s->mem) & + BM_UARTAPP_STAT_TXFF)) { + if (stmp_appuart_copy_tx(u, &c, 1) <= 0) + break; + dev_dbg(s->dev, "%d: '%c'/%x\n", + ++count, chr(c), c); + HW_UARTAPP_DATA_WR_NB(s->mem, c); + } + } +} + +static void stmp_appuart_stop_tx(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); + HW_UARTAPP_CTRL2_CLR_NB(s->mem, BM_UARTAPP_CTRL2_TXE); +} + +static int stmp_appuart_copy_tx(struct uart_port *u, u8 *target, + int tx_buffer_size) +{ + int last = 0, portion; + struct circ_buf *xmit = &u->info->xmit; + + while (last < tx_buffer_size) { /* let's fill the only descriptor */ + if (u->x_char) { + target[last++] = u->x_char; + u->x_char = 0; + } else if (!uart_circ_empty(xmit) && !uart_tx_stopped(u)) { + portion = min((u32)tx_buffer_size, + (u32)uart_circ_chars_pending(xmit)); + portion = min((u32)portion, + (u32)CIRC_CNT_TO_END(xmit->head, xmit->tail, + UART_XMIT_SIZE)); + memcpy(target + last, &xmit->buf[xmit->tail], portion); + xmit->tail = (xmit->tail + portion) & + (UART_XMIT_SIZE - 1); + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(u); + last += portion; + } else { /* All tx data copied into buffer */ + return last; + } + } + return last; +} + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("stmp3xxx app uart driver"); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +module_param(pio_mode, int, 0); |