]> www.infradead.org Git - users/hch/misc.git/commitdiff
i3c: dw: Add power management support
authorAniket <aniketmaurya@google.com>
Mon, 8 Jul 2024 06:21:03 +0000 (06:21 +0000)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Fri, 26 Jul 2024 12:21:30 +0000 (14:21 +0200)
Add support for runtime and system power management.
Handle clocks, resets, pads as part of suspend and resume.
Restore controller registers that could be lost due to suspend.
Finally add get and put calls appropriately in functions which
access controller : bus_init, do_daa, send_ccc_cmd, priv_xfers,
i2c_xfers, ibi and hot-join.

Signed-off-by: Aniket <aniketmaurya@google.com>
Link: https://lore.kernel.org/r/20240708062103.3296587-4-aniketmaurya@google.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
drivers/i3c/master/dw-i3c-master.c
drivers/i3c/master/dw-i3c-master.h

index fadb58ac8b68052e4a7231a644f40681c4c9afc8..8d694672c1104f8ece5428dcd3711b6b4a02ba54 100644 (file)
@@ -17,7 +17,9 @@
 #include <linux/list.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 #include <linux/slab.h>
 
 #define I3C_BUS_THIGH_MAX_NS           41
 
 #define XFER_TIMEOUT (msecs_to_jiffies(1000))
-
+#define RPM_AUTOSUSPEND_TIMEOUT 1000 /* ms */
 struct dw_i3c_cmd {
        u32 cmd_lo;
        u32 cmd_hi;
@@ -650,29 +652,38 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
        struct i3c_device_info info = { };
        int ret;
 
+       ret = pm_runtime_resume_and_get(master->dev);
+       if (ret < 0) {
+               dev_err(master->dev,
+                       "<%s> cannot resume i3c bus master, err: %d\n",
+                       __func__, ret);
+               return ret;
+       }
+
        ret = master->platform_ops->init(master);
        if (ret)
-               return ret;
+               goto rpm_out;
 
        switch (bus->mode) {
        case I3C_BUS_MODE_MIXED_FAST:
        case I3C_BUS_MODE_MIXED_LIMITED:
                ret = dw_i2c_clk_cfg(master);
                if (ret)
-                       return ret;
+                       goto rpm_out;
                fallthrough;
        case I3C_BUS_MODE_PURE:
                ret = dw_i3c_clk_cfg(master);
                if (ret)
-                       return ret;
+                       goto rpm_out;
                break;
        default:
-               return -EINVAL;
+               ret = -EINVAL;
+               goto rpm_out;
        }
 
        ret = i3c_master_get_free_addr(m, 0);
        if (ret < 0)
-               return ret;
+               goto rpm_out;
 
        writel(DEV_ADDR_DYNAMIC_ADDR_VALID | DEV_ADDR_DYNAMIC(ret),
               master->regs + DEVICE_ADDR);
@@ -682,12 +693,15 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
 
        ret = i3c_master_set_info(&master->base, &info);
        if (ret)
-               return ret;
+               goto rpm_out;
 
        dw_i3c_master_set_intr_regs(master);
        dw_i3c_master_enable(master);
 
-       return 0;
+rpm_out:
+       pm_runtime_mark_last_busy(master->dev);
+       pm_runtime_put_autosuspend(master->dev);
+       return ret;
 }
 
 static void dw_i3c_master_bus_cleanup(struct i3c_master_controller *m)
@@ -789,11 +803,21 @@ static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
        if (ccc->id == I3C_CCC_ENTDAA)
                return -EINVAL;
 
+       ret = pm_runtime_resume_and_get(master->dev);
+       if (ret < 0) {
+               dev_err(master->dev,
+                       "<%s> cannot resume i3c bus master, err: %d\n",
+                       __func__, ret);
+               return ret;
+       }
+
        if (ccc->rnw)
                ret = dw_i3c_ccc_get(master, ccc);
        else
                ret = dw_i3c_ccc_set(master, ccc);
 
+       pm_runtime_mark_last_busy(master->dev);
+       pm_runtime_put_autosuspend(master->dev);
        return ret;
 }
 
@@ -806,6 +830,14 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
        u8 p, last_addr = 0;
        int ret, pos;
 
+       ret = pm_runtime_resume_and_get(master->dev);
+       if (ret < 0) {
+               dev_err(master->dev,
+                       "<%s> cannot resume i3c bus master, err: %d\n",
+                       __func__, ret);
+               return ret;
+       }
+
        olddevs = ~(master->free_pos);
 
        /* Prepare DAT before launching DAA. */
@@ -814,8 +846,10 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
                        continue;
 
                ret = i3c_master_get_free_addr(m, last_addr + 1);
-               if (ret < 0)
-                       return -ENOSPC;
+               if (ret < 0) {
+                       ret = -ENOSPC;
+                       goto rpm_out;
+               }
 
                master->devs[pos].addr = ret;
                p = even_parity(ret);
@@ -825,16 +859,21 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
                writel(DEV_ADDR_TABLE_DYNAMIC_ADDR(ret),
                       master->regs +
                       DEV_ADDR_TABLE_LOC(master->datstartaddr, pos));
+
+               ret = 0;
        }
 
        xfer = dw_i3c_master_alloc_xfer(master, 1);
-       if (!xfer)
-               return -ENOMEM;
+       if (!xfer) {
+               ret = -ENOMEM;
+               goto rpm_out;
+       }
 
        pos = dw_i3c_master_get_free_pos(master);
        if (pos < 0) {
                dw_i3c_master_free_xfer(xfer);
-               return pos;
+               ret = pos;
+               goto rpm_out;
        }
        cmd = &xfer->cmds[0];
        cmd->cmd_hi = 0x1;
@@ -859,7 +898,10 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
 
        dw_i3c_master_free_xfer(xfer);
 
-       return 0;
+rpm_out:
+       pm_runtime_mark_last_busy(master->dev);
+       pm_runtime_put_autosuspend(master->dev);
+       return ret;
 }
 
 static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
@@ -894,6 +936,14 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
        if (!xfer)
                return -ENOMEM;
 
+       ret = pm_runtime_resume_and_get(master->dev);
+       if (ret < 0) {
+               dev_err(master->dev,
+                       "<%s> cannot resume i3c bus master, err: %d\n",
+                       __func__, ret);
+               return ret;
+       }
+
        for (i = 0; i < i3c_nxfers; i++) {
                struct dw_i3c_cmd *cmd = &xfer->cmds[i];
 
@@ -935,6 +985,8 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
        ret = xfer->ret;
        dw_i3c_master_free_xfer(xfer);
 
+       pm_runtime_mark_last_busy(master->dev);
+       pm_runtime_put_autosuspend(master->dev);
        return ret;
 }
 
@@ -1045,6 +1097,14 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
        if (!xfer)
                return -ENOMEM;
 
+       ret = pm_runtime_resume_and_get(master->dev);
+       if (ret < 0) {
+               dev_err(master->dev,
+                       "<%s> cannot resume i3c bus master, err: %d\n",
+                       __func__, ret);
+               return ret;
+       }
+
        for (i = 0; i < i2c_nxfers; i++) {
                struct dw_i3c_cmd *cmd = &xfer->cmds[i];
 
@@ -1075,6 +1135,8 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
        ret = xfer->ret;
        dw_i3c_master_free_xfer(xfer);
 
+       pm_runtime_mark_last_busy(master->dev);
+       pm_runtime_put_autosuspend(master->dev);
        return ret;
 }
 
@@ -1217,6 +1279,15 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master,
 static int dw_i3c_master_enable_hotjoin(struct i3c_master_controller *m)
 {
        struct dw_i3c_master *master = to_dw_i3c_master(m);
+       int ret;
+
+       ret = pm_runtime_resume_and_get(master->dev);
+       if (ret < 0) {
+               dev_err(master->dev,
+                       "<%s> cannot resume i3c bus master, err: %d\n",
+                       __func__, ret);
+               return ret;
+       }
 
        dw_i3c_master_enable_sir_signal(master, true);
        writel(readl(master->regs + DEVICE_CTRL) & ~DEV_CTRL_HOT_JOIN_NACK,
@@ -1232,6 +1303,8 @@ static int dw_i3c_master_disable_hotjoin(struct i3c_master_controller *m)
        writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_HOT_JOIN_NACK,
               master->regs + DEVICE_CTRL);
 
+       pm_runtime_mark_last_busy(master->dev);
+       pm_runtime_put_autosuspend(master->dev);
        return 0;
 }
 
@@ -1242,12 +1315,23 @@ static int dw_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
        struct dw_i3c_master *master = to_dw_i3c_master(m);
        int rc;
 
+       rc = pm_runtime_resume_and_get(master->dev);
+       if (rc < 0) {
+               dev_err(master->dev,
+                       "<%s> cannot resume i3c bus master, err: %d\n",
+                       __func__, rc);
+               return rc;
+       }
+
        dw_i3c_master_set_sir_enabled(master, dev, data->index, true);
 
        rc = i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
 
-       if (rc)
+       if (rc) {
                dw_i3c_master_set_sir_enabled(master, dev, data->index, false);
+               pm_runtime_mark_last_busy(master->dev);
+               pm_runtime_put_autosuspend(master->dev);
+       }
 
        return rc;
 }
@@ -1265,6 +1349,8 @@ static int dw_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
 
        dw_i3c_master_set_sir_enabled(master, dev, data->index, false);
 
+       pm_runtime_mark_last_busy(master->dev);
+       pm_runtime_put_autosuspend(master->dev);
        return 0;
 }
 
@@ -1465,6 +1551,8 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
        if (!master->platform_ops)
                master->platform_ops = &dw_i3c_platform_ops_default;
 
+       master->dev = &pdev->dev;
+
        master->regs = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(master->regs))
                return PTR_ERR(master->regs);
@@ -1497,6 +1585,11 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
 
        platform_set_drvdata(pdev, master);
 
+       pm_runtime_set_autosuspend_delay(&pdev->dev, RPM_AUTOSUSPEND_TIMEOUT);
+       pm_runtime_use_autosuspend(&pdev->dev);
+       pm_runtime_set_active(&pdev->dev);
+       pm_runtime_enable(&pdev->dev);
+
        /* Information regarding the FIFOs/QUEUEs depth */
        ret = readl(master->regs + QUEUE_STATUS_LEVEL);
        master->caps.cmdfifodepth = QUEUE_STATUS_LEVEL_CMD(ret);
@@ -1513,10 +1606,15 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
        ret = i3c_master_register(&master->base, &pdev->dev,
                                  &dw_mipi_i3c_ops, false);
        if (ret)
-               goto err_assert_rst;
+               goto err_disable_pm;
 
        return 0;
 
+err_disable_pm:
+       pm_runtime_disable(&pdev->dev);
+       pm_runtime_set_suspended(&pdev->dev);
+       pm_runtime_dont_use_autosuspend(&pdev->dev);
+
 err_assert_rst:
        reset_control_assert(master->core_rst);
 
@@ -1528,7 +1626,9 @@ void dw_i3c_common_remove(struct dw_i3c_master *master)
 {
        i3c_master_unregister(&master->base);
 
-       reset_control_assert(master->core_rst);
+       pm_runtime_disable(master->dev);
+       pm_runtime_set_suspended(master->dev);
+       pm_runtime_dont_use_autosuspend(master->dev);
 }
 EXPORT_SYMBOL_GPL(dw_i3c_common_remove);
 
@@ -1552,6 +1652,96 @@ static void dw_i3c_remove(struct platform_device *pdev)
        dw_i3c_common_remove(master);
 }
 
+static void dw_i3c_master_restore_addrs(struct dw_i3c_master *master)
+{
+       u32 pos, reg_val;
+
+       writel(DEV_ADDR_DYNAMIC_ADDR_VALID | DEV_ADDR_DYNAMIC(master->dev_addr),
+              master->regs + DEVICE_ADDR);
+
+       for (pos = 0; pos < master->maxdevs; pos++) {
+               if (master->free_pos & BIT(pos))
+                       continue;
+
+               if (master->devs[pos].is_i2c_addr)
+                       reg_val = DEV_ADDR_TABLE_LEGACY_I2C_DEV |
+                              DEV_ADDR_TABLE_STATIC_ADDR(master->devs[pos].addr);
+               else
+                       reg_val = DEV_ADDR_TABLE_DYNAMIC_ADDR(master->devs[pos].addr);
+
+               writel(reg_val, master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr, pos));
+       }
+}
+
+static void dw_i3c_master_restore_timing_regs(struct dw_i3c_master *master)
+{
+       writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING);
+       writel(master->bus_free_timing, master->regs + BUS_FREE_TIMING);
+       writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING);
+       writel(master->ext_lcnt_timing, master->regs + SCL_EXT_LCNT_TIMING);
+
+       if (master->i2c_slv_prsnt) {
+               writel(master->i2c_fmp_timing, master->regs + SCL_I2C_FMP_TIMING);
+               writel(master->i2c_fm_timing, master->regs + SCL_I2C_FM_TIMING);
+       }
+}
+
+static int dw_i3c_master_enable_clks(struct dw_i3c_master *master)
+{
+       int ret = 0;
+
+       ret = clk_prepare_enable(master->core_clk);
+       if (ret)
+               return ret;
+
+       ret = clk_prepare_enable(master->pclk);
+       if (ret) {
+               clk_disable_unprepare(master->core_clk);
+               return ret;
+       }
+
+       return 0;
+}
+
+static inline void dw_i3c_master_disable_clks(struct dw_i3c_master *master)
+{
+       clk_disable_unprepare(master->pclk);
+       clk_disable_unprepare(master->core_clk);
+}
+
+static int __maybe_unused dw_i3c_master_runtime_suspend(struct device *dev)
+{
+       struct dw_i3c_master *master = dev_get_drvdata(dev);
+
+       dw_i3c_master_disable(master);
+
+       reset_control_assert(master->core_rst);
+       dw_i3c_master_disable_clks(master);
+       pinctrl_pm_select_sleep_state(dev);
+       return 0;
+}
+
+static int __maybe_unused dw_i3c_master_runtime_resume(struct device *dev)
+{
+       struct dw_i3c_master *master = dev_get_drvdata(dev);
+
+       pinctrl_pm_select_default_state(dev);
+       dw_i3c_master_enable_clks(master);
+       reset_control_deassert(master->core_rst);
+
+       dw_i3c_master_set_intr_regs(master);
+       dw_i3c_master_restore_timing_regs(master);
+       dw_i3c_master_restore_addrs(master);
+
+       dw_i3c_master_enable(master);
+       return 0;
+}
+
+static const struct dev_pm_ops dw_i3c_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+       SET_RUNTIME_PM_OPS(dw_i3c_master_runtime_suspend, dw_i3c_master_runtime_resume, NULL)
+};
+
 static const struct of_device_id dw_i3c_master_of_match[] = {
        { .compatible = "snps,dw-i3c-master-1.00a", },
        {},
@@ -1564,6 +1754,7 @@ static struct platform_driver dw_i3c_driver = {
        .driver = {
                .name = "dw-i3c-master",
                .of_match_table = dw_i3c_master_of_match,
+               .pm = &dw_i3c_pm_ops,
        },
 };
 module_platform_driver(dw_i3c_driver);
index f23e9d5aca8606ec478597f7a42b6c0ba80d7d11..219ff815d3a7349d274fc8e7f2704cbcad07e968 100644 (file)
@@ -25,6 +25,7 @@ struct dw_i3c_dat_entry {
 
 struct dw_i3c_master {
        struct i3c_master_controller base;
+       struct device *dev;
        u16 maxdevs;
        u16 datstartaddr;
        u32 free_pos;