2015-08-15 04:37:57

by Eduardo Valentin

[permalink] [raw]
Subject: [PATCHv4 0/4] serial: imx: rework pm support and add runtime pm


Hello all,


This is version 3 of a patch series to introduce runtime pm in the imx serial
driver. The idea is to get runtime pm to handle ipg and per clocks, idling
the device when possible, configuring wakeups, and saving and restoring
context when needed.

A minor refactoring was needed to get things done. On top of it I am also
adding pm_qos support in the driver too.

Changelog:

V3->V4:
- Remove *dev from sport and reused sport->port.dev.
- Rebased on top of tty-testing (initial 4 patches were already applied by greg)

V3: https://lkml.org/lkml/2015/8/11/581

V2->V3:
- error checking handling on clk_*enable functions
- added a missing return
- moved some of the code from the runtime pm patch to the pm qos patch, which
were causing compilation issues.
V2: http://marc.info/?l=linux-pm&m=143925695931624&w=2

V1->V2:
- The difference now is that it is rebased on top of linux-next, given
that some of the work done in v1 was already sent.
V1: http://marc.info/?l=linux-pm&m=143914435605790&w=2

As always, comments are welcome.

BR,

Eduardo Valentin (4):
serial: imx: add a flag to indicate we are in the suspend path
serial: imx: add runtime pm support
serial: imx: add pm_qos request
serial: imx: use SET_*SYSTEM_PM_OPS helper functions

drivers/tty/serial/imx.c | 296 ++++++++++++++++++++++++++++++++++++++---------
1 file changed, 244 insertions(+), 52 deletions(-)

--
2.5.0


2015-08-15 04:38:29

by Eduardo Valentin

[permalink] [raw]
Subject: [PATCHv4 1/4] serial: imx: add a flag to indicate we are in the suspend path

This is a simple flag that gets set across prepare and complete
callbacks in the suspend path. We the flag we may avoid
runtime pm idling at the same time.

Cc: Fabio Estevam <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: Jiri Slaby <[email protected]>
Cc: [email protected]
Cc: [email protected]
Signed-off-by: Eduardo Valentin <[email protected]>
---
drivers/tty/serial/imx.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)

diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index fe3d41c..50abb60 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -218,6 +218,8 @@ struct imx_port {
wait_queue_head_t dma_wait;
unsigned int saved_reg[10];
bool context_saved;
+
+ bool is_suspending;
};

struct imx_port_ucrs {
@@ -2026,6 +2028,22 @@ static void serial_imx_enable_wakeup(struct imx_port *sport, bool on)
writel(val, sport->port.membase + UCR1);
}

+static int serial_imx_prepare(struct device *dev)
+{
+ struct imx_port *sport = dev_get_drvdata(dev);
+
+ sport->is_suspending = true;
+
+ return 0;
+}
+
+static void serial_imx_complete(struct device *dev)
+{
+ struct imx_port *sport = dev_get_drvdata(dev);
+
+ sport->is_suspending = false;
+}
+
static int imx_serial_port_suspend_noirq(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
@@ -2091,6 +2109,8 @@ static const struct dev_pm_ops imx_serial_port_pm_ops = {
.resume_noirq = imx_serial_port_resume_noirq,
.suspend = imx_serial_port_suspend,
.resume = imx_serial_port_resume,
+ .prepare = serial_imx_prepare,
+ .complete = serial_imx_complete,
};

static struct platform_driver serial_imx_driver = {
--
2.5.0

2015-08-15 04:38:32

by Eduardo Valentin

[permalink] [raw]
Subject: [PATCHv4 2/4] serial: imx: add runtime pm support

This change introduces the runtime pm support on imx serial
driver. The objective is to be able to idle the uart
port whenever it is not in use while still being able
to wake it up when needed. The key changes in this patch are:
1. Move the clock handling to runtime pm. Both, ipg and per,
are now handled in the suspend and resume callbacks. Only
enabling and disabling the clocks are handled in runtime
suspend and resume, so we are able to use runtime pm
in IRQ context.
2. Clocks are prepared in probe and unprepared in remove,
so we do not need to prepare (may sleep) in runtime pm.
3. We mark the device activity based on uart and console
callbacks. Whenever the device is needed and we want to
access registers, we runtime_pm_get and then mark its
last usage when we are done. This is done also across
IRQs and DMA callbacks.
4. We reuse the infrastructure in place for suspend and
resume, so we do not need to redo wakeup configuration,
or context save and restore.

After this change, the clocks are still sane, in the sense
of having balanced clock prepare and enable.

Cc: Fabio Estevam <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: Jiri Slaby <[email protected]>
Cc: [email protected]
Cc: [email protected]
Signed-off-by: Eduardo Valentin <[email protected]>
---
drivers/tty/serial/imx.c | 239 +++++++++++++++++++++++++++++++++++++----------
1 file changed, 191 insertions(+), 48 deletions(-)

diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index 50abb60..d9ccf6b 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -39,6 +39,9 @@
#include <linux/of_device.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_wakeup.h>

#include <asm/irq.h>
#include <linux/platform_data/serial-imx.h>
@@ -369,6 +372,7 @@ static void imx_stop_tx(struct uart_port *port)
if (sport->dma_is_enabled && sport->dma_is_txing)
return;

+ pm_runtime_get_sync(sport->port.dev);
temp = readl(port->membase + UCR1);
writel(temp & ~UCR1_TXMPTYEN, port->membase + UCR1);

@@ -386,6 +390,8 @@ static void imx_stop_tx(struct uart_port *port)
temp &= ~UCR4_TCEN;
writel(temp, port->membase + UCR4);
}
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

/*
@@ -405,12 +411,15 @@ static void imx_stop_rx(struct uart_port *port)
}
}

+ pm_runtime_get_sync(sport->port.dev);
temp = readl(sport->port.membase + UCR2);
writel(temp & ~UCR2_RXEN, sport->port.membase + UCR2);

/* disable the `Receiver Ready Interrrupt` */
temp = readl(sport->port.membase + UCR1);
writel(temp & ~UCR1_RRDYEN, sport->port.membase + UCR1);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

/*
@@ -482,6 +491,7 @@ static void dma_tx_callback(void *data)
unsigned long flags;
unsigned long temp;

+ pm_runtime_get_sync(sport->port.dev);
spin_lock_irqsave(&sport->port.lock, flags);

dma_unmap_sg(sport->port.dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE);
@@ -506,13 +516,17 @@ static void dma_tx_callback(void *data)
if (waitqueue_active(&sport->dma_wait)) {
wake_up(&sport->dma_wait);
dev_dbg(sport->port.dev, "exit in %s.\n", __func__);
- return;
+ goto mark_last;
}

spin_lock_irqsave(&sport->port.lock, flags);
if (!uart_circ_empty(xmit) && !uart_tx_stopped(&sport->port))
imx_dma_tx(sport);
spin_unlock_irqrestore(&sport->port.lock, flags);
+
+mark_last:
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

static void imx_dma_tx(struct imx_port *sport)
@@ -579,6 +593,7 @@ static void imx_start_tx(struct uart_port *port)
struct imx_port *sport = (struct imx_port *)port;
unsigned long temp;

+ pm_runtime_get_sync(sport->port.dev);
if (port->rs485.flags & SER_RS485_ENABLED) {
/* enable transmitter and shifter empty irq */
temp = readl(port->membase + UCR2);
@@ -606,14 +621,18 @@ static void imx_start_tx(struct uart_port *port)
temp &= ~UCR1_TDMAEN;
temp |= UCR1_TXMPTYEN;
writel(temp, sport->port.membase + UCR1);
- return;
+ goto mark_last;
}

if (!uart_circ_empty(&port->state->xmit) &&
!uart_tx_stopped(port))
imx_dma_tx(sport);
- return;
+ goto mark_last;
}
+
+mark_last:
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

static irqreturn_t imx_rtsint(int irq, void *dev_id)
@@ -622,6 +641,7 @@ static irqreturn_t imx_rtsint(int irq, void *dev_id)
unsigned int val;
unsigned long flags;

+ pm_runtime_get_sync(sport->port.dev);
spin_lock_irqsave(&sport->port.lock, flags);

writel(USR1_RTSD, sport->port.membase + USR1);
@@ -630,6 +650,9 @@ static irqreturn_t imx_rtsint(int irq, void *dev_id)
wake_up_interruptible(&sport->port.state->port.delta_msr_wait);

spin_unlock_irqrestore(&sport->port.lock, flags);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
+
return IRQ_HANDLED;
}

@@ -638,9 +661,13 @@ static irqreturn_t imx_txint(int irq, void *dev_id)
struct imx_port *sport = dev_id;
unsigned long flags;

+ pm_runtime_get_sync(sport->port.dev);
spin_lock_irqsave(&sport->port.lock, flags);
imx_transmit_buffer(sport);
spin_unlock_irqrestore(&sport->port.lock, flags);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
+
return IRQ_HANDLED;
}

@@ -651,6 +678,7 @@ static irqreturn_t imx_rxint(int irq, void *dev_id)
struct tty_port *port = &sport->port.state->port;
unsigned long flags, temp;

+ pm_runtime_get_sync(sport->port.dev);
spin_lock_irqsave(&sport->port.lock, flags);

while (readl(sport->port.membase + USR2) & USR2_RDR) {
@@ -711,6 +739,8 @@ static irqreturn_t imx_rxint(int irq, void *dev_id)
out:
spin_unlock_irqrestore(&sport->port.lock, flags);
tty_flip_buffer_push(port);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
return IRQ_HANDLED;
}

@@ -748,6 +778,7 @@ static irqreturn_t imx_int(int irq, void *dev_id)
unsigned int sts;
unsigned int sts2;

+ pm_runtime_get_sync(sport->port.dev);
sts = readl(sport->port.membase + USR1);
sts2 = readl(sport->port.membase + USR2);

@@ -774,6 +805,8 @@ static irqreturn_t imx_int(int irq, void *dev_id)
sport->port.icount.overrun++;
writel(USR2_ORE, sport->port.membase + USR2);
}
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);

return IRQ_HANDLED;
}
@@ -786,11 +819,14 @@ static unsigned int imx_tx_empty(struct uart_port *port)
struct imx_port *sport = (struct imx_port *)port;
unsigned int ret;

+ pm_runtime_get_sync(sport->port.dev);
ret = (readl(sport->port.membase + USR2) & USR2_TXDC) ? TIOCSER_TEMT : 0;

/* If the TX DMA is working, return 0. */
if (sport->dma_is_enabled && sport->dma_is_txing)
ret = 0;
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);

return ret;
}
@@ -803,6 +839,7 @@ static unsigned int imx_get_mctrl(struct uart_port *port)
struct imx_port *sport = (struct imx_port *)port;
unsigned int tmp = TIOCM_DSR | TIOCM_CAR;

+ pm_runtime_get_sync(sport->port.dev);
if (readl(sport->port.membase + USR1) & USR1_RTSS)
tmp |= TIOCM_CTS;

@@ -811,6 +848,8 @@ static unsigned int imx_get_mctrl(struct uart_port *port)

if (readl(sport->port.membase + uts_reg(sport)) & UTS_LOOP)
tmp |= TIOCM_LOOP;
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);

return tmp;
}
@@ -820,6 +859,7 @@ static void imx_set_mctrl(struct uart_port *port, unsigned int mctrl)
struct imx_port *sport = (struct imx_port *)port;
unsigned long temp;

+ pm_runtime_get_sync(sport->port.dev);
if (!(port->rs485.flags & SER_RS485_ENABLED)) {
temp = readl(sport->port.membase + UCR2);
temp &= ~(UCR2_CTS | UCR2_CTSC);
@@ -832,6 +872,8 @@ static void imx_set_mctrl(struct uart_port *port, unsigned int mctrl)
if (mctrl & TIOCM_LOOP)
temp |= UTS_LOOP;
writel(temp, sport->port.membase + uts_reg(sport));
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

/*
@@ -842,6 +884,7 @@ static void imx_break_ctl(struct uart_port *port, int break_state)
struct imx_port *sport = (struct imx_port *)port;
unsigned long flags, temp;

+ pm_runtime_get_sync(sport->port.dev);
spin_lock_irqsave(&sport->port.lock, flags);

temp = readl(sport->port.membase + UCR1) & ~UCR1_SNDBRK;
@@ -852,6 +895,8 @@ static void imx_break_ctl(struct uart_port *port, int break_state)
writel(temp, sport->port.membase + UCR1);

spin_unlock_irqrestore(&sport->port.lock, flags);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

#define TXTL 2 /* reset default */
@@ -909,6 +954,7 @@ static void dma_rx_callback(void *data)
enum dma_status status;
unsigned int count;

+ pm_runtime_get_sync(sport->port.dev);
/* unmap it first */
dma_unmap_sg(sport->port.dev, sgl, 1, DMA_FROM_DEVICE);

@@ -950,6 +996,8 @@ static void dma_rx_callback(void *data)
*/
imx_rx_dma_done(sport);
}
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

static int start_rx_dma(struct imx_port *sport)
@@ -1105,18 +1153,10 @@ static void imx_disable_dma(struct imx_port *sport)
static int imx_startup(struct uart_port *port)
{
struct imx_port *sport = (struct imx_port *)port;
- int retval, i;
+ int i;
unsigned long flags, temp;

- retval = clk_prepare_enable(sport->clk_per);
- if (retval)
- return retval;
- retval = clk_prepare_enable(sport->clk_ipg);
- if (retval) {
- clk_disable_unprepare(sport->clk_per);
- return retval;
- }
-
+ pm_runtime_get_sync(sport->port.dev);
imx_setup_ufcr(sport, 0);

/* disable the DREN bit (Data Ready interrupt enable) before
@@ -1173,6 +1213,8 @@ static int imx_startup(struct uart_port *port)
*/
imx_enable_ms(&sport->port);
spin_unlock_irqrestore(&sport->port.lock, flags);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);

return 0;
}
@@ -1183,6 +1225,7 @@ static void imx_shutdown(struct uart_port *port)
unsigned long temp;
unsigned long flags;

+ pm_runtime_get_sync(sport->port.dev);
if (sport->dma_is_enabled) {
int ret;

@@ -1225,8 +1268,8 @@ static void imx_shutdown(struct uart_port *port)
writel(temp, sport->port.membase + UCR1);
spin_unlock_irqrestore(&sport->port.lock, flags);

- clk_disable_unprepare(sport->clk_per);
- clk_disable_unprepare(sport->clk_ipg);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

static void imx_flush_buffer(struct uart_port *port)
@@ -1239,6 +1282,7 @@ static void imx_flush_buffer(struct uart_port *port)
if (!sport->dma_chan_tx)
return;

+ pm_runtime_get_sync(sport->port.dev);
sport->tx_bytes = 0;
dmaengine_terminate_all(sport->dma_chan_tx);
if (sport->dma_is_txing) {
@@ -1272,6 +1316,8 @@ static void imx_flush_buffer(struct uart_port *port)
writel(ubir, sport->port.membase + UBIR);
writel(ubmr, sport->port.membase + UBMR);
writel(uts, sport->port.membase + IMX21_UTS);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

static void
@@ -1286,6 +1332,7 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
unsigned long num, denom;
uint64_t tdiv64;

+ pm_runtime_get_sync(sport->port.dev);
/*
* We only support CS7 and CS8.
*/
@@ -1441,6 +1488,8 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
if (sport->dma_is_inited && !sport->dma_is_enabled)
imx_enable_dma(sport);
spin_unlock_irqrestore(&sport->port.lock, flags);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

static const char *imx_type(struct uart_port *port)
@@ -1498,13 +1547,7 @@ static int imx_poll_init(struct uart_port *port)
unsigned long temp;
int retval;

- retval = clk_prepare_enable(sport->clk_ipg);
- if (retval)
- return retval;
- retval = clk_prepare_enable(sport->clk_per);
- if (retval)
- clk_disable_unprepare(sport->clk_ipg);
-
+ pm_runtime_get_sync(sport->port.dev);
imx_setup_ufcr(sport, 0);

spin_lock_irqsave(&sport->port.lock, flags);
@@ -1521,22 +1564,36 @@ static int imx_poll_init(struct uart_port *port)
writel(temp, sport->port.membase + UCR2);

spin_unlock_irqrestore(&sport->port.lock, flags);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);

return 0;
}

static int imx_poll_get_char(struct uart_port *port)
{
- if (!(readl_relaxed(port->membase + USR2) & USR2_RDR))
- return NO_POLL_CHAR;
+ int ret;
+
+ pm_runtime_get_sync(sport->port.dev);
+ if (!(readl_relaxed(port->membase + USR2) & USR2_RDR)) {
+ ret = NO_POLL_CHAR;
+ goto mark_last;
+ }
+
+ ret = readl_relaxed(port->membase + URXD0) & URXD_RX_DATA;
+
+mark_last:
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);

- return readl_relaxed(port->membase + URXD0) & URXD_RX_DATA;
+ return ret;
}

static void imx_poll_put_char(struct uart_port *port, unsigned char c)
{
unsigned int status;

+ pm_runtime_get_sync(sport->port.dev);
/* drain */
do {
status = readl_relaxed(port->membase + USR1);
@@ -1549,6 +1606,8 @@ static void imx_poll_put_char(struct uart_port *port, unsigned char c)
do {
status = readl_relaxed(port->membase + USR2);
} while (~status & USR2_TXDC);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}
#endif

@@ -1569,6 +1628,7 @@ static int imx_rs485_config(struct uart_port *port,
if (rs485conf->flags & SER_RS485_ENABLED) {
unsigned long temp;

+ pm_runtime_get_sync(sport->port.dev);
/* disable transmitter */
temp = readl(sport->port.membase + UCR2);
temp &= ~UCR2_CTSC;
@@ -1577,6 +1637,8 @@ static int imx_rs485_config(struct uart_port *port,
else
temp |= UCR2_CTS;
writel(temp, sport->port.membase + UCR2);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

port->rs485 = *rs485conf;
@@ -1631,17 +1693,8 @@ imx_console_write(struct console *co, const char *s, unsigned int count)
unsigned int ucr1;
unsigned long flags = 0;
int locked = 1;
- int retval;
-
- retval = clk_prepare_enable(sport->clk_per);
- if (retval)
- return;
- retval = clk_prepare_enable(sport->clk_ipg);
- if (retval) {
- clk_disable_unprepare(sport->clk_per);
- return;
- }

+ pm_runtime_get_sync(sport->port.dev);
if (sport->port.sysrq)
locked = 0;
else if (oops_in_progress)
@@ -1677,8 +1730,8 @@ imx_console_write(struct console *co, const char *s, unsigned int count)
if (locked)
spin_unlock_irqrestore(&sport->port.lock, flags);

- clk_disable_unprepare(sport->clk_ipg);
- clk_disable_unprepare(sport->clk_per);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
}

/*
@@ -1765,10 +1818,7 @@ imx_console_setup(struct console *co, char *options)
if (sport == NULL)
return -ENODEV;

- /* For setting the registers, we only need to enable the ipg clock. */
- retval = clk_prepare_enable(sport->clk_ipg);
- if (retval)
- goto error_console;
+ pm_runtime_get_sync(sport->port.dev);

if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
@@ -1779,9 +1829,8 @@ imx_console_setup(struct console *co, char *options)

retval = uart_set_options(&sport->port, co, baud, parity, bits, flow);

- clk_disable_unprepare(sport->clk_ipg);
-
-error_console:
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
return retval;
}

@@ -1965,14 +2014,36 @@ static int serial_imx_probe(struct platform_device *pdev)

platform_set_drvdata(pdev, sport);

- return uart_add_one_port(&imx_reg, &sport->port);
+ device_init_wakeup(sport->port.dev, true);
+ pm_runtime_use_autosuspend(sport->port.dev);
+ pm_runtime_set_autosuspend_delay(sport->port.dev, 5000);
+
+ pm_runtime_irq_safe(sport->port.dev);
+ pm_runtime_enable(sport->port.dev);
+
+ clk_prepare(sport->clk_per);
+ clk_prepare(sport->clk_ipg);
+ pm_runtime_get_sync(sport->port.dev);
+ ret = uart_add_one_port(&imx_reg, &sport->port);
+ pm_runtime_mark_last_busy(sport->port.dev);
+ pm_runtime_put_autosuspend(sport->port.dev);
+
+ return ret;
}

static int serial_imx_remove(struct platform_device *pdev)
{
struct imx_port *sport = platform_get_drvdata(pdev);
+ int ret;
+
+ pm_runtime_put_sync(sport->port.dev);
+ pm_runtime_disable(sport->port.dev);
+ clk_unprepare(sport->clk_per);
+ clk_unprepare(sport->clk_ipg);
+ ret = uart_remove_one_port(&imx_reg, &sport->port);
+ device_init_wakeup(&pdev->dev, false);

- return uart_remove_one_port(&imx_reg, &sport->port);
+ return ret;
}

static void serial_imx_restore_context(struct imx_port *sport)
@@ -2028,6 +2099,52 @@ static void serial_imx_enable_wakeup(struct imx_port *sport, bool on)
writel(val, sport->port.membase + UCR1);
}

+static int serial_imx_runtime_suspend(struct device *dev)
+{
+ struct imx_port *sport = dev_get_drvdata(dev);
+
+ if (!sport)
+ return -EINVAL;
+
+ /*
+ * When using 'no_console_suspend', the console UART must not be
+ * suspended. Since driver suspend is managed by runtime suspend,
+ * preventing runtime suspend (by returning error) will keep device
+ * active during suspend.
+ */
+ if (sport->is_suspending && !console_suspend_enabled &&
+ uart_console(&sport->port))
+ return -EBUSY;
+
+ serial_imx_save_context(sport);
+ serial_imx_enable_wakeup(sport, true);
+
+ clk_disable(sport->clk_per);
+ clk_disable(sport->clk_ipg);
+
+ return 0;
+}
+
+static int serial_imx_runtime_resume(struct device *dev)
+{
+ struct imx_port *sport = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_enable(sport->clk_per);
+ if (ret)
+ return ret;
+ ret = clk_enable(sport->clk_ipg);
+ if (ret) {
+ clk_disable(sport->clk_per);
+ return ret;
+ }
+ serial_imx_enable_wakeup(sport, false);
+
+ serial_imx_restore_context(sport);
+
+ return 0;
+}
+
static int serial_imx_prepare(struct device *dev)
{
struct imx_port *sport = dev_get_drvdata(dev);
@@ -2082,12 +2199,24 @@ static int imx_serial_port_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct imx_port *sport = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = clk_prepare_enable(sport->clk_per);
+ if (ret)
+ return ret;
+ ret = clk_prepare_enable(sport->clk_ipg);
+ if (ret) {
+ clk_disable_unprepare(sport->clk_per);
+ return ret;
+ }

/* enable wakeup from i.MX UART */
serial_imx_enable_wakeup(sport, true);
-
uart_suspend_port(&imx_reg, &sport->port);

+ clk_disable_unprepare(sport->clk_per);
+ clk_disable_unprepare(sport->clk_ipg);
+
return 0;
}

@@ -2095,16 +2224,30 @@ static int imx_serial_port_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct imx_port *sport = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = clk_prepare_enable(sport->clk_per);
+ if (ret)
+ return ret;
+ ret = clk_prepare_enable(sport->clk_ipg);
+ if (ret) {
+ clk_disable_unprepare(sport->clk_per);
+ return ret;
+ }

/* disable wakeup from i.MX UART */
serial_imx_enable_wakeup(sport, false);
-
uart_resume_port(&imx_reg, &sport->port);

+ clk_disable_unprepare(sport->clk_per);
+ clk_disable_unprepare(sport->clk_ipg);
+
return 0;
}

static const struct dev_pm_ops imx_serial_port_pm_ops = {
+ SET_RUNTIME_PM_OPS(serial_imx_runtime_suspend,
+ serial_imx_runtime_resume, NULL)
.suspend_noirq = imx_serial_port_suspend_noirq,
.resume_noirq = imx_serial_port_resume_noirq,
.suspend = imx_serial_port_suspend,
--
2.5.0

2015-08-15 04:38:34

by Eduardo Valentin

[permalink] [raw]
Subject: [PATCHv4 3/4] serial: imx: add pm_qos request

This change introduces pm_qos requests in the imx serial driver.
The idea is to skip deeper C-state in case we need a strict
latency requirement in the uart port. The latency is
computed based on the buffer size and the current baud rate.
We schedule a work queue to set the pm qos requirement.

Cc: Fabio Estevam <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: Jiri Slaby <[email protected]>
Cc: [email protected]
Cc: [email protected]
Signed-off-by: Eduardo Valentin <[email protected]>
---
drivers/tty/serial/imx.c | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)

diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index d9ccf6b..24ed0fa 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -42,6 +42,7 @@
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/pm_wakeup.h>
+#include <linux/pm_qos.h>

#include <asm/irq.h>
#include <linux/platform_data/serial-imx.h>
@@ -222,6 +223,10 @@ struct imx_port {
unsigned int saved_reg[10];
bool context_saved;

+ struct pm_qos_request pm_qos_request;
+ u32 latency;
+ u32 calc_latency;
+ struct work_struct qos_work;
bool is_suspending;
};

@@ -1320,6 +1325,14 @@ static void imx_flush_buffer(struct uart_port *port)
pm_runtime_put_autosuspend(sport->port.dev);
}

+static void serial_imx_uart_qos_work(struct work_struct *work)
+{
+ struct imx_port *sport = container_of(work, struct imx_port,
+ qos_work);
+
+ pm_qos_update_request(&sport->pm_qos_request, sport->latency);
+}
+
static void
imx_set_termios(struct uart_port *port, struct ktermios *termios,
struct ktermios *old)
@@ -1393,6 +1406,12 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
baud = uart_get_baud_rate(port, termios, old, 50, port->uartclk / 16);
quot = uart_get_divisor(port, baud);

+ /* calculate wakeup latency constraint */
+ sport->calc_latency = (USEC_PER_SEC * sport->port.fifosize) /
+ (baud / 8);
+ sport->latency = sport->calc_latency;
+ schedule_work(&sport->qos_work);
+
spin_lock_irqsave(&sport->port.lock, flags);

sport->port.read_status_mask = 0;
@@ -2012,6 +2031,11 @@ static int serial_imx_probe(struct platform_device *pdev)

imx_ports[sport->port.line] = sport;

+ sport->latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;
+ sport->calc_latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;
+ pm_qos_add_request(&sport->pm_qos_request, PM_QOS_CPU_DMA_LATENCY,
+ sport->latency);
+ INIT_WORK(&sport->qos_work, serial_imx_uart_qos_work);
platform_set_drvdata(pdev, sport);

device_init_wakeup(sport->port.dev, true);
@@ -2041,6 +2065,7 @@ static int serial_imx_remove(struct platform_device *pdev)
clk_unprepare(sport->clk_per);
clk_unprepare(sport->clk_ipg);
ret = uart_remove_one_port(&imx_reg, &sport->port);
+ pm_qos_remove_request(&sport->pm_qos_request);
device_init_wakeup(&pdev->dev, false);

return ret;
@@ -2119,6 +2144,8 @@ static int serial_imx_runtime_suspend(struct device *dev)
serial_imx_save_context(sport);
serial_imx_enable_wakeup(sport, true);

+ sport->latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;
+ schedule_work(&sport->qos_work);
clk_disable(sport->clk_per);
clk_disable(sport->clk_ipg);

@@ -2140,6 +2167,8 @@ static int serial_imx_runtime_resume(struct device *dev)
}
serial_imx_enable_wakeup(sport, false);

+ sport->latency = sport->calc_latency;
+ schedule_work(&sport->qos_work);
serial_imx_restore_context(sport);

return 0;
--
2.5.0

2015-08-15 04:38:38

by Eduardo Valentin

[permalink] [raw]
Subject: [PATCHv4 4/4] serial: imx: use SET_*SYSTEM_PM_OPS helper functions

Instead of setting manually each field, use the helper functions
provided by the PM subsystem.

Signed-off-by: Eduardo Valentin <[email protected]>
---
drivers/tty/serial/imx.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index 24ed0fa..bdda44d5 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -2277,10 +2277,10 @@ static int imx_serial_port_resume(struct device *dev)
static const struct dev_pm_ops imx_serial_port_pm_ops = {
SET_RUNTIME_PM_OPS(serial_imx_runtime_suspend,
serial_imx_runtime_resume, NULL)
- .suspend_noirq = imx_serial_port_suspend_noirq,
- .resume_noirq = imx_serial_port_resume_noirq,
- .suspend = imx_serial_port_suspend,
- .resume = imx_serial_port_resume,
+ SET_SYSTEM_SLEEP_PM_OPS(imx_serial_port_suspend_noirq,
+ imx_serial_port_resume_noirq)
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_serial_port_suspend,
+ imx_serial_port_resume)
.prepare = serial_imx_prepare,
.complete = serial_imx_complete,
};
--
2.5.0

Subject: Re: [PATCHv4 2/4] serial: imx: add runtime pm support


Hi,

On Friday, August 14, 2015 09:37:46 PM Eduardo Valentin wrote:
> This change introduces the runtime pm support on imx serial
> driver. The objective is to be able to idle the uart
> port whenever it is not in use while still being able
> to wake it up when needed. The key changes in this patch are:
> 1. Move the clock handling to runtime pm. Both, ipg and per,
> are now handled in the suspend and resume callbacks. Only
> enabling and disabling the clocks are handled in runtime
> suspend and resume, so we are able to use runtime pm
> in IRQ context.
> 2. Clocks are prepared in probe and unprepared in remove,
> so we do not need to prepare (may sleep) in runtime pm.
> 3. We mark the device activity based on uart and console
> callbacks. Whenever the device is needed and we want to
> access registers, we runtime_pm_get and then mark its
> last usage when we are done. This is done also across
> IRQs and DMA callbacks.
> 4. We reuse the infrastructure in place for suspend and
> resume, so we do not need to redo wakeup configuration,
> or context save and restore.
>
> After this change, the clocks are still sane, in the sense
> of having balanced clock prepare and enable.

The clock changes in this patch seem to make this driver
non-functional with CONFIG_PM=n. Have you tested your
changes with CONFIG_PM=n?

Generally the driver should not depend on PM support to
enable its clocks. We had this issue in few Exynos-specific
drivers not that long time ago..

Best regards,
--
Bartlomiej Zolnierkiewicz
Samsung R&D Institute Poland
Samsung Electronics

2015-08-17 17:28:33

by Eduardo Valentin

[permalink] [raw]
Subject: Re: [PATCHv4 2/4] serial: imx: add runtime pm support

Bartlomiej,

On Mon, Aug 17, 2015 at 05:40:59PM +0200, Bartlomiej Zolnierkiewicz wrote:
>
> Hi,
>
> On Friday, August 14, 2015 09:37:46 PM Eduardo Valentin wrote:
> > This change introduces the runtime pm support on imx serial
> > driver. The objective is to be able to idle the uart
> > port whenever it is not in use while still being able
> > to wake it up when needed. The key changes in this patch are:
> > 1. Move the clock handling to runtime pm. Both, ipg and per,
> > are now handled in the suspend and resume callbacks. Only
> > enabling and disabling the clocks are handled in runtime
> > suspend and resume, so we are able to use runtime pm
> > in IRQ context.
> > 2. Clocks are prepared in probe and unprepared in remove,
> > so we do not need to prepare (may sleep) in runtime pm.
> > 3. We mark the device activity based on uart and console
> > callbacks. Whenever the device is needed and we want to
> > access registers, we runtime_pm_get and then mark its
> > last usage when we are done. This is done also across
> > IRQs and DMA callbacks.
> > 4. We reuse the infrastructure in place for suspend and
> > resume, so we do not need to redo wakeup configuration,
> > or context save and restore.
> >
> > After this change, the clocks are still sane, in the sense
> > of having balanced clock prepare and enable.
>
> The clock changes in this patch seem to make this driver
> non-functional with CONFIG_PM=n. Have you tested your
> changes with CONFIG_PM=n?

This is a valid point. I tried it yes, but maybe because in my
environment I had another user of the pll3_80m I did not see
issues with PM=n. Now after sanitizing my .config, executing
the system with only the uart port / console as user of pll3_80m,
I see the uart console hanging because the uart clocks get gated.

>
> Generally the driver should not depend on PM support to
> enable its clocks. We had this issue in few Exynos-specific
> drivers not that long time ago..
>

No doubt here. I am checking the best way to cover for this case.
Probably I will go for adding the support via the pm_clk*
infrastructure, as already suggested by khilman.

Thanks for spotting this.

BR,

> Best regards,
> --
> Bartlomiej Zolnierkiewicz
> Samsung R&D Institute Poland
> Samsung Electronics
>


Attachments:
(No filename) (2.22 kB)
signature.asc (490.00 B)
Digital signature
Download all attachments