| /* |
| * arch/arm/mach-omap2/serial.c |
| * |
| * OMAP2 serial support. |
| * |
| * Copyright (C) 2005-2008 Nokia Corporation |
| * Author: Paul Mundt <[email protected]> |
| * |
| * Major rework for PM support by Kevin Hilman |
| * |
| * Based off of arch/arm/mach-omap/omap1/serial.c |
| * |
| * Copyright (C) 2009 Texas Instruments |
| * Added OMAP4 support - Santosh Shilimkar <[email protected] |
| * |
| * This file is subject to the terms and conditions of the GNU General Public |
| * License. See the file "COPYING" in the main directory of this archive |
| * for more details. |
| */ |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/clk.h> |
| #include <linux/io.h> |
| #include <linux/delay.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/pm_runtime.h> |
| |
| #include <plat/omap-serial.h> |
| #include <plat/common.h> |
| #include <plat/board.h> |
| #include <plat/clock.h> |
| #include <plat/dma.h> |
| #include <plat/omap_device.h> |
| #include <plat/omap-pm.h> |
| |
| #include "prm2xxx_3xxx.h" |
| #include "pm.h" |
| #include "cm2xxx_3xxx.h" |
| #include "prm-regbits-34xx.h" |
| #include "control.h" |
| #include "mux.h" |
| |
| #define MAX_UART_HWMOD_NAME_LEN 16 |
| |
| static int omap_uart_con_id __initdata = -1; |
| |
| static struct omap_uart_port_info omap_serial_default_info[] = { |
| { |
| .use_dma = 0, |
| .dma_rx_buf_size = DEFAULT_RXDMA_BUFSIZE, |
| .dma_rx_poll_rate = DEFAULT_RXDMA_POLLRATE, |
| .dma_rx_timeout = DEFAULT_RXDMA_TIMEOUT, |
| .auto_sus_timeout = DEFAULT_AUTOSUSPEND_DELAY, |
| }, |
| }; |
| |
| static int uart_idle_hwmod(struct omap_device *od) |
| { |
| omap_hwmod_idle(od->hwmods[0]); |
| |
| return 0; |
| } |
| |
| static int uart_enable_hwmod(struct omap_device *od) |
| { |
| omap_hwmod_enable(od->hwmods[0]); |
| |
| return 0; |
| } |
| |
| static struct omap_device_pm_latency omap_uart_latency[] = { |
| { |
| .deactivate_func = uart_idle_hwmod, |
| .activate_func = uart_enable_hwmod, |
| .flags = OMAP_DEVICE_LATENCY_AUTO_ADJUST, |
| }, |
| }; |
| |
| #ifdef CONFIG_OMAP_MUX |
| static struct omap_device_pad default_uart1_pads[] __initdata = { |
| { |
| .name = "uart1_cts.uart1_cts", |
| .enable = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "uart1_rts.uart1_rts", |
| .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "uart1_tx.uart1_tx", |
| .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "uart1_rx.uart1_rx", |
| .flags = OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP, |
| .enable = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0, |
| .idle = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0, |
| }, |
| }; |
| |
| static struct omap_device_pad default_uart2_pads[] __initdata = { |
| { |
| .name = "uart2_cts.uart2_cts", |
| .enable = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "uart2_rts.uart2_rts", |
| .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "uart2_tx.uart2_tx", |
| .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "uart2_rx.uart2_rx", |
| .flags = OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP, |
| .enable = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0, |
| .idle = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0, |
| }, |
| }; |
| |
| static struct omap_device_pad default_uart3_pads[] __initdata = { |
| { |
| .name = "uart3_cts_rctx.uart3_cts_rctx", |
| .enable = OMAP_PIN_INPUT_PULLUP | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "uart3_rts_sd.uart3_rts_sd", |
| .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "uart3_tx_irtx.uart3_tx_irtx", |
| .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "uart3_rx_irrx.uart3_rx_irrx", |
| .flags = OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP, |
| .enable = OMAP_PIN_INPUT | OMAP_MUX_MODE0, |
| .idle = OMAP_PIN_INPUT | OMAP_MUX_MODE0, |
| }, |
| }; |
| |
| static struct omap_device_pad default_omap36xx_uart4_pads[] __initdata = { |
| { |
| .name = "gpmc_wait2.uart4_tx", |
| .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "gpmc_wait3.uart4_rx", |
| .flags = OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP, |
| .enable = OMAP_PIN_INPUT | OMAP_MUX_MODE2, |
| .idle = OMAP_PIN_INPUT | OMAP_MUX_MODE2, |
| }, |
| }; |
| |
| static struct omap_device_pad default_omap4_uart4_pads[] __initdata = { |
| { |
| .name = "uart4_tx.uart4_tx", |
| .enable = OMAP_PIN_OUTPUT | OMAP_MUX_MODE0, |
| }, |
| { |
| .name = "uart4_rx.uart4_rx", |
| .flags = OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP, |
| .enable = OMAP_PIN_INPUT | OMAP_MUX_MODE0, |
| .idle = OMAP_PIN_INPUT | OMAP_MUX_MODE0, |
| }, |
| }; |
| #else |
| static struct omap_device_pad default_uart1_pads[] __initdata = {}; |
| static struct omap_device_pad default_uart2_pads[] __initdata = {}; |
| static struct omap_device_pad default_uart3_pads[] __initdata = {}; |
| static struct omap_device_pad default_omap36xx_uart4_pads[] __initdata = {}; |
| static struct omap_device_pad default_omap4_uart4_pads[] __initdata = {}; |
| #endif |
| |
| static __init void omap_serial_fill_default_pads(struct omap_board_data *bdata) |
| { |
| BUG_ON(!cpu_is_omap44xx() && !cpu_is_omap34xx()); |
| |
| switch (bdata->id) { |
| case 0: |
| bdata->pads = default_uart1_pads; |
| bdata->pads_cnt = ARRAY_SIZE(default_uart1_pads); |
| break; |
| case 1: |
| bdata->pads = default_uart2_pads; |
| bdata->pads_cnt = ARRAY_SIZE(default_uart2_pads); |
| break; |
| case 2: |
| bdata->pads = default_uart3_pads; |
| bdata->pads_cnt = ARRAY_SIZE(default_uart3_pads); |
| break; |
| case 3: |
| if (cpu_is_omap44xx()) { |
| bdata->pads = default_omap4_uart4_pads; |
| bdata->pads_cnt = |
| ARRAY_SIZE(default_omap4_uart4_pads); |
| } else { |
| bdata->pads = default_omap36xx_uart4_pads; |
| bdata->pads_cnt = |
| ARRAY_SIZE(default_omap36xx_uart4_pads); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* TBD: Will be removed once we have irq-chaing mechanism */ |
| static bool omap_uart_chk_wakeup(struct platform_device *pdev) |
| { |
| struct omap_uart_port_info *up = pdev->dev.platform_data; |
| struct omap_device *od; |
| u32 wkst = 0; |
| bool ret = false; |
| |
| od = to_omap_device(pdev); |
| if (omap_hwmod_pad_get_wakeup_status(od->hwmods[0])) |
| ret = true; |
| |
| if (up->wk_st && up->wk_en && up->wk_mask) { |
| /* Check for normal UART wakeup (and clear it) */ |
| wkst = __raw_readl(up->wk_st) & up->wk_mask; |
| if (wkst) { |
| __raw_writel(wkst, up->wk_st); |
| ret = true; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static void omap_uart_wakeup_enable(struct platform_device *pdev, bool enable) |
| { |
| struct omap_device *od; |
| |
| od = to_omap_device(pdev); |
| if (enable) |
| omap_hwmod_enable_wakeup(od->hwmods[0]); |
| else |
| omap_hwmod_disable_wakeup(od->hwmods[0]); |
| } |
| |
| static void omap_uart_idle_init(struct omap_uart_port_info *uart, |
| unsigned short num) |
| { |
| if (cpu_is_omap44xx()) { |
| uart->wer |= OMAP4_UART_WER_MOD_WKUP; |
| } else if (cpu_is_omap34xx()) { |
| u32 mod = num > 1 ? OMAP3430_PER_MOD : CORE_MOD; |
| u32 wk_mask = 0; |
| |
| uart->wer |= OMAP2_UART_WER_MOD_WKUP; |
| uart->wk_en = OMAP34XX_PRM_REGADDR(mod, PM_WKEN1); |
| uart->wk_st = OMAP34XX_PRM_REGADDR(mod, PM_WKST1); |
| switch (num) { |
| case 0: |
| wk_mask = OMAP3430_ST_UART1_MASK; |
| break; |
| case 1: |
| wk_mask = OMAP3430_ST_UART2_MASK; |
| break; |
| case 2: |
| wk_mask = OMAP3430_ST_UART3_MASK; |
| break; |
| case 3: |
| wk_mask = OMAP3630_ST_UART4_MASK; |
| break; |
| } |
| uart->wk_mask = wk_mask; |
| } else if (cpu_is_omap24xx()) { |
| u32 wk_mask = 0; |
| u32 wk_en = PM_WKEN1, wk_st = PM_WKST1; |
| |
| switch (num) { |
| case 0: |
| wk_mask = OMAP24XX_ST_UART1_MASK; |
| break; |
| case 1: |
| wk_mask = OMAP24XX_ST_UART2_MASK; |
| break; |
| case 2: |
| wk_en = OMAP24XX_PM_WKEN2; |
| wk_st = OMAP24XX_PM_WKST2; |
| wk_mask = OMAP24XX_ST_UART3_MASK; |
| break; |
| } |
| uart->wk_mask = wk_mask; |
| if (cpu_is_omap2430()) { |
| uart->wk_en = OMAP2430_PRM_REGADDR(CORE_MOD, wk_en); |
| uart->wk_st = OMAP2430_PRM_REGADDR(CORE_MOD, wk_st); |
| } else if (cpu_is_omap2420()) { |
| uart->wk_en = OMAP2420_PRM_REGADDR(CORE_MOD, wk_en); |
| uart->wk_st = OMAP2420_PRM_REGADDR(CORE_MOD, wk_st); |
| } |
| } else { |
| uart->wk_en = NULL; |
| uart->wk_st = NULL; |
| uart->wk_mask = 0; |
| } |
| } |
| |
| char *cmdline_find_option(char *str) |
| { |
| extern char *saved_command_line; |
| |
| return strstr(saved_command_line, str); |
| } |
| |
| struct omap_hwmod *omap_uart_hwmod_lookup(int num) |
| { |
| struct omap_hwmod *oh; |
| char oh_name[MAX_UART_HWMOD_NAME_LEN]; |
| |
| snprintf(oh_name, MAX_UART_HWMOD_NAME_LEN, "uart%d", num + 1); |
| oh = omap_hwmod_lookup(oh_name); |
| WARN(IS_ERR(oh), "Could not lookup hmwod info for %s\n", |
| oh_name); |
| return oh; |
| } |
| |
| void omap_rts_mux_write(u16 val, int num) |
| { |
| struct omap_hwmod *oh; |
| int i; |
| |
| oh = omap_uart_hwmod_lookup(num); |
| if (!oh) |
| return; |
| |
| for (i = 0; i < oh->mux->nr_pads ; i++) { |
| if (strstr(oh->mux->pads[i].name, "rts")) { |
| omap_mux_write(oh->mux->pads[i].partition, |
| val, |
| oh->mux->pads[i].mux[0].reg_offset); |
| break; |
| } |
| } |
| } |
| |
| static int __init omap_serial_early_init(void) |
| { |
| int i = 0; |
| char omap_tty_name[MAX_UART_HWMOD_NAME_LEN]; |
| struct omap_hwmod *oh; |
| |
| for (i = 0; i < OMAP_MAX_HSUART_PORTS; i++) { |
| snprintf(omap_tty_name, MAX_UART_HWMOD_NAME_LEN, |
| "%s%d", OMAP_SERIAL_NAME, i); |
| if (cmdline_find_option(omap_tty_name)) { |
| omap_uart_con_id = i; |
| oh = omap_uart_hwmod_lookup(i); |
| oh->flags |= HWMOD_INIT_NO_IDLE | HWMOD_INIT_NO_RESET; |
| return 0; |
| } |
| } |
| return 0; |
| } |
| core_initcall(omap_serial_early_init); |
| |
| void __init omap_serial_init_port_pads(int id, struct omap_device_pad *pads, |
| int size, struct omap_uart_port_info *info) |
| { |
| struct omap_board_data bdata; |
| |
| bdata.id = id; |
| bdata.flags = 0; |
| bdata.pads = pads; |
| bdata.pads_cnt = size; |
| |
| if (!bdata.pads) |
| omap_serial_fill_default_pads(&bdata); |
| |
| omap_serial_init_port(&bdata, info); |
| } |
| |
| /** |
| * omap_serial_init_port() - initialize single serial port |
| * @bdata: port specific board data pointer |
| * @info: platform specific data pointer |
| * |
| * This function initialies serial driver for given port only. |
| * Platforms can call this function instead of omap_serial_init() |
| * if they don't plan to use all available UARTs as serial ports. |
| * |
| * Don't mix calls to omap_serial_init_port() and omap_serial_init(), |
| * use only one of the two. |
| */ |
| void __init omap_serial_init_port(struct omap_board_data *bdata, |
| struct omap_uart_port_info *info) |
| { |
| struct omap_hwmod *oh; |
| struct omap_device *od; |
| struct omap_uart_port_info *pdata; |
| char *name = DRIVER_NAME; |
| |
| if (WARN_ON(!bdata)) |
| return; |
| if (WARN_ON(bdata->id < 0)) |
| return; |
| if (WARN_ON(bdata->id >= OMAP_MAX_HSUART_PORTS)) |
| return; |
| |
| oh = omap_uart_hwmod_lookup(bdata->id); |
| if (!oh) |
| return; |
| |
| if (info == NULL) |
| info = omap_serial_default_info; |
| |
| pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) { |
| pr_err("Memory allocation for UART pdata failed\n"); |
| return; |
| } |
| |
| if (cpu_is_omap34xx() || cpu_is_omap44xx()) |
| pdata->errata |= UART_ERRATA_i202_MDR1_ACCESS; |
| |
| omap_uart_idle_init(pdata, bdata->id); |
| |
| pdata->uartclk = OMAP24XX_BASE_BAUD * 16; |
| pdata->flags = UPF_BOOT_AUTOCONF; |
| pdata->enable_wakeup = omap_uart_wakeup_enable; |
| pdata->use_dma = info->use_dma; |
| pdata->chk_wakeup = omap_uart_chk_wakeup; |
| pdata->dma_rx_buf_size = info->dma_rx_buf_size; |
| pdata->dma_rx_poll_rate = info->dma_rx_poll_rate; |
| pdata->dma_rx_timeout = info->dma_rx_timeout; |
| pdata->auto_sus_timeout = info->auto_sus_timeout; |
| pdata->wake_peer = info->wake_peer; |
| pdata->rts_mux_driver_control = info->rts_mux_driver_control; |
| if (bdata->id == omap_uart_con_id) { |
| pdata->console_uart = true; |
| #ifdef CONFIG_DEBUG_LL |
| pdata->auto_sus_timeout = -1; |
| #endif |
| } |
| |
| if (pdata->use_dma && |
| cpu_is_omap44xx() && omap_rev() > OMAP4430_REV_ES1_0) |
| pdata->errata |= OMAP4_UART_ERRATA_i659_TX_THR; |
| |
| od = omap_device_build(name, bdata->id, oh, pdata, |
| sizeof(*pdata), omap_uart_latency, |
| ARRAY_SIZE(omap_uart_latency), false); |
| WARN(IS_ERR(od), "Could not build omap_device for %s: %s.\n", |
| name, oh->name); |
| |
| oh->mux = omap_hwmod_mux_init(bdata->pads, bdata->pads_cnt); |
| |
| if (((cpu_is_omap34xx() || cpu_is_omap44xx()) && bdata->pads) || |
| (pdata->wk_en && pdata->wk_mask)) { |
| device_init_wakeup(&od->pdev.dev, true); |
| } |
| |
| kfree(pdata); |
| } |
| |
| /** |
| * omap_serial_board_init() - initialize all supported serial ports |
| * @platform_data: platform specific data pointer |
| * |
| * Initializes all available UARTs as serial ports. Platforms |
| * can call this function when they want to have default behaviour |
| * for serial ports (e.g initialize them all as serial ports). |
| */ |
| void __init omap_serial_board_init(struct omap_uart_port_info *platform_data) |
| { |
| struct omap_board_data bdata; |
| u8 i; |
| |
| for (i = 0; i < OMAP_MAX_HSUART_PORTS; i++) { |
| bdata.id = i; |
| bdata.flags = 0; |
| bdata.pads = NULL; |
| bdata.pads_cnt = 0; |
| |
| if (cpu_is_omap44xx() || cpu_is_omap34xx()) |
| omap_serial_fill_default_pads(&bdata); |
| |
| if (platform_data == NULL) |
| omap_serial_init_port(&bdata, NULL); |
| else |
| omap_serial_init_port(&bdata, &platform_data[i]); |
| } |
| } |
| |
| /** |
| * omap_serial_init() - initialize all supported serial ports |
| * |
| * Initializes all available UARTs. |
| * Platforms can call this function when they want to have default behaviour |
| * for serial ports (e.g initialize them all as serial ports). |
| */ |
| void __init omap_serial_init(void) |
| { |
| omap_serial_board_init(NULL); |
| } |