]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
i2c: qup: Vote for interconnect bandwidth to DRAM
authorStephan Gerhold <stephan.gerhold@kernkonzept.com>
Tue, 28 Nov 2023 09:48:37 +0000 (10:48 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 29 May 2025 09:12:34 +0000 (11:12 +0200)
[ Upstream commit d4f35233a6345f62637463ef6e0708f44ffaa583 ]

When the I2C QUP controller is used together with a DMA engine it needs
to vote for the interconnect path to the DRAM. Otherwise it may be
unable to access the memory quickly enough.

The requested peak bandwidth is dependent on the I2C core clock.

To avoid sending votes too often the bandwidth is always requested when
a DMA transfer starts, but dropped only on runtime suspend. Runtime
suspend should only happen if no transfer is active. After resumption we
can defer the next vote until the first DMA transfer actually happens.

The implementation is largely identical to the one introduced for
spi-qup in commit ecdaa9473019 ("spi: qup: Vote for interconnect
bandwidth to DRAM") since both drivers represent the same hardware
block.

Signed-off-by: Stephan Gerhold <stephan.gerhold@kernkonzept.com>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20231128-i2c-qup-dvfs-v1-3-59a0e3039111@kernkonzept.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/i2c/busses/i2c-qup.c

index da20b4487c9a54be137251a6f0e36a12a4e3088b..3a36d682ed5726d7a956180ea2072dc46d2f37d6 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/err.h>
 #include <linux/i2c.h>
+#include <linux/interconnect.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/module.h>
 /* TAG length for DATA READ in RX FIFO  */
 #define READ_RX_TAGS_LEN               2
 
+#define QUP_BUS_WIDTH                  8
+
 static unsigned int scl_freq;
 module_param_named(scl_freq, scl_freq, uint, 0444);
 MODULE_PARM_DESC(scl_freq, "SCL frequency override");
@@ -227,6 +230,7 @@ struct qup_i2c_dev {
        int                     irq;
        struct clk              *clk;
        struct clk              *pclk;
+       struct icc_path         *icc_path;
        struct i2c_adapter      adap;
 
        int                     clk_ctl;
@@ -255,6 +259,10 @@ struct qup_i2c_dev {
        /* To configure when bus is in run state */
        u32                     config_run;
 
+       /* bandwidth votes */
+       u32                     src_clk_freq;
+       u32                     cur_bw_clk_freq;
+
        /* dma parameters */
        bool                    is_dma;
        /* To check if the current transfer is using DMA */
@@ -453,6 +461,23 @@ static int qup_i2c_bus_active(struct qup_i2c_dev *qup, int len)
        return ret;
 }
 
+static int qup_i2c_vote_bw(struct qup_i2c_dev *qup, u32 clk_freq)
+{
+       u32 needed_peak_bw;
+       int ret;
+
+       if (qup->cur_bw_clk_freq == clk_freq)
+               return 0;
+
+       needed_peak_bw = Bps_to_icc(clk_freq * QUP_BUS_WIDTH);
+       ret = icc_set_bw(qup->icc_path, 0, needed_peak_bw);
+       if (ret)
+               return ret;
+
+       qup->cur_bw_clk_freq = clk_freq;
+       return 0;
+}
+
 static void qup_i2c_write_tx_fifo_v1(struct qup_i2c_dev *qup)
 {
        struct qup_i2c_block *blk = &qup->blk;
@@ -838,6 +863,10 @@ static int qup_i2c_bam_xfer(struct i2c_adapter *adap, struct i2c_msg *msg,
        int ret = 0;
        int idx = 0;
 
+       ret = qup_i2c_vote_bw(qup, qup->src_clk_freq);
+       if (ret)
+               return ret;
+
        enable_irq(qup->irq);
        ret = qup_i2c_req_dma(qup);
 
@@ -1643,6 +1672,7 @@ static void qup_i2c_disable_clocks(struct qup_i2c_dev *qup)
        config = readl(qup->base + QUP_CONFIG);
        config |= QUP_CLOCK_AUTO_GATE;
        writel(config, qup->base + QUP_CONFIG);
+       qup_i2c_vote_bw(qup, 0);
        clk_disable_unprepare(qup->pclk);
 }
 
@@ -1743,6 +1773,11 @@ static int qup_i2c_probe(struct platform_device *pdev)
                        goto fail_dma;
                }
                qup->is_dma = true;
+
+               qup->icc_path = devm_of_icc_get(&pdev->dev, NULL);
+               if (IS_ERR(qup->icc_path))
+                       return dev_err_probe(&pdev->dev, PTR_ERR(qup->icc_path),
+                                            "failed to get interconnect path\n");
        }
 
 nodma:
@@ -1791,6 +1826,7 @@ nodma:
                qup_i2c_enable_clocks(qup);
                src_clk_freq = clk_get_rate(qup->clk);
        }
+       qup->src_clk_freq = src_clk_freq;
 
        /*
         * Bootloaders might leave a pending interrupt on certain QUP's,