diff options
Diffstat (limited to 'drivers/mailbox/imx-mailbox.c')
-rw-r--r-- | drivers/mailbox/imx-mailbox.c | 117 |
1 files changed, 107 insertions, 10 deletions
diff --git a/drivers/mailbox/imx-mailbox.c b/drivers/mailbox/imx-mailbox.c index 7906624a731c..7205b825c8b5 100644 --- a/drivers/mailbox/imx-mailbox.c +++ b/drivers/mailbox/imx-mailbox.c @@ -12,6 +12,7 @@ #include <linux/mailbox_controller.h> #include <linux/module.h> #include <linux/of_device.h> +#include <linux/pm_runtime.h> #include <linux/slab.h> #define IMX_MU_xSR_GIPn(x) BIT(28 + (3 - (x))) @@ -66,6 +67,8 @@ struct imx_mu_priv { struct clk *clk; int irq; + u32 xcr; + bool side_b; }; @@ -154,12 +157,17 @@ static int imx_mu_scu_tx(struct imx_mu_priv *priv, switch (cp->type) { case IMX_MU_TYPE_TX: - if (msg->hdr.size > sizeof(*msg)) { + /* + * msg->hdr.size specifies the number of u32 words while + * sizeof yields bytes. + */ + + if (msg->hdr.size > sizeof(*msg) / 4) { /* * The real message size can be different to * struct imx_sc_rpc_msg_max size */ - dev_err(priv->dev, "Exceed max msg size (%zu) on TX, got: %i\n", sizeof(*msg), msg->hdr.size); + dev_err(priv->dev, "Maximal message size (%zu bytes) exceeded on TX; got: %i bytes\n", sizeof(*msg), msg->hdr.size << 2); return -EINVAL; } @@ -198,9 +206,8 @@ static int imx_mu_scu_rx(struct imx_mu_priv *priv, imx_mu_xcr_rmw(priv, 0, IMX_MU_xCR_RIEn(0)); *data++ = imx_mu_read(priv, priv->dcfg->xRR[0]); - if (msg.hdr.size > sizeof(msg)) { - dev_err(priv->dev, "Exceed max msg size (%zu) on RX, got: %i\n", - sizeof(msg), msg.hdr.size); + if (msg.hdr.size > sizeof(msg) / 4) { + dev_err(priv->dev, "Maximal message size (%zu bytes) exceeded on RX; got: %i bytes\n", sizeof(msg), msg.hdr.size << 2); return -EINVAL; } @@ -285,8 +292,10 @@ static int imx_mu_startup(struct mbox_chan *chan) { struct imx_mu_priv *priv = to_imx_mu_priv(chan->mbox); struct imx_mu_con_priv *cp = chan->con_priv; + unsigned long irq_flag = IRQF_SHARED; int ret; + pm_runtime_get_sync(priv->dev); if (cp->type == IMX_MU_TYPE_TXDB) { /* Tx doorbell don't have ACK support */ tasklet_init(&cp->txdb_tasklet, imx_mu_txdb_tasklet, @@ -294,8 +303,12 @@ static int imx_mu_startup(struct mbox_chan *chan) return 0; } - ret = request_irq(priv->irq, imx_mu_isr, IRQF_SHARED | - IRQF_NO_SUSPEND, cp->irq_desc, chan); + /* IPC MU should be with IRQF_NO_SUSPEND set */ + if (!priv->dev->pm_domain) + irq_flag |= IRQF_NO_SUSPEND; + + ret = request_irq(priv->irq, imx_mu_isr, irq_flag, + cp->irq_desc, chan); if (ret) { dev_err(priv->dev, "Unable to acquire IRQ %d\n", priv->irq); @@ -323,6 +336,7 @@ static void imx_mu_shutdown(struct mbox_chan *chan) if (cp->type == IMX_MU_TYPE_TXDB) { tasklet_kill(&cp->txdb_tasklet); + pm_runtime_put_sync(priv->dev); return; } @@ -341,6 +355,7 @@ static void imx_mu_shutdown(struct mbox_chan *chan) } free_irq(priv->irq, chan); + pm_runtime_put_sync(priv->dev); } static const struct mbox_chan_ops imx_mu_ops = { @@ -374,7 +389,7 @@ static struct mbox_chan *imx_mu_scu_xlate(struct mbox_controller *mbox, break; default: dev_err(mbox->dev, "Invalid chan type: %d\n", type); - return NULL; + return ERR_PTR(-EINVAL); } if (chan >= mbox->num_chans) { @@ -508,14 +523,39 @@ static int imx_mu_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); - return devm_mbox_controller_register(dev, &priv->mbox); + ret = devm_mbox_controller_register(dev, &priv->mbox); + if (ret) { + clk_disable_unprepare(priv->clk); + return ret; + } + + pm_runtime_enable(dev); + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + goto disable_runtime_pm; + } + + ret = pm_runtime_put_sync(dev); + if (ret < 0) + goto disable_runtime_pm; + + clk_disable_unprepare(priv->clk); + + return 0; + +disable_runtime_pm: + pm_runtime_disable(dev); + clk_disable_unprepare(priv->clk); + return ret; } static int imx_mu_remove(struct platform_device *pdev) { struct imx_mu_priv *priv = platform_get_drvdata(pdev); - clk_disable_unprepare(priv->clk); + pm_runtime_disable(priv->dev); return 0; } @@ -558,12 +598,69 @@ static const struct of_device_id imx_mu_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_mu_dt_ids); +static int imx_mu_suspend_noirq(struct device *dev) +{ + struct imx_mu_priv *priv = dev_get_drvdata(dev); + + if (!priv->clk) + priv->xcr = imx_mu_read(priv, priv->dcfg->xCR); + + return 0; +} + +static int imx_mu_resume_noirq(struct device *dev) +{ + struct imx_mu_priv *priv = dev_get_drvdata(dev); + + /* + * ONLY restore MU when context lost, the TIE could + * be set during noirq resume as there is MU data + * communication going on, and restore the saved + * value will overwrite the TIE and cause MU data + * send failed, may lead to system freeze. This issue + * is observed by testing freeze mode suspend. + */ + if (!imx_mu_read(priv, priv->dcfg->xCR) && !priv->clk) + imx_mu_write(priv, priv->xcr, priv->dcfg->xCR); + + return 0; +} + +static int imx_mu_runtime_suspend(struct device *dev) +{ + struct imx_mu_priv *priv = dev_get_drvdata(dev); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int imx_mu_runtime_resume(struct device *dev) +{ + struct imx_mu_priv *priv = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) + dev_err(dev, "failed to enable clock\n"); + + return ret; +} + +static const struct dev_pm_ops imx_mu_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_mu_suspend_noirq, + imx_mu_resume_noirq) + SET_RUNTIME_PM_OPS(imx_mu_runtime_suspend, + imx_mu_runtime_resume, NULL) +}; + static struct platform_driver imx_mu_driver = { .probe = imx_mu_probe, .remove = imx_mu_remove, .driver = { .name = "imx_mu", .of_match_table = imx_mu_dt_ids, + .pm = &imx_mu_pm_ops, }, }; module_platform_driver(imx_mu_driver); |