@@ -33,10 +33,21 @@
#include <linux/clk.h>
#include <linux/serial_core.h>
#include <linux/irq.h>
+#include <linux/pm_runtime.h>
#include <plat/dma.h>
#include <plat/dmtimer.h>
#include <plat/omap-serial.h>
+#include <plat/serial.h>
+#include <plat/omap_device.h>
+
+#ifdef CONFIG_SERIAL_OMAP_CONSOLE
+#define omap_uart_console(port) ((port)->cons && (port)->cons->index == (port)->line)
+#else
+#define omap_uart_console(port) NULL
+#endif
+
+#define OMAP_UART_CLK_PUT_DELAY (5 * HZ)
static struct uart_omap_port *ui[OMAP_MAX_HSUART_PORTS];
@@ -44,6 +55,7 @@ static struct uart_omap_port *ui[OMAP_MAX_HSUART_PORTS];
static void uart_tx_dma_callback(int lch, u16 ch_status, void *data);
static void serial_omap_rx_timeout(unsigned long uart_no);
static int serial_omap_start_rxdma(struct uart_omap_port *up);
+static void omap_uart_mdr1_errataset(struct uart_omap_port *up);
static inline unsigned int serial_in(struct uart_omap_port *up, int offset)
{
@@ -90,6 +102,73 @@ serial_omap_get_divisor(struct uart_port *port, unsigned int baud)
return port->uartclk/(baud * divisor);
}
+static void omap_uart_smart_idle(struct uart_omap_port *up)
+{
+ struct platform_device *pdev = up->pdev;
+ struct omap_device *od = container_of(pdev, struct omap_device, pdev);
+
+ omap_hwmod_set_slave_idlemode(od->hwmods[0], HWMOD_IDLEMODE_SMART);
+}
+
+static void serial_omap_port_disable(struct uart_omap_port *up)
+{
+ if (!pm_runtime_suspended(&up->pdev->dev)) {
+ del_timer(&up->inactivity_timer);
+ if (omap_uart_console(&up->port))
+ console_stop(up->port.cons);
+
+ pm_runtime_put_sync(&up->pdev->dev);
+ }
+}
+
+static void serial_omap_inactivity_timer(unsigned long uart_no)
+{
+ struct uart_omap_port *up = ui[uart_no];
+
+ up->can_sleep = 1;
+ omap_uart_smart_idle(up);
+}
+
+static void serial_omap_port_enable(struct uart_omap_port *up)
+{
+ if (pm_runtime_suspended(&up->pdev->dev)) {
+ if (omap_uart_console(&up->port))
+ console_start(up->port.cons);
+
+ pm_runtime_get_sync(&up->pdev->dev);
+ }
+
+ up->can_sleep = 0;
+ mod_timer(&up->inactivity_timer, jiffies + OMAP_UART_CLK_PUT_DELAY);
+}
+
+void omap_uart_prepare_idle(int num)
+{
+ struct uart_omap_port *up = ui[num];
+
+ if (up && up->can_sleep)
+ serial_omap_port_disable(up);
+}
+
+void omap_uart_resume_idle(int num)
+{
+ struct uart_omap_port *up = ui[num];
+ struct omap_device *od;
+ struct platform_device *pdev;
+
+ if (!up)
+ return;
+
+ pdev = up->pdev;
+ od = container_of(pdev, struct omap_device, pdev);
+
+ if (omap_hmwod_pad_wakeup_status(od->hwmods[0]) == true)
+ serial_omap_port_enable(up);
+
+ if (up->wk_st && (__raw_readl(up->wk_st) & up->wk_mask))
+ serial_omap_port_enable(up);
+}
+
static void serial_omap_stop_rxdma(struct uart_omap_port *up)
{
if (up->uart_dma.rx_dma_used) {
@@ -105,6 +184,7 @@ static void serial_omap_enable_ms(struct uart_port *port)
{
struct uart_omap_port *up = (struct uart_omap_port *)port;
+ serial_omap_port_enable(up);
dev_dbg(up->port.dev, "serial_omap_enable_ms+%d\n", up->pdev->id);
up->ier |= UART_IER_MSI;
serial_out(up, UART_IER, up->ier);
@@ -114,6 +194,7 @@ static void serial_omap_stop_tx(struct uart_port *port)
{
struct uart_omap_port *up = (struct uart_omap_port *)port;
+ serial_omap_port_enable(up);
if (up->use_dma &&
up->uart_dma.tx_dma_channel != OMAP_UART_DMA_CH_FREE) {
/*
@@ -137,6 +218,7 @@ static void serial_omap_stop_rx(struct uart_port *port)
{
struct uart_omap_port *up = (struct uart_omap_port *)port;
+ serial_omap_port_enable(up);
if (up->use_dma)
serial_omap_stop_rxdma(up);
up->ier &= ~UART_IER_RLSI;
@@ -258,6 +340,7 @@ static void serial_omap_start_tx(struct uart_port *port)
unsigned int start;
int ret = 0;
+ serial_omap_port_enable(up);
if (!up->use_dma) {
serial_omap_enable_ier_thri(up);
return;
@@ -351,6 +434,7 @@ static inline irqreturn_t serial_omap_irq(int irq, void *dev_id)
unsigned int iir, lsr;
unsigned long flags;
+ serial_omap_port_enable(up);
iir = serial_in(up, UART_IIR);
if (iir & UART_IIR_NO_INT)
return IRQ_NONE;
@@ -385,6 +469,7 @@ static unsigned int serial_omap_tx_empty(struct uart_port *port)
unsigned long flags = 0;
unsigned int ret = 0;
+ serial_omap_port_enable(up);
dev_dbg(up->port.dev, "serial_omap_tx_empty+%d\n", up->pdev->id);
spin_lock_irqsave(&up->port.lock, flags);
ret = serial_in(up, UART_LSR) & UART_LSR_TEMT ? TIOCSER_TEMT : 0;
@@ -399,6 +484,7 @@ static unsigned int serial_omap_get_mctrl(struct uart_port *port)
unsigned char status;
unsigned int ret = 0;
+ serial_omap_port_enable(up);
status = check_modem_status(up);
dev_dbg(up->port.dev, "serial_omap_get_mctrl+%d\n", up->pdev->id);
@@ -419,6 +505,7 @@ static void serial_omap_set_mctrl(struct uart_port *port, unsigned int mctrl)
unsigned char mcr = 0;
dev_dbg(up->port.dev, "serial_omap_set_mctrl+%d\n", up->pdev->id);
+ serial_omap_port_enable(up);
if (mctrl & TIOCM_RTS)
mcr |= UART_MCR_RTS;
if (mctrl & TIOCM_DTR)
@@ -440,6 +527,7 @@ static void serial_omap_break_ctl(struct uart_port *port, int break_state)
unsigned long flags = 0;
dev_dbg(up->port.dev, "serial_omap_break_ctl+%d\n", up->pdev->id);
+ serial_omap_port_enable(up);
spin_lock_irqsave(&up->port.lock, flags);
if (break_state == -1)
up->lcr |= UART_LCR_SBC;
@@ -465,6 +553,7 @@ static int serial_omap_startup(struct uart_port *port)
dev_dbg(up->port.dev, "serial_omap_startup+%d\n", up->pdev->id);
+ serial_omap_port_enable(up);
/*
* Clear the FIFO buffers and disable them.
* (they will be reenabled in set_termios())
@@ -530,6 +619,8 @@ static void serial_omap_shutdown(struct uart_port *port)
unsigned long flags = 0;
dev_dbg(up->port.dev, "serial_omap_shutdown+%d\n", up->pdev->id);
+
+ serial_omap_port_enable(up);
/*
* Disable interrupts from this port
*/
@@ -668,6 +759,10 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios,
baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/13);
quot = serial_omap_get_divisor(port, baud);
+ up->dll = quot & 0xff;
+ up->dlh = quot >> 8;
+ up->mdr1 = UART_OMAP_MDR1_DISABLE;
+
up->fcr = UART_FCR_R_TRIG_01 | UART_FCR_T_TRIG_01 |
UART_FCR_ENABLE_FIFO;
if (up->use_dma)
@@ -677,6 +772,7 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios,
* Ok, we're now changing the port state. Do it with
* interrupts disabled.
*/
+ serial_omap_port_enable(up);
spin_lock_irqsave(&up->port.lock, flags);
/*
@@ -755,8 +851,11 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios,
serial_out(up, UART_MCR, up->mcr);
/* Protocol, Baud Rate, and Interrupt Settings */
+ if (up->errata & UART_ERRATA_i202_MDR1_ACCESS)
+ omap_uart_mdr1_errataset(up);
+ else
+ serial_out(up, UART_OMAP_MDR1, up->mdr1);
- serial_out(up, UART_OMAP_MDR1, UART_OMAP_MDR1_DISABLE);
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
up->efr = serial_in(up, UART_EFR);
@@ -766,8 +865,8 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios,
serial_out(up, UART_IER, 0);
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
- serial_out(up, UART_DLL, quot & 0xff); /* LS of divisor */
- serial_out(up, UART_DLM, quot >> 8); /* MS of divisor */
+ serial_out(up, UART_DLL, up->dll); /* LS of divisor */
+ serial_out(up, UART_DLM, up->dlh); /* MS of divisor */
serial_out(up, UART_LCR, 0);
serial_out(up, UART_IER, up->ier);
@@ -777,9 +876,14 @@ serial_omap_set_termios(struct uart_port *port, struct ktermios *termios,
serial_out(up, UART_LCR, cval);
if (baud > 230400 && baud != 3000000)
- serial_out(up, UART_OMAP_MDR1, UART_OMAP_MDR1_13X_MODE);
+ up->mdr1 = UART_OMAP_MDR1_13X_MODE;
+ else
+ up->mdr1 = UART_OMAP_MDR1_16X_MODE;
+
+ if (up->errata & UART_ERRATA_i202_MDR1_ACCESS)
+ omap_uart_mdr1_errataset(up);
else
- serial_out(up, UART_OMAP_MDR1, UART_OMAP_MDR1_16X_MODE);
+ serial_out(up, UART_OMAP_MDR1, up->mdr1);
/* Hardware Flow Control Configuration */
@@ -818,6 +922,8 @@ serial_omap_pm(struct uart_port *port, unsigned int state,
unsigned char efr;
dev_dbg(up->port.dev, "serial_omap_pm+%d\n", up->pdev->id);
+
+ serial_omap_port_enable(up);
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
efr = serial_in(up, UART_EFR);
serial_out(up, UART_EFR, efr | UART_EFR_ECB);
@@ -827,6 +933,8 @@ serial_omap_pm(struct uart_port *port, unsigned int state,
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
serial_out(up, UART_EFR, efr);
serial_out(up, UART_LCR, 0);
+ if (state)
+ serial_omap_port_disable(up);
}
static void serial_omap_release_port(struct uart_port *port)
@@ -904,6 +1012,8 @@ static inline void wait_for_xmitr(struct uart_omap_port *up)
static void serial_omap_poll_put_char(struct uart_port *port, unsigned char ch)
{
struct uart_omap_port *up = (struct uart_omap_port *)port;
+
+ serial_omap_port_enable(up);
wait_for_xmitr(up);
serial_out(up, UART_TX, ch);
}
@@ -911,8 +1021,10 @@ static void serial_omap_poll_put_char(struct uart_port *port, unsigned char ch)
static int serial_omap_poll_get_char(struct uart_port *port)
{
struct uart_omap_port *up = (struct uart_omap_port *)port;
- unsigned int status = serial_in(up, UART_LSR);
+ unsigned int status;
+ serial_omap_port_enable(up);
+ status = serial_in(up, UART_LSR);
if (!(status & UART_LSR_DR))
return NO_POLL_CHAR;
@@ -922,7 +1034,6 @@ static int serial_omap_poll_get_char(struct uart_port *port)
#endif /* CONFIG_CONSOLE_POLL */
#ifdef CONFIG_SERIAL_OMAP_CONSOLE
-
static struct uart_omap_port *serial_omap_console_ports[4];
static struct uart_driver serial_omap_reg;
@@ -931,6 +1042,7 @@ static void serial_omap_console_putchar(struct uart_port *port, int ch)
{
struct uart_omap_port *up = (struct uart_omap_port *)port;
+ serial_omap_port_enable(up);
wait_for_xmitr(up);
serial_out(up, UART_TX, ch);
}
@@ -944,6 +1056,7 @@ serial_omap_console_write(struct console *co, const char *s,
unsigned int ier;
int locked = 1;
+ serial_omap_port_enable(up);
local_irq_save(flags);
if (up->port.sysrq)
locked = 0;
@@ -1058,22 +1171,25 @@ static struct uart_driver serial_omap_reg = {
.cons = OMAP_CONSOLE,
};
-static int
-serial_omap_suspend(struct platform_device *pdev, pm_message_t state)
+static int serial_omap_suspend(struct device *dev)
{
- struct uart_omap_port *up = platform_get_drvdata(pdev);
+ struct uart_omap_port *up = dev_get_drvdata(dev);
- if (up)
+ if (up) {
uart_suspend_port(&serial_omap_reg, &up->port);
+ serial_omap_port_disable(up);
+ }
+
return 0;
}
-static int serial_omap_resume(struct platform_device *dev)
+static int serial_omap_resume(struct device *dev)
{
- struct uart_omap_port *up = platform_get_drvdata(dev);
+ struct uart_omap_port *up = dev_get_drvdata(dev);
if (up)
uart_resume_port(&serial_omap_reg, &up->port);
+
return 0;
}
@@ -1221,9 +1337,10 @@ static void uart_tx_dma_callback(int lch, u16 ch_status, void *data)
static int serial_omap_probe(struct platform_device *pdev)
{
- struct uart_omap_port *up;
+ struct uart_omap_port *up = NULL;
struct resource *mem, *irq, *dma_tx, *dma_rx;
struct omap_uart_port_info *omap_up_info = pdev->dev.platform_data;
+ struct omap_device *od;
int ret = -ENOSPC;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -1273,12 +1390,23 @@ static int serial_omap_probe(struct platform_device *pdev)
up->port.ops = &serial_omap_pops;
up->port.line = pdev->id;
- up->port.membase = omap_up_info->membase;
- up->port.mapbase = omap_up_info->mapbase;
+ up->port.mapbase = mem->start;
+ up->port.membase = ioremap(mem->start, mem->end - mem->start);
+
+ if (!up->port.membase) {
+ dev_err(&pdev->dev, "can't ioremap UART\n");
+ ret = -ENOMEM;
+ goto err1;
+ }
+
up->port.flags = omap_up_info->flags;
- up->port.irqflags = omap_up_info->irqflags;
up->port.uartclk = omap_up_info->uartclk;
up->uart_dma.uart_base = mem->start;
+ up->errata = omap_up_info->errata;
+
+ up->wk_st = omap_up_info->wk_st;
+ up->wk_en = omap_up_info->wk_en;
+ up->wk_mask = omap_up_info->wk_mask;
if (omap_up_info->dma_enabled) {
up->uart_dma.uart_dma_tx = dma_tx->start;
@@ -1291,19 +1419,35 @@ static int serial_omap_probe(struct platform_device *pdev)
up->uart_dma.tx_dma_channel = OMAP_UART_DMA_CH_FREE;
up->uart_dma.rx_dma_channel = OMAP_UART_DMA_CH_FREE;
}
+ init_timer(&(up->inactivity_timer));
+ up->inactivity_timer.function = serial_omap_inactivity_timer;
+ up->inactivity_timer.data = up->pdev->id;
+
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_irq_safe(&pdev->dev);
+
+ if (omap_up_info->console_uart) {
+ od = container_of(pdev, struct omap_device, pdev);
+ omap_hwmod_idle(od->hwmods[0]);
+ serial_omap_port_enable(up);
+ }
ui[pdev->id] = up;
serial_omap_add_console_port(up);
ret = uart_add_one_port(&serial_omap_reg, &up->port);
if (ret != 0)
- goto do_release_region;
+ goto err1;
+ dev_set_drvdata(&pdev->dev, up);
platform_set_drvdata(pdev, up);
+
return 0;
err:
dev_err(&pdev->dev, "[UART%d]: failure [%s]: %d\n",
pdev->id, __func__, ret);
+err1:
+ kfree(up);
do_release_region:
release_mem_region(mem->start, (mem->end - mem->start) + 1);
return ret;
@@ -1315,20 +1459,138 @@ static int serial_omap_remove(struct platform_device *dev)
platform_set_drvdata(dev, NULL);
if (up) {
+ pm_runtime_disable(&up->pdev->dev);
uart_remove_one_port(&serial_omap_reg, &up->port);
kfree(up);
}
return 0;
}
+/*
+ * Work Around for Errata i202 (3430 - 1.12, 3630 - 1.6)
+ * The access to uart register after MDR1 Access
+ * causes UART to corrupt data.
+ *
+ * Need a delay =
+ * 5 L4 clock cycles + 5 UART functional clock cycle (@48MHz = ~0.2uS)
+ * give 10 times as much
+ */
+static void omap_uart_mdr1_errataset(struct uart_omap_port *up)
+{
+ u8 timeout = 255;
+
+ serial_out(up, UART_OMAP_MDR1, up->mdr1);
+ udelay(2);
+ serial_out(up, UART_FCR, up->fcr | UART_FCR_CLEAR_XMIT |
+ UART_FCR_CLEAR_RCVR);
+ /*
+ * Wait for FIFO to empty: when empty, RX_FIFO_E bit is 0 and
+ * TX_FIFO_E bit is 1.
+ */
+ while (UART_LSR_THRE != (serial_in(up, UART_LSR) &
+ (UART_LSR_THRE | UART_LSR_DR))) {
+ timeout--;
+ if (!timeout) {
+ /* Should *never* happen. we warn and carry on */
+ dev_crit(&up->pdev->dev, "Errata i202: timedout %x\n",
+ serial_in(up, UART_LSR));
+ break;
+ }
+ udelay(1);
+ }
+}
+
+static void omap_uart_restore_context(struct uart_omap_port *up)
+{
+ u16 efr = 0;
+
+ if (up->errata & UART_ERRATA_i202_MDR1_ACCESS)
+ omap_uart_mdr1_errataset(up);
+ else
+ serial_out(up, UART_OMAP_MDR1, up->mdr1);
+ serial_out(up, UART_LCR, 0xBF); /* Config B mode */
+ efr = serial_in(up, UART_EFR);
+ serial_out(up, UART_EFR, UART_EFR_ECB);
+ serial_out(up, UART_LCR, 0x0); /* Operational mode */
+ serial_out(up, UART_IER, 0x0);
+ serial_out(up, UART_LCR, 0xBF); /* Config B mode */
+ serial_out(up, UART_DLL, up->dll);
+ serial_out(up, UART_DLM, up->dlh);
+ serial_out(up, UART_LCR, 0x0); /* Operational mode */
+ serial_out(up, UART_IER, up->ier);
+ serial_out(up, UART_FCR, up->fcr);
+ serial_out(up, UART_LCR, 0x80);
+ serial_out(up, UART_MCR, up->mcr);
+ serial_out(up, UART_LCR, 0xBF); /* Config B mode */
+ serial_out(up, UART_EFR, efr);
+ serial_out(up, UART_LCR, UART_LCR_WLEN8);
+ if (up->errata & UART_ERRATA_i202_MDR1_ACCESS)
+ omap_uart_mdr1_errataset(up);
+ else
+ /* UART 16x mode */
+ serial_out(up, UART_OMAP_MDR1, up->mdr1);
+}
+
+static void omap_uart_enable_wakeup(struct uart_omap_port *up)
+{
+ /* Set wake-enable bit */
+ if (up->wk_en && up->wk_mask) {
+ u32 v = __raw_readl(up->wk_en);
+ v |= up->wk_mask;
+ __raw_writel(v, up->wk_en);
+ }
+}
+
+static void omap_uart_disable_wakeup(struct uart_omap_port *up)
+{
+ /* Clear wake-enable bit */
+ if (up->wk_en && up->wk_mask) {
+ u32 v = __raw_readl(up->wk_en);
+ v &= ~up->wk_mask;
+ __raw_writel(v, up->wk_en);
+ }
+}
+
+static int omap_serial_runtime_suspend(struct device *dev)
+{
+ struct uart_omap_port *up = dev_get_drvdata(dev);
+
+ if (!up)
+ goto done;
+
+ if (device_may_wakeup(&up->pdev->dev))
+ omap_uart_enable_wakeup(up);
+ else
+ omap_uart_disable_wakeup(up);
+done:
+ return 0;
+}
+
+static int omap_serial_runtime_resume(struct device *dev)
+{
+ struct uart_omap_port *up = dev_get_drvdata(dev);
+
+ if (!up)
+ goto done;
+
+ omap_uart_restore_context(up);
+done:
+ return 0;
+}
+
+static const struct dev_pm_ops omap_serial_dev_pm_ops = {
+ .suspend = serial_omap_suspend,
+ .resume = serial_omap_resume,
+ .runtime_suspend = omap_serial_runtime_suspend,
+ .runtime_resume = omap_serial_runtime_resume,
+};
+
static struct platform_driver serial_omap_driver = {
.probe = serial_omap_probe,
.remove = serial_omap_remove,
-
- .suspend = serial_omap_suspend,
- .resume = serial_omap_resume,
.driver = {
.name = DRIVER_NAME,
+ .pm = &omap_serial_dev_pm_ops,
},
};