diff options
Diffstat (limited to 'arch/arm/mach-omap2/serial.c')
-rw-r--r-- | arch/arm/mach-omap2/serial.c | 440 |
1 files changed, 414 insertions, 26 deletions
diff --git a/arch/arm/mach-omap2/serial.c b/arch/arm/mach-omap2/serial.c index 29dc6f52905..b094c15bfe4 100644 --- a/arch/arm/mach-omap2/serial.c +++ b/arch/arm/mach-omap2/serial.c @@ -6,6 +6,8 @@ * Copyright (C) 2005-2008 Nokia Corporation * Author: Paul Mundt <paul.mundt@nokia.com> * + * Major rework for PM support by Kevin Hilman + * * Based off of arch/arm/mach-omap/omap1/serial.c * * Copyright (C) 2009 Texas Instruments @@ -24,9 +26,50 @@ #include <mach/common.h> #include <mach/board.h> +#include <mach/clock.h> +#include <mach/control.h> + +#include "prm.h" +#include "pm.h" +#include "prm-regbits-34xx.h" + +#define UART_OMAP_WER 0x17 /* Wake-up enable register */ + +#define DEFAULT_TIMEOUT (5 * HZ) + +struct omap_uart_state { + int num; + int can_sleep; + struct timer_list timer; + u32 timeout; -static struct clk *uart_ick[OMAP_MAX_NR_PORTS]; -static struct clk *uart_fck[OMAP_MAX_NR_PORTS]; + void __iomem *wk_st; + void __iomem *wk_en; + u32 wk_mask; + u32 padconf; + + struct clk *ick; + struct clk *fck; + int clocked; + + struct plat_serial8250_port *p; + struct list_head node; + +#if defined(CONFIG_ARCH_OMAP3) && defined(CONFIG_PM) + int context_valid; + + /* Registers to be saved/restored for OFF-mode */ + u16 dll; + u16 dlh; + u16 ier; + u16 sysc; + u16 scr; + u16 wer; +#endif +}; + +static struct omap_uart_state omap_uart[OMAP_MAX_NR_PORTS]; +static LIST_HEAD(uart_list); static struct plat_serial8250_port serial_platform_data[] = { { @@ -77,30 +120,358 @@ static inline void serial_write_reg(struct plat_serial8250_port *p, int offset, * properly. Note that the TX watermark initialization may not be needed * once the 8250.c watermark handling code is merged. */ -static inline void __init omap_serial_reset(struct plat_serial8250_port *p) +static inline void __init omap_uart_reset(struct omap_uart_state *uart) { + struct plat_serial8250_port *p = uart->p; + serial_write_reg(p, UART_OMAP_MDR1, 0x07); serial_write_reg(p, UART_OMAP_SCR, 0x08); serial_write_reg(p, UART_OMAP_MDR1, 0x00); serial_write_reg(p, UART_OMAP_SYSC, (0x02 << 3) | (1 << 2) | (1 << 0)); } -void omap_serial_enable_clocks(int enable) +#if defined(CONFIG_PM) && defined(CONFIG_ARCH_OMAP3) + +static int enable_off_mode; /* to be removed by full off-mode patches */ + +static void omap_uart_save_context(struct omap_uart_state *uart) { - int i; - for (i = 0; i < OMAP_MAX_NR_PORTS; i++) { - if (uart_ick[i] && uart_fck[i]) { - if (enable) { - clk_enable(uart_ick[i]); - clk_enable(uart_fck[i]); - } else { - clk_disable(uart_ick[i]); - clk_disable(uart_fck[i]); + u16 lcr = 0; + struct plat_serial8250_port *p = uart->p; + + if (!enable_off_mode) + return; + + lcr = serial_read_reg(p, UART_LCR); + serial_write_reg(p, UART_LCR, 0xBF); + uart->dll = serial_read_reg(p, UART_DLL); + uart->dlh = serial_read_reg(p, UART_DLM); + serial_write_reg(p, UART_LCR, lcr); + uart->ier = serial_read_reg(p, UART_IER); + uart->sysc = serial_read_reg(p, UART_OMAP_SYSC); + uart->scr = serial_read_reg(p, UART_OMAP_SCR); + uart->wer = serial_read_reg(p, UART_OMAP_WER); + + uart->context_valid = 1; +} + +static void omap_uart_restore_context(struct omap_uart_state *uart) +{ + u16 efr = 0; + struct plat_serial8250_port *p = uart->p; + + if (!enable_off_mode) + return; + + if (!uart->context_valid) + return; + + uart->context_valid = 0; + + serial_write_reg(p, UART_OMAP_MDR1, 0x7); + serial_write_reg(p, UART_LCR, 0xBF); /* Config B mode */ + efr = serial_read_reg(p, UART_EFR); + serial_write_reg(p, UART_EFR, UART_EFR_ECB); + serial_write_reg(p, UART_LCR, 0x0); /* Operational mode */ + serial_write_reg(p, UART_IER, 0x0); + serial_write_reg(p, UART_LCR, 0xBF); /* Config B mode */ + serial_write_reg(p, UART_DLL, uart->dll); + serial_write_reg(p, UART_DLM, uart->dlh); + serial_write_reg(p, UART_LCR, 0x0); /* Operational mode */ + serial_write_reg(p, UART_IER, uart->ier); + serial_write_reg(p, UART_FCR, 0xA1); + serial_write_reg(p, UART_LCR, 0xBF); /* Config B mode */ + serial_write_reg(p, UART_EFR, efr); + serial_write_reg(p, UART_LCR, UART_LCR_WLEN8); + serial_write_reg(p, UART_OMAP_SCR, uart->scr); + serial_write_reg(p, UART_OMAP_WER, uart->wer); + serial_write_reg(p, UART_OMAP_SYSC, uart->sysc); + serial_write_reg(p, UART_OMAP_MDR1, 0x00); /* UART 16x mode */ +} +#else +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 */ + +static inline void omap_uart_enable_clocks(struct omap_uart_state *uart) +{ + if (uart->clocked) + return; + + clk_enable(uart->ick); + clk_enable(uart->fck); + uart->clocked = 1; + omap_uart_restore_context(uart); +} + +#ifdef CONFIG_PM + +static inline void omap_uart_disable_clocks(struct omap_uart_state *uart) +{ + if (!uart->clocked) + return; + + omap_uart_save_context(uart); + uart->clocked = 0; + clk_disable(uart->ick); + clk_disable(uart->fck); +} + +static void omap_uart_smart_idle_enable(struct omap_uart_state *uart, + int enable) +{ + struct plat_serial8250_port *p = uart->p; + u16 sysc; + + sysc = serial_read_reg(p, UART_OMAP_SYSC) & 0x7; + if (enable) + sysc |= 0x2 << 3; + else + sysc |= 0x1 << 3; + + serial_write_reg(p, UART_OMAP_SYSC, sysc); +} + +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); + else + del_timer(&uart->timer); +} + +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); +} + +static void omap_uart_idle_timer(unsigned long data) +{ + struct omap_uart_state *uart = (struct omap_uart_state *)data; + + omap_uart_allow_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); + return; + } + } +} + +void omap_uart_resume_idle(int num) +{ + struct omap_uart_state *uart; + + list_for_each_entry(uart, &uart_list, node) { + if (num == uart->num) { + omap_uart_enable_clocks(uart); + + /* Check for IO pad wakeup */ + if (cpu_is_omap34xx() && uart->padconf) { + u16 p = omap_ctrl_readw(uart->padconf); + + if (p & OMAP3_PADCONF_WAKEUPEVENT0) + omap_uart_block_sleep(uart); } + + /* Check for normal UART wakeup */ + if (__raw_readl(uart->wk_st) & uart->wk_mask) + omap_uart_block_sleep(uart); + + return; } } } +void omap_uart_prepare_suspend(void) +{ + struct omap_uart_state *uart; + + list_for_each_entry(uart, &uart_list, node) { + omap_uart_allow_sleep(uart); + } +} + +int omap_uart_can_sleep(void) +{ + struct omap_uart_state *uart; + int can_sleep = 1; + + list_for_each_entry(uart, &uart_list, node) { + if (!uart->clocked) + continue; + + if (!uart->can_sleep) { + can_sleep = 0; + continue; + } + + /* This UART can now safely sleep. */ + omap_uart_allow_sleep(uart); + } + + return can_sleep; +} + +/** + * omap_uart_interrupt() + * + * This handler is used only to detect that *any* UART interrupt has + * occurred. It does _nothing_ to handle the interrupt. Rather, + * any UART interrupt will trigger the inactivity timer so the + * UART will not idle or sleep for its timeout period. + * + **/ +static irqreturn_t omap_uart_interrupt(int irq, void *dev_id) +{ + struct omap_uart_state *uart = dev_id; + + omap_uart_block_sleep(uart); + + return IRQ_NONE; +} + +static u32 sleep_timeout = DEFAULT_TIMEOUT; + +static void omap_uart_idle_init(struct omap_uart_state *uart) +{ + u32 v; + struct plat_serial8250_port *p = uart->p; + int ret; + + uart->can_sleep = 0; + uart->timeout = sleep_timeout; + setup_timer(&uart->timer, omap_uart_idle_timer, + (unsigned long) uart); + mod_timer(&uart->timer, jiffies + uart->timeout); + omap_uart_smart_idle_enable(uart, 0); + + if (cpu_is_omap34xx()) { + u32 mod = (uart->num == 2) ? OMAP3430_PER_MOD : CORE_MOD; + u32 wk_mask = 0; + u32 padconf = 0; + + uart->wk_en = OMAP34XX_PRM_REGADDR(mod, PM_WKEN1); + uart->wk_st = OMAP34XX_PRM_REGADDR(mod, PM_WKST1); + switch (uart->num) { + case 0: + wk_mask = OMAP3430_ST_UART1_MASK; + padconf = 0x182; + break; + case 1: + wk_mask = OMAP3430_ST_UART2_MASK; + padconf = 0x17a; + break; + case 2: + wk_mask = OMAP3430_ST_UART3_MASK; + padconf = 0x19e; + break; + } + uart->wk_mask = wk_mask; + uart->padconf = padconf; + } else if (cpu_is_omap24xx()) { + u32 wk_mask = 0; + + if (cpu_is_omap2430()) { + uart->wk_en = OMAP2430_PRM_REGADDR(CORE_MOD, PM_WKEN1); + uart->wk_st = OMAP2430_PRM_REGADDR(CORE_MOD, PM_WKST1); + } else if (cpu_is_omap2420()) { + uart->wk_en = OMAP2420_PRM_REGADDR(CORE_MOD, PM_WKEN1); + uart->wk_st = OMAP2420_PRM_REGADDR(CORE_MOD, PM_WKST1); + } + switch (uart->num) { + case 0: + wk_mask = OMAP24XX_ST_UART1_MASK; + break; + case 1: + wk_mask = OMAP24XX_ST_UART2_MASK; + break; + case 2: + wk_mask = OMAP24XX_ST_UART3_MASK; + break; + } + uart->wk_mask = wk_mask; + } else { + uart->wk_en = 0; + uart->wk_st = 0; + uart->wk_mask = 0; + uart->padconf = 0; + } + + /* Set wake-enable bit */ + if (uart->wk_en && uart->wk_mask) { + v = __raw_readl(uart->wk_en); + v |= uart->wk_mask; + __raw_writel(v, uart->wk_en); + } + + /* Ensure IOPAD wake-enables are set */ + if (cpu_is_omap34xx() && uart->padconf) { + u16 v; + + v = omap_ctrl_readw(uart->padconf); + v |= OMAP3_PADCONF_WAKEUPENABLE0; + omap_ctrl_writew(v, uart->padconf); + } + + p->flags |= UPF_SHARE_IRQ; + ret = request_irq(p->irq, omap_uart_interrupt, IRQF_SHARED, + "serial idle", (void *)uart); + WARN_ON(ret); +} + +static ssize_t sleep_timeout_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", sleep_timeout / HZ); +} + +static ssize_t sleep_timeout_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + struct omap_uart_state *uart; + unsigned int value; + + if (sscanf(buf, "%u", &value) != 1) { + printk(KERN_ERR "sleep_timeout_store: Invalid value\n"); + return -EINVAL; + } + sleep_timeout = value * HZ; + list_for_each_entry(uart, &uart_list, node) { + uart->timeout = sleep_timeout; + if (uart->timeout) + mod_timer(&uart->timer, jiffies + uart->timeout); + else + /* A zero value means disable timeout feature */ + omap_uart_block_sleep(uart); + } + return n; +} + +static struct kobj_attribute sleep_timeout_attr = + __ATTR(sleep_timeout, 0644, sleep_timeout_show, sleep_timeout_store); + +#else +static inline void omap_uart_idle_init(struct omap_uart_state *uart) {} +#endif /* CONFIG_PM */ + static struct platform_device serial_device = { .name = "serial8250", .id = PLAT8250_DEV_PLATFORM, @@ -111,7 +482,7 @@ static struct platform_device serial_device = { void __init omap_serial_init(void) { - int i; + int i, err; const struct omap_uart_config *info; char name[16]; @@ -132,6 +503,7 @@ void __init omap_serial_init(void) for (i = 0; i < OMAP_MAX_NR_PORTS; i++) { struct plat_serial8250_port *p = serial_platform_data + i; + struct omap_uart_state *uart = &omap_uart[i]; if (!(info->enabled_uarts & (1 << i))) { p->membase = NULL; @@ -140,23 +512,39 @@ void __init omap_serial_init(void) } sprintf(name, "uart%d_ick", i+1); - uart_ick[i] = clk_get(NULL, name); - if (IS_ERR(uart_ick[i])) { + uart->ick = clk_get(NULL, name); + if (IS_ERR(uart->ick)) { printk(KERN_ERR "Could not get uart%d_ick\n", i+1); - uart_ick[i] = NULL; - } else - clk_enable(uart_ick[i]); + uart->ick = NULL; + } sprintf(name, "uart%d_fck", i+1); - uart_fck[i] = clk_get(NULL, name); - if (IS_ERR(uart_fck[i])) { + uart->fck = clk_get(NULL, name); + if (IS_ERR(uart->fck)) { printk(KERN_ERR "Could not get uart%d_fck\n", i+1); - uart_fck[i] = NULL; - } else - clk_enable(uart_fck[i]); + uart->fck = NULL; + } + + if (!uart->ick || !uart->fck) + continue; - omap_serial_reset(p); + uart->num = i; + p->private_data = uart; + uart->p = p; + list_add(&uart->node, &uart_list); + + omap_uart_enable_clocks(uart); + omap_uart_reset(uart); + omap_uart_idle_init(uart); } - platform_device_register(&serial_device); + err = platform_device_register(&serial_device); + +#ifdef CONFIG_PM + if (!err) + err = sysfs_create_file(&serial_device.dev.kobj, + &sleep_timeout_attr.attr); +#endif + } + |