diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index d7b89508311f..14eb7e802bc8 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -1050,6 +1050,7 @@ config I2C_PXA_SLAVE config I2C_QCOM_CCI tristate "Qualcomm Camera Control Interface" depends on ARCH_QCOM || COMPILE_TEST + select PM_OPP help If you say yes to this option, support will be included for the built-in camera control interface on the Qualcomm SoCs. diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c index a9287063c245..d692f72c50a1 100644 --- a/drivers/i2c/busses/i2c-qcom-cci.c +++ b/drivers/i2c/busses/i2c-qcom-cci.c @@ -11,6 +11,7 @@ #include #include #include +#include #define CCI_HW_VERSION 0x0 #define CCI_RESET_CMD 0x004 @@ -318,7 +319,7 @@ static const struct hw_params *cci_get_hw_params(struct cci *cci, int mode) unsigned long rate = clk_get_rate(cci->cci_clk); int ri = cci_clk_rate_idx(rate); - if (ri >= 0 && mode <= cci->data->max_mode && cci_hw_params[ri][mode].thigh) + if (ri >= 0 && mode <= cci->data->max_mode) return &cci_hw_params[ri][mode]; return NULL; @@ -575,6 +576,66 @@ static void cci_disable_clocks(struct cci *cci) clk_bulk_disable_unprepare(cci->nclocks, cci->clocks); } +/* + * The single CCI clock is shared by all masters, which may run in different + * modes. Pick the lowest rate that has a valid timing set for every active + * master's mode. + */ +static int cci_get_required_rate(struct cci *cci, unsigned long *rate) +{ + for (int ri = 0; ri < NUM_CCI_CLK_RATES; ri++) { + bool supported = true; + + for (int i = 0; i < cci->data->num_masters; i++) { + int mode = cci->master[i].mode; + + if (!cci->master[i].cci) + continue; + + if (mode > cci->data->max_mode || + !cci_hw_params[ri][mode].thigh) { + supported = false; + break; + } + } + + if (supported) { + *rate = cci_clk_rates[ri]; + return 0; + } + } + + return -EOPNOTSUPP; +} + +static int cci_set_core_rate(struct cci *cci) +{ + struct device *dev = cci->dev; + unsigned long rate; + int ret; + + ret = cci_get_required_rate(cci, &rate); + if (ret) { + dev_err(dev, "no CCI clock rate satisfies all masters\n"); + return ret; + } + + ret = dev_pm_opp_set_rate(dev, rate); + if (ret) { + dev_err(dev, "CCI clock could not be set to %lu Hz\n", rate); + return ret; + } + + /* + * Sanity: The hw_params timings are only valid at the exact + * expected rate, verify what landed on the hardware. + */ + if (clk_get_rate(cci->cci_clk) != rate) + dev_warn(dev, "CCI clock is not at expected %lu Hz\n", rate); + + return 0; +} + static int __maybe_unused cci_suspend_runtime(struct device *dev) { struct cci *cci = dev_get_drvdata(dev); @@ -678,6 +739,19 @@ static int cci_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(cci->cci_clk), "failed to get CCI clock\n"); + ret = devm_pm_opp_set_clkname(dev, "cci"); + if (ret) + return dev_err_probe(dev, ret, "failed to set CCI OPP clk\n"); + + /* OPP table is optional */ + ret = devm_pm_opp_of_add_table(dev); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to add OPP table\n"); + + ret = cci_set_core_rate(cci); + if (ret) + return ret; + ret = cci_enable_clocks(cci); if (ret < 0) return ret;