aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Walmsley <paul@pwsan.com>2012-01-21 02:27:41 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2012-01-24 17:11:07 -0500
commit43cf7c0bebf50d0b68aa42ae6d24cf08e3f24823 (patch)
tree84d71f6a775c017af20c3c9c4fc951d09e1eb4dd
parent0a697b22252c9d7208b5fb3e9fbd124dd229f1d2 (diff)
tty: serial: OMAP: transmit FIFO threshold interrupts don't wake the chip
It seems that when the transmit FIFO threshold is reached on OMAP UARTs, it does not result in a PRCM wakeup. This appears to be a silicon bug. This means that if the MPU powerdomain is in a low-power state, the MPU will not be awakened to refill the FIFO until the next interrupt from another device. The best solution, at least for the short term, would be for the OMAP serial driver to call a OMAP subarchitecture function to prevent the MPU powerdomain from entering a low power state while the FIFO has data to transmit. However, we no longer have a clean way to do this, since patches that add platform_data function pointers have been deprecated by the OMAP maintainer. So we attempt to work around this as well. The workarounds depend on the setting of CONFIG_CPU_IDLE. When CONFIG_CPU_IDLE=n, the driver will now only transmit one byte at a time. This causes the transmit FIFO threshold interrupt to stay active until there is no more data to be sent. Thus, the MPU powerdomain stays on during transmits. Aside from that energy consumption penalty, each transmitted byte results in a huge number of UART interrupts -- about five per byte. This wastes CPU time and is quite inefficient, but is probably the most expedient workaround in this case. When CONFIG_CPU_IDLE=y, there is a slightly more direct workaround: the PM QoS constraint can be abused to keep the MPU powerdomain on. This results in a normal number of interrupts, but, similar to the above workaround, wastes power by preventing the MPU from entering WFI. Future patches are planned for the 3.4 merge window to implement more efficient, but also more disruptive, workarounds to these problems. DMA operation is unaffected by this patch. Signed-off-by: Paul Walmsley <paul@pwsan.com> Cc: Tomi Valkeinen <tomi.valkeinen@ti.com> Cc: Govindraj Raja <govindraj.r@ti.com> Cc: Kevin Hilman <khilman@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-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 9ff444469f3d..12a64eb8c624 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 {
131 u32 context_loss_cnt; 131 u32 context_loss_cnt;
132 u32 errata; 132 u32 errata;
133 u8 wakeups_enabled; 133 u8 wakeups_enabled;
134 u8 max_tx_count;
134 135
135 struct pm_qos_request pm_qos_request; 136 struct pm_qos_request pm_qos_request;
136 u32 latency; 137 u32 latency;
diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c
index ca54f038ab45..e00ac05cfdb4 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)
88 serial_out(up, UART_FCR, 0); 88 serial_out(up, UART_FCR, 0);
89} 89}
90 90
91/**
92 * serial_omap_block_cpu_low_power_state - prevent MPU pwrdm from leaving ON
93 * @up: struct uart_omap_port *
94 *
95 * Prevent the MPU powerdomain from entering a power state lower than
96 * ON. (It should be sufficient to prevent it from entering INACTIVE,
97 * but there is presently no easy way to do this.) This works around
98 * a suspected silicon bug in the OMAP UART IP blocks. The UARTs should
99 * wake the PRCM when the transmit FIFO threshold interrupt is raised, but
100 * they do not. See also serial_omap_allow_cpu_low_power_state(). No
101 * return value.
102 */
103static void serial_omap_block_cpu_low_power_state(struct uart_omap_port *up)
104{
105#ifdef CONFIG_CPU_IDLE
106 up->latency = 1;
107 schedule_work(&up->qos_work);
108#else
109 up->max_tx_count = 1;
110#endif
111}
112
113/**
114 * serial_omap_allow_cpu_low_power_state - remove power state restriction on MPU
115 * @up: struct uart_omap_port *
116 *
117 * Cancel the effects of serial_omap_block_cpu_low_power_state().
118 * This should allow the MPU powerdomain to enter a power state lower
119 * than ON, assuming the rest of the kernel is not restricting it.
120 * This works around a suspected silicon bug in the OMAP UART IP
121 * blocks. The UARTs should wake the PRCM when the transmit FIFO
122 * threshold interrupt is raised, but they do not. No return value.
123 */
124static void serial_omap_allow_cpu_low_power_state(struct uart_omap_port *up)
125{
126#ifdef CONFIG_CPU_IDLE
127 up->latency = up->calc_latency;
128 schedule_work(&up->qos_work);
129#else
130 up->max_tx_count = up->port.fifosize / 4;
131#endif
132}
133
91/* 134/*
92 * serial_omap_get_divisor - calculate divisor value 135 * serial_omap_get_divisor - calculate divisor value
93 * @port: uart port info 136 * @port: uart port info
@@ -163,6 +206,9 @@ static void serial_omap_stop_tx(struct uart_port *port)
163 serial_out(up, UART_IER, up->ier); 206 serial_out(up, UART_IER, up->ier);
164 } 207 }
165 208
209 if (!up->use_dma)
210 serial_omap_allow_cpu_low_power_state(up);
211
166 pm_runtime_mark_last_busy(&up->pdev->dev); 212 pm_runtime_mark_last_busy(&up->pdev->dev);
167 pm_runtime_put_autosuspend(&up->pdev->dev); 213 pm_runtime_put_autosuspend(&up->pdev->dev);
168} 214}
@@ -264,7 +310,7 @@ static void transmit_chars(struct uart_omap_port *up)
264 serial_omap_stop_tx(&up->port); 310 serial_omap_stop_tx(&up->port);
265 return; 311 return;
266 } 312 }
267 count = up->port.fifosize / 4; 313 count = up->max_tx_count;
268 do { 314 do {
269 serial_out(up, UART_TX, xmit->buf[xmit->tail]); 315 serial_out(up, UART_TX, xmit->buf[xmit->tail]);
270 xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); 316 xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
@@ -297,6 +343,7 @@ static void serial_omap_start_tx(struct uart_port *port)
297 343
298 if (!up->use_dma) { 344 if (!up->use_dma) {
299 pm_runtime_get_sync(&up->pdev->dev); 345 pm_runtime_get_sync(&up->pdev->dev);
346 serial_omap_block_cpu_low_power_state(up);
300 serial_omap_enable_ier_thri(up); 347 serial_omap_enable_ier_thri(up);
301 pm_runtime_mark_last_busy(&up->pdev->dev); 348 pm_runtime_mark_last_busy(&up->pdev->dev);
302 pm_runtime_put_autosuspend(&up->pdev->dev); 349 pm_runtime_put_autosuspend(&up->pdev->dev);
@@ -1421,6 +1468,8 @@ static int serial_omap_probe(struct platform_device *pdev)
1421 up->port.fifosize = 64; 1468 up->port.fifosize = 64;
1422 up->port.ops = &serial_omap_pops; 1469 up->port.ops = &serial_omap_pops;
1423 1470
1471 up->max_tx_count = up->port.fifosize / 4;
1472
1424 if (pdev->dev.of_node) 1473 if (pdev->dev.of_node)
1425 up->port.line = of_alias_get_id(pdev->dev.of_node, "serial"); 1474 up->port.line = of_alias_get_id(pdev->dev.of_node, "serial");
1426 else 1475 else