From: Tero Kristo <tero.kristo@xxxxxxxxx> This patch contains following improvements: - Only RX interrupt will now kick the sleep prevent timer - TX fifo status is checked before disabling clocks, this will prevent on-going transmission to be cut - Smartidle is now enabled/disabled only while switching clocks, as having smartidle enabled while RX/TX prevents any wakeups from being received from UART module - Added workqueue for wakeup checks, as jiffy timer access within the idle loop results into skewed timers as jiffy timers are stopped - Added garbage_timer for ignoring the first character received during the first tick after clock enable, this prevents garbage characters to be received in low sleep states - omap_uart_enable_irqs() changed to use enable_irq / disable_irq instead of request / free. Using request/free changes the behavior after first suspend due to reversed interrupt handler ordering Signed-off-by: Tero Kristo <tero.kristo@xxxxxxxxx> --- arch/arm/mach-omap2/serial.c | 67 +++++++++++++++++++++++++++++++++++------- 1 files changed, 56 insertions(+), 11 deletions(-) diff --git a/arch/arm/mach-omap2/serial.c b/arch/arm/mach-omap2/serial.c index 5f3035e..06d18f5 100644 --- a/arch/arm/mach-omap2/serial.c +++ b/arch/arm/mach-omap2/serial.c @@ -23,6 +23,7 @@ #include <linux/serial_reg.h> #include <linux/clk.h> #include <linux/io.h> +#include <linux/workqueue.h> #include <plat/common.h> #include <plat/board.h> @@ -48,7 +49,10 @@ struct omap_uart_state { int num; int can_sleep; struct timer_list timer; + struct timer_list garbage_timer; + struct work_struct wakeup_work; u32 timeout; + u8 garbage_ignore; void __iomem *wk_st; void __iomem *wk_en; @@ -243,6 +247,11 @@ static inline void omap_uart_save_context(struct omap_uart_state *uart) {} static inline void omap_uart_restore_context(struct omap_uart_state *uart) {} #endif /* CONFIG_PM && CONFIG_ARCH_OMAP3 */ +#ifdef CONFIG_PM +static void omap_uart_smart_idle_enable(struct omap_uart_state *uart, + int enable); +#endif + static inline void omap_uart_enable_clocks(struct omap_uart_state *uart) { if (uart->clocked) @@ -252,6 +261,15 @@ static inline void omap_uart_enable_clocks(struct omap_uart_state *uart) clk_enable(uart->fck); uart->clocked = 1; omap_uart_restore_context(uart); +#ifdef CONFIG_PM + omap_uart_smart_idle_enable(uart, 0); +#endif + + /* Set up garbage timer to ignore RX during first jiffy */ + if (uart->timeout) { + mod_timer(&uart->garbage_timer, jiffies + 1); + uart->garbage_ignore = 1; + } } #ifdef CONFIG_PM @@ -263,6 +281,7 @@ static inline void omap_uart_disable_clocks(struct omap_uart_state *uart) omap_uart_save_context(uart); uart->clocked = 0; + omap_uart_smart_idle_enable(uart, 1); clk_disable(uart->ick); clk_disable(uart->fck); } @@ -320,7 +339,6 @@ static void omap_uart_block_sleep(struct omap_uart_state *uart) { omap_uart_enable_clocks(uart); - omap_uart_smart_idle_enable(uart, 0); uart->can_sleep = 0; if (uart->timeout) mod_timer(&uart->timer, jiffies + uart->timeout); @@ -338,7 +356,6 @@ static void omap_uart_allow_sleep(struct omap_uart_state *uart) if (!uart->clocked) return; - omap_uart_smart_idle_enable(uart, 1); uart->can_sleep = 1; del_timer(&uart->timer); } @@ -350,13 +367,30 @@ static void omap_uart_idle_timer(unsigned long data) omap_uart_allow_sleep(uart); } +static void omap_uart_garbage_timer(unsigned long data) +{ + struct omap_uart_state *uart = (struct omap_uart_state *)data; + + uart->garbage_ignore = 0; +} + +static void omap_uart_wakeup_work(struct work_struct *work) +{ + struct omap_uart_state *uart = + container_of(work, struct omap_uart_state, wakeup_work); + + omap_uart_block_sleep(uart); +} + void omap_uart_prepare_idle(int num) { struct omap_uart_state *uart; list_for_each_entry(uart, &uart_list, node) { if (num == uart->num && uart->can_sleep) { - omap_uart_disable_clocks(uart); + if (serial_read_reg(uart->p, UART_LSR) & + UART_LSR_TEMT) + omap_uart_disable_clocks(uart); return; } } @@ -375,12 +409,12 @@ void omap_uart_resume_idle(int num) u16 p = omap_ctrl_readw(uart->padconf); if (p & OMAP3_PADCONF_WAKEUPEVENT0) - omap_uart_block_sleep(uart); + schedule_work(&uart->wakeup_work); } /* Check for normal UART wakeup */ if (__raw_readl(uart->wk_st) & uart->wk_mask) - omap_uart_block_sleep(uart); + schedule_work(&uart->wakeup_work); return; } } @@ -428,8 +462,18 @@ int omap_uart_can_sleep(void) static irqreturn_t omap_uart_interrupt(int irq, void *dev_id) { struct omap_uart_state *uart = dev_id; + u8 lsr; - omap_uart_block_sleep(uart); + lsr = serial_read_reg(uart->p, UART_LSR); + /* Check for receive interrupt */ + if (lsr & UART_LSR_DR) { + omap_uart_block_sleep(uart); + if (uart->garbage_ignore) { + del_timer(&uart->garbage_timer); + uart->garbage_ignore = 0; + serial_read_reg(uart->p, UART_RX); + } + } return IRQ_NONE; } @@ -443,6 +487,9 @@ static void omap_uart_idle_init(struct omap_uart_state *uart) uart->timeout = DEFAULT_TIMEOUT; setup_timer(&uart->timer, omap_uart_idle_timer, (unsigned long) uart); + setup_timer(&uart->garbage_timer, omap_uart_garbage_timer, + (unsigned long) uart); + INIT_WORK(&uart->wakeup_work, omap_uart_wakeup_work); if (uart->timeout) mod_timer(&uart->timer, jiffies + uart->timeout); omap_uart_smart_idle_enable(uart, 0); @@ -507,15 +554,13 @@ static void omap_uart_idle_init(struct omap_uart_state *uart) void omap_uart_enable_irqs(int enable) { - int ret; struct omap_uart_state *uart; list_for_each_entry(uart, &uart_list, node) { if (enable) - ret = request_irq(uart->p->irq, omap_uart_interrupt, - IRQF_SHARED, "serial idle", (void *)uart); + enable_irq(uart->p->irq); else - free_irq(uart->p->irq, (void *)uart); + disable_irq(uart->p->irq); } } -- 1.5.4.3 -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html