aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/plat-omap/include/plat/omap-serial.h1
-rw-r--r--drivers/tty/serial/omap-serial.c51
2 files changed, 51 insertions, 1 deletions
diff --git a/arch/arm/plat-omap/include/plat/omap-serial.h b/arch/arm/plat-omap/include/plat/omap-serial.h
index 9ff444469f3..12a64eb8c62 100644
--- a/arch/arm/plat-omap/include/plat/omap-serial.h
+++ b/arch/arm/plat-omap/include/plat/omap-serial.h
@@ -131,6 +131,7 @@ struct uart_omap_port {
u32 context_loss_cnt;
u32 errata;
u8 wakeups_enabled;
+ u8 max_tx_count;
struct pm_qos_request pm_qos_request;
u32 latency;
diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c
index ca54f038ab4..e00ac05cfdb 100644
--- a/drivers/tty/serial/omap-serial.c
+++ b/drivers/tty/serial/omap-serial.c
@@ -88,6 +88,49 @@ static inline void serial_omap_clear_fifos(struct uart_omap_port *up)
serial_out(up, UART_FCR, 0);
}
+/**
+ * serial_omap_block_cpu_low_power_state - prevent MPU pwrdm from leaving ON
+ * @up: struct uart_omap_port *
+ *
+ * Prevent the MPU powerdomain from entering a power state lower than
+ * ON. (It should be sufficient to prevent it from entering INACTIVE,
+ * but there is presently no easy way to do this.) This works around
+ * a suspected silicon bug in the OMAP UART IP blocks. The UARTs should
+ * wake the PRCM when the transmit FIFO threshold interrupt is raised, but
+ * they do not. See also serial_omap_allow_cpu_low_power_state(). No
+ * return value.
+ */
+static void serial_omap_block_cpu_low_power_state(struct uart_omap_port *up)
+{
+#ifdef CONFIG_CPU_IDLE
+ up->latency = 1;
+ schedule_work(&up->qos_work);
+#else
+ up->max_tx_count = 1;
+#endif
+}
+
+/**
+ * serial_omap_allow_cpu_low_power_state - remove power state restriction on MPU
+ * @up: struct uart_omap_port *
+ *
+ * Cancel the effects of serial_omap_block_cpu_low_power_state().
+ * This should allow the MPU powerdomain to enter a power state lower
+ * than ON, assuming the rest of the kernel is not restricting it.
+ * This works around a suspected silicon bug in the OMAP UART IP
+ * blocks. The UARTs should wake the PRCM when the transmit FIFO
+ * threshold interrupt is raised, but they do not. No return value.
+ */
+static void serial_omap_allow_cpu_low_power_state(struct uart_omap_port *up)
+{
+#ifdef CONFIG_CPU_IDLE
+ up->latency = up->calc_latency;
+ schedule_work(&up->qos_work);
+#else
+ up->max_tx_count = up->port.fifosize / 4;
+#endif
+}
+
/*
* serial_omap_get_divisor - calculate divisor value
* @port: uart port info
@@ -163,6 +206,9 @@ static void serial_omap_stop_tx(struct uart_port *port)
serial_out(up, UART_IER, up->ier);
}
+ if (!up->use_dma)
+ serial_omap_allow_cpu_low_power_state(up);
+
pm_runtime_mark_last_busy(&up->pdev->dev);
pm_runtime_put_autosuspend(&up->pdev->dev);
}
@@ -264,7 +310,7 @@ static void transmit_chars(struct uart_omap_port *up)
serial_omap_stop_tx(&up->port);
return;
}
- count = up->port.fifosize / 4;
+ count = up->max_tx_count;
do {
serial_out(up, UART_TX, xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
@@ -297,6 +343,7 @@ static void serial_omap_start_tx(struct uart_port *port)
if (!up->use_dma) {
pm_runtime_get_sync(&up->pdev->dev);
+ serial_omap_block_cpu_low_power_state(up);
serial_omap_enable_ier_thri(up);
pm_runtime_mark_last_busy(&up->pdev->dev);
pm_runtime_put_autosuspend(&up->pdev->dev);
@@ -1421,6 +1468,8 @@ static int serial_omap_probe(struct platform_device *pdev)
up->port.fifosize = 64;
up->port.ops = &serial_omap_pops;
+ up->max_tx_count = up->port.fifosize / 4;
+
if (pdev->dev.of_node)
up->port.line = of_alias_get_id(pdev->dev.of_node, "serial");
else