diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/tty/serial/Kconfig | 8 | ||||
-rw-r--r-- | drivers/tty/serial/amba-pl011.c | 366 |
2 files changed, 344 insertions, 30 deletions
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 070b442c1f8..b2831ed01bb 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -63,6 +63,14 @@ config SERIAL_AMBA_PL011_CONSOLE your boot loader (lilo or loadlin) about how to pass options to the kernel at boot time.) +config SERIAL_AMBA_PL011_CLOCK_CONTROL + bool "Support for clock control on AMBA serial port" + depends on SERIAL_AMBA_PL011 + select CONSOLE_POLL + ---help--- + Say Y here if you wish to use amba set_termios function to control + the pl011 clock. Any positive baudrate passed enables clock, + config SERIAL_SB1250_DUART tristate "BCM1xxx on-chip DUART serial support" depends on SIBYTE_SB1xxx_SOC=y diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 9cb09ae7a56..3618278df03 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -47,6 +47,7 @@ #include <linux/amba/serial.h> #include <linux/clk.h> #include <linux/slab.h> +#include <linux/regulator/consumer.h> #include <linux/dmaengine.h> #include <linux/dma-mapping.h> #include <linux/scatterlist.h> @@ -67,6 +68,37 @@ #define UART_DR_ERROR (UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE) #define UART_DUMMY_DR_RX (1 << 16) +/* + * The console UART is handled differently for power management (it doesn't + * take the regulator, in order to allow the system to go to sleep even if the + * console is open). This should be removed once cable detect is in place. + */ +#ifdef CONFIG_SERIAL_CORE_CONSOLE +#define uart_console(port) ((port)->cons \ + && (port)->cons->index == (port)->line) +#else +#define uart_console(port) (0) +#endif + +/* Available amba pl011 port clock states */ +enum pl011_clk_states { + PL011_CLK_OFF = 0, /* clock disabled */ + PL011_CLK_REQUEST_OFF, /* disable after TX flushed */ + PL011_CLK_ON, /* clock enabled */ + PL011_PORT_OFF, /* port disabled */ +}; + +/* + * Backup registers to be used during regulator startup/shutdown + */ +static const u32 backup_regs[] = { + UART011_IBRD, + UART011_FBRD, + ST_UART011_LCRH_RX, + ST_UART011_LCRH_TX, + UART011_CR, + UART011_IMSC, +}; #define UART_WA_SAVE_NR 14 @@ -158,10 +190,18 @@ struct uart_amba_port { unsigned int im; /* interrupt mask */ unsigned int old_status; unsigned int fifosize; /* vendor-specific */ + unsigned int ifls; /* vendor-specific */ unsigned int lcrh_tx; /* vendor-specific */ unsigned int lcrh_rx; /* vendor-specific */ unsigned int old_cr; /* state during shutdown */ bool autorts; +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL + enum pl011_clk_states clk_state; /* actual clock state */ + struct delayed_work clk_off_work; /* work used for clock off */ + unsigned int clk_off_delay; /* clock off delay */ +#endif + struct regulator *regulator; + u32 backup[ARRAY_SIZE(backup_regs)]; char type[12]; bool interrupt_may_hang; /* vendor-specific */ #ifdef CONFIG_DMA_ENGINE @@ -1117,6 +1157,246 @@ static void pl011_lockup_wa(unsigned long data) tty->hw_stopped = 0; } +static void __pl011_startup(struct uart_amba_port *uap) +{ + unsigned int cr; + + writew(uap->ifls, uap->port.membase + UART011_IFLS); + + /* + * Provoke TX FIFO interrupt into asserting. + */ + cr = UART01x_CR_UARTEN | UART011_CR_TXE | UART011_CR_LBE; + writew(cr, uap->port.membase + UART011_CR); + writew(0, uap->port.membase + UART011_FBRD); + writew(1, uap->port.membase + UART011_IBRD); + writew(0, uap->port.membase + uap->lcrh_rx); + if (uap->lcrh_tx != uap->lcrh_rx) { + int i; + /* + * Wait 10 PCLKs before writing LCRH_TX register, + * to get this delay write read only register 10 times + */ + for (i = 0; i < 10; ++i) + writew(0xff, uap->port.membase + UART011_MIS); + writew(0, uap->port.membase + uap->lcrh_tx); + } + writew(0, uap->port.membase + UART01x_DR); + while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY) + barrier(); +} + +/* Backup the registers during regulator startup/shutdown */ +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL +static int pl011_backup(struct uart_amba_port *uap, bool suspend) +{ + int i, cnt; + + if (!suspend) { + __pl011_startup(uap); + writew(0, uap->port.membase + UART011_CR); + } + + for (i = 0; i < ARRAY_SIZE(backup_regs); i++) { + if (suspend) + uap->backup[i] = readw(uap->port.membase + + backup_regs[i]); + else { + if (backup_regs[i] == ST_UART011_LCRH_TX) { + /* + * Wait 10 PCLKs before writing LCRH_TX + * register, to get this delay write read + * only register 10 times + */ + for (cnt = 0; cnt < 10; ++cnt) + writew(0xff, uap->port.membase + + UART011_MIS); + } + + writew(uap->backup[i], + uap->port.membase + backup_regs[i]); + } + } + return 0; +} +#endif + +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL +/* Turn clock off if TX buffer is empty, otherwise reschedule */ +static void pl011_clock_off(struct work_struct *work) +{ + struct uart_amba_port *uap = container_of(work, struct uart_amba_port, + clk_off_work.work); + struct uart_port *port = &uap->port; + struct circ_buf *xmit = &port->state->xmit; + unsigned long flags; + bool disable_regulator = false; + unsigned int busy, interrupt_status; + + spin_lock_irqsave(&port->lock, flags); + + interrupt_status = readw(uap->port.membase + UART011_MIS); + busy = readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY; + + if (uap->clk_state == PL011_CLK_REQUEST_OFF) { + if (uart_circ_empty(xmit) && !interrupt_status && !busy) { + if (!uart_console(&uap->port) && uap->regulator) { + pl011_backup(uap, true); + disable_regulator = true; + } + uap->clk_state = PL011_CLK_OFF; + clk_disable(uap->clk); + } else + schedule_delayed_work(&uap->clk_off_work, + uap->clk_off_delay); + } + + spin_unlock_irqrestore(&port->lock, flags); + + if (disable_regulator) + regulator_disable(uap->regulator); +} + +/* Request to turn off uart clock once pending TX is flushed */ +static void pl011_clock_request_off(struct uart_port *port) +{ + unsigned long flags; + struct uart_amba_port *uap = (struct uart_amba_port *)(port); + + spin_lock_irqsave(&port->lock, flags); + + if (uap->clk_state == PL011_CLK_ON) { + uap->clk_state = PL011_CLK_REQUEST_OFF; + /* Turn off later */ + schedule_delayed_work(&uap->clk_off_work, + uap->clk_off_delay); + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +/* Request to immediately turn on uart clock */ +static void pl011_clock_on(struct uart_port *port) +{ + unsigned long flags; + struct uart_amba_port *uap = (struct uart_amba_port *)(port); + + spin_lock_irqsave(&port->lock, flags); + + switch (uap->clk_state) { + case PL011_CLK_OFF: + clk_enable(uap->clk); + if (!uart_console(&uap->port) && uap->regulator) { + spin_unlock_irqrestore(&port->lock, flags); + regulator_enable(uap->regulator); + spin_lock_irqsave(&port->lock, flags); + pl011_backup(uap, false); + } + /* fallthrough */ + case PL011_CLK_REQUEST_OFF: + cancel_delayed_work(&uap->clk_off_work); + uap->clk_state = PL011_CLK_ON; + break; + default: + break; + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void pl011_clock_check(struct uart_amba_port *uap) +{ + /* Reshedule work during off request */ + if (uap->clk_state == PL011_CLK_REQUEST_OFF) + /* New TX - restart work */ + if (cancel_delayed_work(&uap->clk_off_work)) + schedule_delayed_work(&uap->clk_off_work, + uap->clk_off_delay); +} + +static int pl011_power_startup(struct uart_amba_port *uap) +{ + int retval = 0; + + if (uap->clk_state == PL011_PORT_OFF) { + if (!uart_console(&uap->port) && uap->regulator) + regulator_enable(uap->regulator); + retval = clk_enable(uap->clk); + if (!retval) + uap->clk_state = PL011_CLK_ON; + else + uap->clk_state = PL011_PORT_OFF; + } + + return retval; +} + +static void pl011_power_shutdown(struct uart_amba_port *uap) +{ + bool disable_regulator = false; + + cancel_delayed_work_sync(&uap->clk_off_work); + + spin_lock_irq(&uap->port.lock); + if (uap->clk_state == PL011_CLK_ON || + uap->clk_state == PL011_CLK_REQUEST_OFF) { + clk_disable(uap->clk); + if (!uart_console(&uap->port) && uap->regulator) + disable_regulator = true; + } + uap->clk_state = PL011_PORT_OFF; + spin_unlock_irq(&uap->port.lock); + + if (disable_regulator) + regulator_disable(uap->regulator); +} + +static void +pl011_clock_control(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ + speed_t new_baud = tty_termios_baud_rate(termios); + + if (new_baud == 0) + pl011_clock_request_off(port); + else + pl011_clock_on(port); +} + +static void pl011_clock_control_init(struct uart_amba_port *uap) +{ + uap->clk_state = PL011_PORT_OFF; + INIT_DELAYED_WORK(&uap->clk_off_work, pl011_clock_off); + uap->clk_off_delay = HZ / 10; /* 100 ms */ +} + +#else +/* Blank functions for clock control */ +static inline void pl011_clock_check(struct uart_amba_port *uap) +{ +} + +static inline int pl011_power_startup(struct uart_amba_port *uap) +{ + return clk_enable(uap->clk); +} + +static inline void pl011_power_shutdown(struct uart_amba_port *uap) +{ + clk_disable(uap->clk); +} + +static inline void +pl011_clock_control(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ +} + +static inline void pl011_clock_control_init(struct uart_amba_port *uap) +{ +} +#endif + static void pl011_stop_tx(struct uart_port *port) { struct uart_amba_port *uap = (struct uart_amba_port *)port; @@ -1208,6 +1488,9 @@ static void pl011_tx_chars(struct uart_amba_port *uap) break; } while (--count > 0); + if (count) + pl011_clock_check(uap); + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&uap->port); @@ -1389,9 +1672,9 @@ static int pl011_startup(struct uart_port *port) goto out; /* - * Try to enable the clock producer. + * Try to enable the clock producer and the regulator. */ - retval = clk_enable(uap->clk); + retval = pl011_power_startup(uap); if (retval) goto clk_unprep; @@ -1408,29 +1691,7 @@ static int pl011_startup(struct uart_port *port) if (retval) goto clk_dis; - writew(uap->vendor->ifls, uap->port.membase + UART011_IFLS); - - /* - * Provoke TX FIFO interrupt into asserting. - */ - cr = UART01x_CR_UARTEN | UART011_CR_TXE | UART011_CR_LBE; - writew(cr, uap->port.membase + UART011_CR); - writew(0, uap->port.membase + UART011_FBRD); - writew(1, uap->port.membase + UART011_IBRD); - writew(0, uap->port.membase + uap->lcrh_rx); - if (uap->lcrh_tx != uap->lcrh_rx) { - int i; - /* - * Wait 10 PCLKs before writing LCRH_TX register, - * to get this delay write read only register 10 times - */ - for (i = 0; i < 10; ++i) - writew(0xff, uap->port.membase + UART011_MIS); - writew(0, uap->port.membase + uap->lcrh_tx); - } - writew(0, uap->port.membase + UART01x_DR); - while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY) - barrier(); + __pl011_startup(uap); /* restore RTS and DTR */ cr = uap->old_cr & (UART011_CR_RTS | UART011_CR_DTR); @@ -1471,7 +1732,7 @@ static int pl011_startup(struct uart_port *port) return 0; clk_dis: - clk_disable(uap->clk); + pl011_power_shutdown(uap); clk_unprep: clk_unprepare(uap->clk); out: @@ -1538,9 +1799,9 @@ static void pl011_shutdown(struct uart_port *port) } /* - * Shut down the clock producer + * Shut down the clock producer and the producer */ - clk_disable(uap->clk); + pl011_power_shutdown(uap); clk_unprepare(uap->clk); if (uap->port.dev->platform_data) { @@ -1553,6 +1814,32 @@ static void pl011_shutdown(struct uart_port *port) } +/* Power/Clock management. */ +static void pl011_serial_pm(struct uart_port *port, unsigned int state, +unsigned int oldstate) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + + switch (state) { + case 0: /*fully on */ + /* + * Enable the peripheral clock for this serial port. + * This is called on uart_open() or a resume event. + */ + pl011_power_startup(uap); + break; + case 3: /* powered down */ + /* + * Disable the peripheral clock for this serial port. + * This is called on uart_close() or a suspend event. + */ + pl011_power_shutdown(uap); + break; + default: + printk(KERN_ERR "pl011_serial: unknown pm %d\n", state); + } +} + static void pl011_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) @@ -1566,7 +1853,12 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, clkdiv = 8; else clkdiv = 16; - + /* + * Must be before uart_get_baud_rate() call, because + * this function changes baudrate to default in case of 0 + * B0 hangup !!! + */ + pl011_clock_control(port, termios, old); /* * Ask the core to calculate the divisor for us. */ @@ -1754,6 +2046,7 @@ static struct uart_ops amba_pl011_pops = { .request_port = pl010_request_port, .config_port = pl010_config_port, .verify_port = pl010_verify_port, + .pm = pl011_serial_pm, #ifdef CONFIG_CONSOLE_POLL .poll_get_char = pl010_get_poll_char, .poll_put_char = pl010_put_poll_char, @@ -1917,7 +2210,6 @@ static struct uart_driver amba_reg = { .nr = UART_NR, .cons = AMBA_CONSOLE, }; - static int pl011_probe(struct amba_device *dev, const struct amba_id *id) { struct uart_amba_port *uap; @@ -1946,6 +2238,12 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) goto free; } + uap->regulator = regulator_get(&dev->dev, "v-uart"); + if (IS_ERR(uap->regulator)) { + dev_warn(&dev->dev, "could not get uart regulator\n"); + uap->regulator = NULL; + } + uap->clk = clk_get(&dev->dev, NULL); if (IS_ERR(uap->clk)) { ret = PTR_ERR(uap->clk); @@ -1953,6 +2251,7 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) } uap->vendor = vendor; + uap->ifls = vendor->ifls; uap->lcrh_rx = vendor->lcrh_rx; uap->lcrh_tx = vendor->lcrh_tx; uap->old_cr = 0; @@ -1978,6 +2277,9 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) amba_ports[i] = uap; amba_set_drvdata(dev, uap); + + pl011_clock_control_init(uap); + ret = uart_add_one_port(&amba_reg, &uap->port); if (ret) { amba_set_drvdata(dev, NULL); @@ -1985,6 +2287,8 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) pl011_dma_remove(uap); clk_put(uap->clk); unmap: + if (uap->regulator) + regulator_put(uap->regulator); iounmap(base); free: kfree(uap); @@ -2008,6 +2312,8 @@ static int pl011_remove(struct amba_device *dev) pl011_dma_remove(uap); iounmap(uap->port.membase); + if (uap->regulator) + regulator_put(uap->regulator); clk_put(uap->clk); kfree(uap); return 0; |