mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
i2c: qcom-cci: Enforce the required CCI clock rate
The CCI hw_params timing values are only valid at the specific clock rate they were calibrated for. A previous change made the driver select the timing set matching the currently running clock rate, but the rate itself was still left to the DT (assigned-clock-rates) or the bootloader, which is fragile: if no rate is enforced the timings may not match and violate the I2C specification. Actively drive the CCI clock to the rate required by the configured modes. The single CCI clock is shared by all masters, which may run in different modes, so cci_get_required_rate() picks the lowest rate that has a valid timing set for every active master's mode. This avoids clocking the bus faster than necessary while still satisfying every master (e.g. a Fast+ master forces 37.5 MHz). Apply the rate through the OPP framework so that boards describing an opp table also get the required power-domain/regulator votes for that rate. Boards without an OPP table simply fall back to plain clk_set_rate() behavior, so existing DTs keep working. Drop the now-redundant hw_params[].thigh check in cci_get_hw_params(), cci_get_required_rate() already rejects rates lacking a valid timing set for an active master's mode. Suggested-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com> Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> Signed-off-by: Andi Shyti <andi.shyti@kernel.org> Link: https://patch.msgid.link/20260929-cci-clk-fix-v6-5-62a268cccb0f@oss.qualcomm.com
This commit is contained in:
parent
826a1d134c
commit
fa1eb3af12
|
|
@ -1050,6 +1050,7 @@ config I2C_PXA_SLAVE
|
||||||
config I2C_QCOM_CCI
|
config I2C_QCOM_CCI
|
||||||
tristate "Qualcomm Camera Control Interface"
|
tristate "Qualcomm Camera Control Interface"
|
||||||
depends on ARCH_QCOM || COMPILE_TEST
|
depends on ARCH_QCOM || COMPILE_TEST
|
||||||
|
select PM_OPP
|
||||||
help
|
help
|
||||||
If you say yes to this option, support will be included for the
|
If you say yes to this option, support will be included for the
|
||||||
built-in camera control interface on the Qualcomm SoCs.
|
built-in camera control interface on the Qualcomm SoCs.
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/pm_runtime.h>
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/pm_opp.h>
|
||||||
|
|
||||||
#define CCI_HW_VERSION 0x0
|
#define CCI_HW_VERSION 0x0
|
||||||
#define CCI_RESET_CMD 0x004
|
#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);
|
unsigned long rate = clk_get_rate(cci->cci_clk);
|
||||||
int ri = cci_clk_rate_idx(rate);
|
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 &cci_hw_params[ri][mode];
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -575,6 +576,66 @@ static void cci_disable_clocks(struct cci *cci)
|
||||||
clk_bulk_disable_unprepare(cci->nclocks, cci->clocks);
|
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)
|
static int __maybe_unused cci_suspend_runtime(struct device *dev)
|
||||||
{
|
{
|
||||||
struct cci *cci = dev_get_drvdata(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),
|
return dev_err_probe(dev, PTR_ERR(cci->cci_clk),
|
||||||
"failed to get CCI clock\n");
|
"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);
|
ret = cci_enable_clocks(cci);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user