mirror of
https://github.com/genesi/linux-legacy.git
synced 2026-09-05 22:37:51 +00:00
No all regiser use _SCT. stmp3xxx_setl may write to wrong address. please use __raw_write (value, REG_XXX_SET) to write date to SCT register. Only REG_XXX_SCT is defined when it exist at hardware. Signed-off-by: Frank.Li <Frank.Li@freescale.com>
1082 lines
26 KiB
C
1082 lines
26 KiB
C
/*
|
|
* 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 <mach/platform.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 = "stmp3xxx-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, dev_name(s->dev));
|
|
if (err)
|
|
goto out;
|
|
err = stmp3xxx_dma_request(s->dma_tx, s->dev, dev_name(s->dev));
|
|
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(RX_BUFFER_SIZE, APBX_CHn_CMD_XFER_COUNT) |
|
|
BF(1, APBX_CHn_CMD_CMDWORDS) |
|
|
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(RX_BUFFER_SIZE, UARTAPP_CTRL0_XFER_COUNT) |
|
|
BM_UARTAPP_CTRL0_RXTO_ENABLE |
|
|
BF(3, UARTAPP_CTRL0_RXTIMEOUT);
|
|
}
|
|
#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_IMX;
|
|
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 = (void __iomem *)(r->start - STMP3XXX_REGS_PHBASE
|
|
+ (u32)STMP3XXX_REGS_BASE);
|
|
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 = __raw_readl(REGS_UARTAPP1_BASE + HW_UARTAPP_VERSION);
|
|
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__);
|
|
__raw_writel(BM_UARTAPP_CTRL2_RXE, s->mem + HW_STMP3XXX_CLR);
|
|
}
|
|
|
|
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)
|
|
__raw_writel(BM_UARTAPP_LINECTRL_BRK,
|
|
s->mem + HW_UARTAPP_LINECTRL_SET);
|
|
else
|
|
__raw_writel(BM_UARTAPP_LINECTRL_BRK,
|
|
s->mem + HW_UARTAPP_LINECTRL_CLR);
|
|
}
|
|
|
|
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 = __raw_readl(s->mem + HW_UARTAPP_CTRL2);
|
|
|
|
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);
|
|
__raw_writel(ctrl, s->mem + HW_UARTAPP_CTRL2);
|
|
}
|
|
|
|
static u32 stmp_appuart_get_mctrl(struct uart_port *u)
|
|
{
|
|
struct stmp_appuart_port *s = to_appuart(u);
|
|
u32 stat = __raw_readl(s->mem + HW_UARTAPP_STAT);
|
|
int ctrl2 = __raw_readl(s->mem + HW_UARTAPP_CTRL2);
|
|
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((u32)s->mem, s->memsize, dev_name(s->dev)))
|
|
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((u32)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 dev_name(s->dev);
|
|
}
|
|
|
|
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 = __raw_readl(s->mem + HW_UARTAPP_CTRL2);
|
|
|
|
/* 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(bm, UARTAPP_LINECTRL_WLEN));
|
|
ctrl |= BF(bm, UARTAPP_LINECTRL_WLEN);
|
|
|
|
/* 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(div & 0x3F, UARTAPP_LINECTRL_BAUD_DIVFRAC);
|
|
ctrl |= BF(div >> 6, UARTAPP_LINECTRL_BAUD_DIVINT);
|
|
|
|
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);
|
|
__raw_writel(ctrl,
|
|
s->mem + HW_UARTAPP_LINECTRL);
|
|
__raw_writel(ctrl2,
|
|
s->mem + HW_UARTAPP_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 = __raw_readl(s->mem + HW_UARTAPP_STAT);
|
|
|
|
if (count < 0) {
|
|
count =
|
|
__raw_readl(s->mem +
|
|
HW_UARTAPP_STAT) & BM_UARTAPP_STAT_RXCOUNT;
|
|
dev_dbg(s->dev, "count = %d\n", count);
|
|
}
|
|
|
|
for (;;) {
|
|
if (!rx_buffer) {
|
|
if (stat & BM_UARTAPP_STAT_RXFE)
|
|
break;
|
|
c = __raw_readl(s->mem + HW_UARTAPP_DATA) & 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) {
|
|
__raw_writel(stat, s->mem + HW_UARTAPP_STAT);
|
|
stat =
|
|
__raw_readl(s->mem + HW_UARTAPP_STAT);
|
|
}
|
|
}
|
|
|
|
__raw_writel(stat, s->mem + HW_UARTAPP_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(RX_BUFFER_SIZE, APBX_CHn_CMD_XFER_COUNT) |
|
|
BF(1, APBX_CHn_CMD_CMDWORDS) |
|
|
BM_APBX_CHn_CMD_WAIT4ENDCMD |
|
|
BM_APBX_CHn_CMD_SEMAPHORE |
|
|
BM_APBX_CHn_CMD_IRQONCMPLT |
|
|
BF(BV_APBX_CHn_CMD_COMMAND__DMA_WRITE, APBX_CHn_CMD_COMMAND);
|
|
r->command->pio_words[0] =
|
|
__raw_readl(REGS_UARTAPP1_BASE +
|
|
HW_UARTAPP_CTRL0) | BF(RX_BUFFER_SIZE,
|
|
UARTAPP_CTRL0_XFER_COUNT) |
|
|
BM_UARTAPP_CTRL0_RXTO_ENABLE | BF(3, UARTAPP_CTRL0_RXTIMEOUT);
|
|
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 = __raw_readl(s->mem + HW_UARTAPP_STAT);
|
|
|
|
istatus = __raw_readl(s->mem + HW_UARTAPP_INTR);
|
|
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);
|
|
__raw_writel(BM_UARTAPP_INTR_CTSMIS,
|
|
s->mem + HW_UARTAPP_INTR_CLR);
|
|
}
|
|
|
|
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);
|
|
|
|
__raw_writel(istatus & 0xFFFF,
|
|
s->mem + HW_UARTAPP_INTR_CLR);
|
|
|
|
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(size, UARTAPP_CTRL1_XFER_COUNT);
|
|
d->command->cmd = BF(size, APBX_CHn_CMD_XFER_COUNT) |
|
|
BF(1, APBX_CHn_CMD_CMDWORDS) |
|
|
BM_APBX_CHn_CMD_WAIT4ENDCMD |
|
|
BM_APBX_CHn_CMD_SEMAPHORE |
|
|
BM_APBX_CHn_CMD_IRQONCMPLT |
|
|
BF(BV_APBX_CHn_CMD_COMMAND__DMA_READ, APBX_CHn_CMD_COMMAND);
|
|
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);
|
|
|
|
__raw_writel(BM_UARTAPP_CTRL2_UARTEN,
|
|
s->mem + HW_UARTAPP_CTRL2_SET);
|
|
/* Enable the Application UART DMA bits. */
|
|
if (!pio_mode) {
|
|
__raw_writel(BM_UARTAPP_CTRL2_TXDMAE | BM_UARTAPP_CTRL2_RXDMAE
|
|
| BM_UARTAPP_CTRL2_DMAONERR,
|
|
s->mem + HW_UARTAPP_CTRL2_SET);
|
|
/* clear any pending interrupts */
|
|
__raw_writel(0, s->mem + HW_UARTAPP_INTR);
|
|
|
|
/* reset all dma channels */
|
|
stmp3xxx_dma_reset_channel(s->dma_tx);
|
|
stmp3xxx_dma_reset_channel(s->dma_rx);
|
|
} else {
|
|
__raw_writel(BM_UARTAPP_INTR_RXIEN |
|
|
BM_UARTAPP_INTR_RTIEN,
|
|
s->mem + HW_UARTAPP_INTR);
|
|
}
|
|
__raw_writel(BM_UARTAPP_INTR_CTSMIEN,
|
|
s->mem + HW_UARTAPP_INTR_SET);
|
|
|
|
/*
|
|
* 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)
|
|
*/
|
|
__raw_writel(BM_UARTAPP_LINECTRL_FEN, s->mem + HW_UARTAPP_LINECTRL_SET);
|
|
|
|
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. */
|
|
__raw_writel(
|
|
BM_UARTAPP_CTRL0_SFTRST, s->mem + HW_UARTAPP_CTRL0_SET);
|
|
|
|
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 (__raw_readl(s->mem + HW_UARTAPP_STAT) &
|
|
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 */
|
|
__raw_writel(BM_UARTAPP_CTRL2_TXE, s->mem + HW_UARTAPP_CTRL2_SET);
|
|
|
|
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 (!
|
|
(__raw_readl
|
|
(s->mem + HW_UARTAPP_STAT) & 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);
|
|
__raw_writel(c, s->mem + HW_UARTAPP_DATA);
|
|
}
|
|
}
|
|
}
|
|
|
|
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__);
|
|
__raw_writel(BM_UARTAPP_CTRL2_TXE, s->mem + HW_UARTAPP_CTRL2_CLR);
|
|
}
|
|
|
|
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);
|