* [PATCH v2 2/4] power: supply: max17042_battery: Initialize MAX17055 from battery info
From: Vincent Cloutier @ 2026-07-20 1:11 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
Conor Dooley, linux-pm, devicetree, linux-kernel,
Vincent Cloutier
In-Reply-To: <20260720011144.1280219-1-vincent.cloutier@icloud.com>
From: Vincent Cloutier <vincent@cloutier.co>
MAX17055 can use charge-full-design-microamp-hours and
charge-term-current-microamp from monitored-battery for its POR
configuration. Prepare quantized DesignCap, IChgTerm, and EZ Config dQAcc
values in the power-supply registration callback after the core has parsed
battery information.
Validate all supplied values before changing the sparse configuration.
Reject zero, values that quantize to zero, capacities that cannot produce
dQAcc, and positive termination currents outside the signed register range.
Limit this path to MAX17055 because the other supported gauges require
complete characterization data.
Follow the documented POR sequence. Wait for FSTAT.DNR, save HibCfg, exit
hibernate, write stable battery registers once and verify them after 1 ms,
issue command writes directly, poll until refresh clears, and restore
HibCfg. Clear POR only after every step succeeds.
Keep driver-backed properties unavailable while initialization is
incomplete. If an I/O error interrupts the transaction, restore hibernate
before the next attempt and retry periodically so a transient startup
failure does not become permanent.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/max17042_battery.c | 335 +++++++++++++++++++++---
include/linux/power/max17042_battery.h | 2 +
2 files changed, 306 insertions(+), 31 deletions(-)
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index d409d2f0d383..b25bf26135e0 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -62,18 +62,30 @@
#define MAX17042_RESISTANCE_LSB 1 / 4096 /* Ω */
#define MAX17042_TEMPERATURE_LSB 1 / 256 /* °C */
+#define MAX17055_DQACC_DIV 32
+#define MAX17055_DPACC_FACTOR 44138
+#define MAX17055_DPACC_VCHG_FACTOR 51200
+#define MAX17055_FSTAT_DNR_BIT BIT(0)
+#define MAX17055_DNR_POLL_US 10000
+#define MAX17055_DNR_TIMEOUT_US 2000000
+#define MAX17055_INIT_RETRY_DELAY_MS 10000
+#define MAX17055_REFRESH_POLL_US 10000
+#define MAX17055_REFRESH_TIMEOUT_US 1000000
+
struct max17042_chip {
struct device *dev;
struct regmap *regmap;
struct power_supply *battery;
enum max170xx_chip_type chip_type;
struct max17042_config_data *config_data;
- struct work_struct work;
- int init_complete;
+ struct delayed_work work;
int irq;
int task_period;
bool enable_current_sense;
bool enable_por_init;
+ bool init_complete;
+ bool hib_restore_pending;
+ u16 hib_cfg;
unsigned int r_sns;
int vmin; /* in millivolts */
int vmax; /* in millivolts */
@@ -256,7 +268,7 @@ static int max17042_get_property(struct power_supply *psy,
u32 data;
u64 data64;
- if (!chip->init_complete)
+ if (!READ_ONCE(chip->init_complete))
return -EAGAIN;
switch (psp) {
@@ -566,6 +578,24 @@ static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value)
return ret;
}
+static int max17055_write_verify_reg(struct regmap *map, u8 reg, u32 value)
+{
+ u32 read_value;
+ int ret;
+
+ ret = regmap_write(map, reg, value);
+ if (ret)
+ return ret;
+
+ usleep_range(1000, 2000);
+
+ ret = regmap_read(map, reg, &read_value);
+ if (ret)
+ return ret;
+
+ return read_value == value ? 0 : -EIO;
+}
+
static inline void max17042_override_por(struct regmap *map,
u8 reg, u16 value)
{
@@ -801,8 +831,12 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17042_CONFIG, config->config);
max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer);
- max17042_override_por(map, MAX17042_DesignCap, config->design_cap);
- max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term);
+ if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) {
+ max17042_override_por(map, MAX17042_DesignCap,
+ config->design_cap);
+ max17042_override_por(map, MAX17042_ICHGTerm,
+ config->ichgt_term);
+ }
max17042_override_por(map, MAX17042_AtRate, config->at_rate);
max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg);
@@ -812,8 +846,10 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17042_FullCAP, config->fullcap);
max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom);
- max17042_override_por(map, MAX17042_dQacc, config->dqacc);
- max17042_override_por(map, MAX17042_dPacc, config->dpacc);
+ if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) {
+ max17042_override_por(map, MAX17042_dQacc, config->dqacc);
+ max17042_override_por(map, MAX17042_dPacc, config->dpacc);
+ }
max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0);
max17042_override_por(map, MAX17042_TempCo, config->tcompc0);
@@ -847,26 +883,162 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
}
-static int max17042_init_chip(struct max17042_chip *chip)
+static int max17055_override_battery_values(struct max17042_chip *chip)
{
+ struct max17042_config_data *config = chip->config_data;
struct regmap *map = chip->regmap;
+ unsigned int design_cap;
+ unsigned int model_cfg;
+ unsigned int dqacc;
+ u64 dpacc;
int ret;
+ if (config->design_cap) {
+ ret = max17055_write_verify_reg(map, MAX17042_DesignCap,
+ config->design_cap);
+ if (ret)
+ return ret;
+ }
+
+ if (config->dqacc) {
+ ret = max17055_write_verify_reg(map, MAX17042_dQacc,
+ config->dqacc);
+ if (ret)
+ return ret;
+ }
+
+ if (config->ichgt_term) {
+ ret = max17055_write_verify_reg(map, MAX17042_ICHGTerm,
+ config->ichgt_term);
+ if (ret)
+ return ret;
+ }
+
+ if (!config->design_cap && !config->dqacc)
+ return 0;
+
+ ret = regmap_read(map, MAX17042_DesignCap, &design_cap);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, MAX17042_dQacc, &dqacc);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, MAX17055_ModelCfg, &model_cfg);
+ if (ret)
+ return ret;
+
+ if (!design_cap || !dqacc)
+ return -ERANGE;
+
+ dpacc = (u64)dqacc *
+ (model_cfg & MAX17055_MODELCFG_VCHG_BIT ?
+ MAX17055_DPACC_VCHG_FACTOR : MAX17055_DPACC_FACTOR);
+ do_div(dpacc, design_cap);
+ if (dpacc > U16_MAX)
+ return -ERANGE;
+
+ return max17055_write_verify_reg(map, MAX17042_dPacc, (u16)dpacc);
+}
+
+static int max17055_restore_hibernate(struct max17042_chip *chip)
+{
+ int restore_hib_ret;
+ int soft_wakeup_ret;
+
+ soft_wakeup_ret = regmap_write(chip->regmap, MAX17055_SoftWakeup, 0);
+ restore_hib_ret = max17055_write_verify_reg(chip->regmap,
+ MAX17055_HibCfg,
+ chip->hib_cfg);
+ if (!soft_wakeup_ret && !restore_hib_ret)
+ chip->hib_restore_pending = false;
+
+ return soft_wakeup_ret ?: restore_hib_ret;
+}
+
+static int max17055_init_chip(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+ unsigned int hib_cfg;
+ unsigned int model_cfg;
+ unsigned int fstat;
+ int restore_ret;
+ int ret;
+
+ if (chip->hib_restore_pending) {
+ ret = max17055_restore_hibernate(chip);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_read_poll_timeout(map, MAX17042_FSTAT, fstat,
+ !(fstat & MAX17055_FSTAT_DNR_BIT),
+ MAX17055_DNR_POLL_US,
+ MAX17055_DNR_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, MAX17055_HibCfg, &hib_cfg);
+ if (ret)
+ return ret;
+
+ chip->hib_cfg = hib_cfg;
+ chip->hib_restore_pending = true;
+
+ ret = regmap_write(map, MAX17055_SoftWakeup, 0x0090);
+ if (ret)
+ goto restore_hibernate;
+
+ ret = max17055_write_verify_reg(map, MAX17055_HibCfg, 0);
+ if (ret)
+ goto restore_hibernate;
+
+ ret = regmap_write(map, MAX17055_SoftWakeup, 0);
+ if (ret)
+ goto restore_hibernate;
+
max17042_override_por_values(chip);
+ ret = max17055_override_battery_values(chip);
+ if (ret)
+ goto restore_hibernate;
+
+ ret = regmap_write_bits(map, MAX17055_ModelCfg,
+ MAX17055_MODELCFG_REFRESH_BIT,
+ MAX17055_MODELCFG_REFRESH_BIT);
+ if (ret)
+ goto restore_hibernate;
+
+ ret = regmap_read_poll_timeout(map, MAX17055_ModelCfg, model_cfg,
+ !(model_cfg &
+ MAX17055_MODELCFG_REFRESH_BIT),
+ MAX17055_REFRESH_POLL_US,
+ MAX17055_REFRESH_TIMEOUT_US);
+
+restore_hibernate:
+ restore_ret = max17055_restore_hibernate(chip);
+ if (restore_ret)
+ return restore_ret;
+
+ return ret;
+}
+
+static int max17042_init_chip(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+ int ret;
+
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
- regmap_write_bits(map, MAX17055_ModelCfg,
- MAX17055_MODELCFG_REFRESH_BIT,
- MAX17055_MODELCFG_REFRESH_BIT);
- }
+ ret = max17055_init_chip(chip);
+ if (ret)
+ return ret;
+ } else {
+ max17042_override_por_values(chip);
- /* After Power up, the MAX17042 requires 500mS in order
- * to perform signal debouncing and initial SOC reporting
- */
- msleep(500);
+ /* Allow signal debouncing and initial SOC reporting. */
+ msleep(500);
- if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) {
- /* Initialize configuration */
max17042_write_config_regs(chip);
/* write cell characterization data */
@@ -902,8 +1074,7 @@ static int max17042_init_chip(struct max17042_chip *chip)
}
/* Init complete, Clear the POR bit */
- regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0);
- return 0;
+ return regmap_clear_bits(map, MAX17042_STATUS, STATUS_POR_BIT);
}
static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off)
@@ -989,18 +1160,28 @@ static irqreturn_t max17042_thread_handler(int id, void *dev)
static void max17042_init_worker(struct work_struct *work)
{
- struct max17042_chip *chip = container_of(work,
+ struct max17042_chip *chip = container_of(to_delayed_work(work),
struct max17042_chip, work);
- int ret;
+ int ret = 0;
/* Initialize registers according to values from config_data */
- if (chip->enable_por_init && chip->config_data) {
+ if (chip->enable_por_init && chip->config_data)
ret = max17042_init_chip(chip);
- if (ret)
- return;
+
+ if (ret) {
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
+ dev_warn_ratelimited(chip->dev,
+ "initialization failed: %d, retrying\n", ret);
+ queue_delayed_work(system_freezable_wq, &chip->work,
+ msecs_to_jiffies(MAX17055_INIT_RETRY_DELAY_MS));
+ } else {
+ dev_err(chip->dev, "initialization failed: %d\n", ret);
+ }
+ return;
}
- chip->init_complete = 1;
+ WRITE_ONCE(chip->init_complete, true);
+ power_supply_changed(chip->battery);
}
#ifdef CONFIG_OF
@@ -1060,6 +1241,93 @@ static int max17042_init_defaults(struct max17042_chip *chip)
return 0;
}
+static int max17042_apply_battery_properties(struct max17042_chip *chip,
+ struct power_supply_battery_info *info)
+{
+ struct max17042_config_data *config;
+ struct device *dev = chip->dev;
+ bool have_design_cap;
+ bool have_ichgt_term;
+ u16 design_cap = 0;
+ u16 ichgt_term = 0;
+ u16 dqacc = 0;
+ u64 data64;
+
+ if (!info || chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055)
+ return 0;
+
+ have_design_cap = chip->enable_current_sense &&
+ info->charge_full_design_uah >= 0;
+ have_ichgt_term = chip->enable_current_sense &&
+ info->charge_term_current_ua >= 0;
+ if (!have_design_cap && !have_ichgt_term)
+ return 0;
+
+ if (have_design_cap) {
+ if (!info->charge_full_design_uah)
+ return dev_err_probe(dev, -EINVAL,
+ "battery design capacity must be positive\n");
+
+ data64 = (u64)info->charge_full_design_uah * chip->r_sns;
+ do_div(data64, MAX17042_CAPACITY_LSB);
+ if (!data64)
+ return dev_err_probe(dev, -ERANGE,
+ "battery design capacity is too small for sense resistor\n");
+ if (data64 > U16_MAX)
+ return dev_err_probe(dev, -ERANGE,
+ "battery design capacity exceeds register range\n");
+
+ design_cap = (u16)data64;
+ dqacc = design_cap / MAX17055_DQACC_DIV;
+ if (!dqacc)
+ return dev_err_probe(dev, -ERANGE,
+ "battery design capacity is too small for EZ config\n");
+ }
+
+ if (have_ichgt_term) {
+ if (!info->charge_term_current_ua)
+ return dev_err_probe(dev, -EINVAL,
+ "charge termination current must be positive\n");
+
+ data64 = (u64)info->charge_term_current_ua * chip->r_sns;
+ do_div(data64, MAX17042_CURRENT_LSB);
+ if (!data64)
+ return dev_err_probe(dev, -ERANGE,
+ "charge termination current is too small for sense resistor\n");
+ if (data64 > S16_MAX)
+ return dev_err_probe(dev, -ERANGE,
+ "charge termination current exceeds positive register range\n");
+
+ ichgt_term = (u16)data64;
+ }
+
+ config = chip->config_data;
+ if (!config) {
+ config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+ }
+
+ if (have_design_cap) {
+ config->design_cap = design_cap;
+ config->dqacc = dqacc;
+ }
+ if (have_ichgt_term)
+ config->ichgt_term = ichgt_term;
+
+ chip->config_data = config;
+ chip->enable_por_init = true;
+
+ return 0;
+}
+
+static int max17042_init_battery(struct power_supply *psy)
+{
+ struct max17042_chip *chip = power_supply_get_drvdata(psy);
+
+ return max17042_apply_battery_properties(chip, psy->battery_info);
+}
+
static const struct regmap_config max17042_regmap_config = {
.name = "max17042",
.reg_bits = 8,
@@ -1113,6 +1381,7 @@ static const struct power_supply_desc max17042_psy_desc = {
.set_property = max17042_set_property,
.property_is_writeable = max17042_property_is_writeable,
.external_power_changed = power_supply_changed,
+ .init = max17042_init_battery,
.properties = max17042_battery_props,
.num_properties = ARRAY_SIZE(max17042_battery_props),
};
@@ -1123,6 +1392,7 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = {
.get_property = max17042_get_property,
.set_property = max17042_set_property,
.property_is_writeable = max17042_property_is_writeable,
+ .init = max17042_init_battery,
.properties = max17042_battery_props,
.num_properties = ARRAY_SIZE(max17042_battery_props) - 2,
};
@@ -1242,15 +1512,18 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
chip->irq = irq;
- regmap_read(chip->regmap, MAX17042_STATUS, &val);
+ ret = regmap_read(chip->regmap, MAX17042_STATUS, &val);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read status\n");
+
if (val & STATUS_POR_BIT) {
- ret = devm_work_autocancel(dev, &chip->work,
- max17042_init_worker);
+ ret = devm_delayed_work_autocancel(dev, &chip->work,
+ max17042_init_worker);
if (ret)
return ret;
- schedule_work(&chip->work);
+ queue_delayed_work(system_freezable_wq, &chip->work, 0);
} else {
- chip->init_complete = 1;
+ WRITE_ONCE(chip->init_complete, true);
}
return 0;
diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h
index 13aeab1597c6..810e068eafd2 100644
--- a/include/linux/power/max17042_battery.h
+++ b/include/linux/power/max17042_battery.h
@@ -25,6 +25,7 @@
#define MAX17042_CHARACTERIZATION_DATA_SIZE 48
#define MAX17055_MODELCFG_REFRESH_BIT BIT(15)
+#define MAX17055_MODELCFG_VCHG_BIT BIT(10)
enum max17042_register {
MAX17042_STATUS = 0x00,
@@ -124,6 +125,7 @@ enum max17055_register {
MAX17055_ConvgCfg = 0x49,
MAX17055_VFRemCap = 0x4A,
+ MAX17055_SoftWakeup = 0x60,
MAX17055_STATUS2 = 0xB0,
MAX17055_POWER = 0xB1,
--
2.55.0
^ permalink raw reply related
* [PATCH v2 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery
From: Vincent Cloutier @ 2026-07-20 1:11 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
Conor Dooley, linux-pm, devicetree, linux-kernel,
Vincent Cloutier
In-Reply-To: <20260720011144.1280219-1-vincent.cloutier@icloud.com>
From: Vincent Cloutier <vincent@cloutier.co>
The MAX17042 family binding references the generic power-supply schema,
but its additionalProperties restriction means generic properties must
also be admitted explicitly.
Allow monitored-battery so these fuel gauges can reference the physical
battery whose characteristics they consume.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
.../devicetree/bindings/power/supply/maxim,max17042.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
index 242b33f2bcba..ace684159e0e 100644
--- a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
+++ b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
@@ -67,6 +67,8 @@ properties:
Voltage threshold to report battery as over voltage (in mV).
Default is not to report over-voltage events.
+ monitored-battery: true
+
power-supplies: true
required:
--
2.55.0
^ permalink raw reply related
* [PATCH v2 3/4] power: supply: max17042_battery: Honor MAX17055 charge voltage
From: Vincent Cloutier @ 2026-07-20 1:11 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
Conor Dooley, linux-pm, devicetree, linux-kernel,
Vincent Cloutier
In-Reply-To: <20260720011144.1280219-1-vincent.cloutier@icloud.com>
From: Vincent Cloutier <vincent@cloutier.co>
MAX17055 ModelCfg.VChg selects the charge-voltage range used by EZ Config.
Consume voltage-max-design-microvolt from monitored-battery and set VChg
only when the value is strictly greater than 4.275 V. Reject an explicit
zero voltage and preserve the register when the property is absent.
Update only the VChg bit and verify its read-back so unrelated ModelCfg
fields remain intact. Treat a VChg-only override as an accumulator change:
read effective DesignCap and dQAcc, derive and verify the matching dPAcc,
then request model refresh. This keeps voltage-only firmware descriptions
coherent with the selected charge-voltage range.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/max17042_battery.c | 42 +++++++++++++++++++++++--
1 file changed, 39 insertions(+), 3 deletions(-)
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index b25bf26135e0..0f6bf6d4c031 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -66,6 +66,7 @@
#define MAX17055_DPACC_FACTOR 44138
#define MAX17055_DPACC_VCHG_FACTOR 51200
#define MAX17055_FSTAT_DNR_BIT BIT(0)
+#define MAX17055_VCHG_THRESHOLD_UV 4275000
#define MAX17055_DNR_POLL_US 10000
#define MAX17055_DNR_TIMEOUT_US 2000000
#define MAX17055_INIT_RETRY_DELAY_MS 10000
@@ -83,6 +84,7 @@ struct max17042_chip {
int task_period;
bool enable_current_sense;
bool enable_por_init;
+ bool enable_vchg_override;
bool init_complete;
bool hib_restore_pending;
u16 hib_cfg;
@@ -879,7 +881,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17047_V_empty, config->vempty);
}
- if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 &&
+ !chip->enable_vchg_override)
max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
}
@@ -914,7 +917,19 @@ static int max17055_override_battery_values(struct max17042_chip *chip)
return ret;
}
- if (!config->design_cap && !config->dqacc)
+ if (chip->enable_vchg_override) {
+ ret = regmap_update_bits(map, MAX17055_ModelCfg,
+ MAX17055_MODELCFG_VCHG_BIT,
+ config->model_cfg &
+ MAX17055_MODELCFG_VCHG_BIT);
+ if (ret)
+ return ret;
+
+ usleep_range(1000, 2000);
+ }
+
+ if (!config->design_cap && !config->dqacc &&
+ !chip->enable_vchg_override)
return 0;
ret = regmap_read(map, MAX17042_DesignCap, &design_cap);
@@ -928,6 +943,10 @@ static int max17055_override_battery_values(struct max17042_chip *chip)
ret = regmap_read(map, MAX17055_ModelCfg, &model_cfg);
if (ret)
return ret;
+ if (chip->enable_vchg_override &&
+ (model_cfg & MAX17055_MODELCFG_VCHG_BIT) !=
+ (config->model_cfg & MAX17055_MODELCFG_VCHG_BIT))
+ return -EIO;
if (!design_cap || !dqacc)
return -ERANGE;
@@ -1248,9 +1267,11 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip,
struct device *dev = chip->dev;
bool have_design_cap;
bool have_ichgt_term;
+ bool have_vchg;
u16 design_cap = 0;
u16 ichgt_term = 0;
u16 dqacc = 0;
+ u16 model_cfg = 0;
u64 data64;
if (!info || chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055)
@@ -1260,7 +1281,8 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip,
info->charge_full_design_uah >= 0;
have_ichgt_term = chip->enable_current_sense &&
info->charge_term_current_ua >= 0;
- if (!have_design_cap && !have_ichgt_term)
+ have_vchg = info->voltage_max_design_uv >= 0;
+ if (!have_design_cap && !have_ichgt_term && !have_vchg)
return 0;
if (have_design_cap) {
@@ -1301,6 +1323,15 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip,
ichgt_term = (u16)data64;
}
+ if (have_vchg) {
+ if (!info->voltage_max_design_uv)
+ return dev_err_probe(dev, -EINVAL,
+ "battery design voltage must be positive\n");
+
+ if (info->voltage_max_design_uv > MAX17055_VCHG_THRESHOLD_UV)
+ model_cfg = MAX17055_MODELCFG_VCHG_BIT;
+ }
+
config = chip->config_data;
if (!config) {
config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL);
@@ -1314,6 +1345,11 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip,
}
if (have_ichgt_term)
config->ichgt_term = ichgt_term;
+ if (have_vchg) {
+ config->model_cfg &= ~MAX17055_MODELCFG_VCHG_BIT;
+ config->model_cfg |= model_cfg;
+ chip->enable_vchg_override = true;
+ }
chip->config_data = config;
chip->enable_por_init = true;
--
2.55.0
^ permalink raw reply related
* [PATCH v2 1/4] power: supply: Add registration init callback
From: Vincent Cloutier @ 2026-07-20 1:11 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
Conor Dooley, linux-pm, devicetree, linux-kernel,
Vincent Cloutier
In-Reply-To: <20260720011144.1280219-1-vincent.cloutier@icloud.com>
From: Vincent Cloutier <vincent@cloutier.co>
Some battery drivers need to consume monitored-battery data before their
power_supply is visible. That lets them prepare hardware configuration from
parsed battery information without racing userspace exposure.
power_supply_get_battery_info() already runs in
__power_supply_register() for battery devices before device_add(). Add an
optional descriptor init callback after driver data and battery info are
available. The callback runs in sleepable process context while the power
supply is still unpublished.
Require callbacks to return zero or a negative errno. Defensively reject
positive returns so registration cannot return an invalid error pointer.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/power_supply_core.c | 8 ++++++++
include/linux/power_supply.h | 8 ++++++++
2 files changed, 16 insertions(+)
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 2532e221b2e1..3ed4c253706f 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -1624,6 +1624,14 @@ __power_supply_register(struct device *parent,
init_rwsem(&psy->extensions_sem);
INIT_LIST_HEAD(&psy->extensions);
+ if (desc->init) {
+ rc = desc->init(psy);
+ if (WARN_ON_ONCE(rc > 0))
+ rc = -EINVAL;
+ if (rc)
+ goto check_supplies_failed;
+ }
+
rc = device_add(dev);
if (rc)
goto device_add_failed;
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 7a5e4c3242a0..6042f5e1fdc2 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -281,6 +281,14 @@ struct power_supply_desc {
int (*property_is_writeable)(struct power_supply *psy,
enum power_supply_property psp);
void (*external_power_changed)(struct power_supply *psy);
+ /*
+ * Optional registration-time initialization. This runs in sleepable
+ * process context after driver data and any battery info are available,
+ * but before the device is added. The callback must not publish changes
+ * because registration is not complete. Return 0 on success or a negative
+ * errno on failure.
+ */
+ int (*init)(struct power_supply *psy);
/*
* Set if thermal zone should not be created for this power supply.
--
2.55.0
^ permalink raw reply related
* [PATCH v2 0/4] power: supply: initialize MAX17055 from battery info
From: Vincent Cloutier @ 2026-07-20 1:11 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
Conor Dooley, linux-pm, devicetree, linux-kernel,
Vincent Cloutier
From: Vincent Cloutier <vincent@cloutier.co>
Hi,
This is the remaining unmerged tail of the MAX17042/MAX17055 v1 series.
Six v1 patches are upstream, and the v1 cleanup patch is covered by
699f0f71ac98. This revision retains the requested registration callback
architecture and strengthens the MAX17055 POR sequence.
The power-supply callback runs after the core parses monitored-battery but
before device_add(). This lets the driver validate and prepare DesignCap,
IChgTerm, dQAcc, and VChg without another firmware parser or a
power_supply_config handoff. dPAcc is derived later from register
read-back.
The MAX17055 path waits for FSTAT.DNR, saves HibCfg, exits hibernate,
writes each stable battery register once, and verifies it after 1 ms.
SoftWakeup is written directly, and Refresh is requested once before
polling. The driver derives dPAcc from read-back DesignCap, dQAcc, and
ModelCfg values, restores HibCfg, and clears POR only after the complete
sequence succeeds.
Driver-backed properties remain unavailable while initialization is
incomplete. An interrupted transaction restores hibernate and retries
every 10 seconds without depending on POR still being set, so a transient
startup error does not become permanent.
VChg remains a masked ModelCfg update. A voltage-only override reads
effective DesignCap and dQAcc and refreshes dPAcc for the selected voltage
range. Zero is rejected. Exactly 4.275 V selects the low range; only values
above 4.275 V set VChg. An absent property preserves firmware's setting.
---
Changes since v1:
- send only the unmerged battery-info and VChg work
- split the power-supply core and MAX17055 driver changes
- use the requested registration init callback
- wait for FSTAT.DNR before programming POR values
- save, exit, and restore hibernate around MAX17055 EZ Config
- write stable registers once, wait 1 ms, and verify their read-back
- issue each command once and poll ModelCfg.Refresh
- derive dPAcc from register read-back values
- retry interrupted initialization periodically without a POR recheck
- keep properties unavailable until initialization completes
- recalculate dPAcc for a VChg-only override and reject zero voltage
- allow monitored-battery in the MAX17042 family binding
- drop the Librem 5 battery description because its user-replaceable pack
cannot be identified reliably from the board revision
The series was also folded into a Librem 5r4 kernel and tested after a full
battery-removal gauge POR using a local battery description. Status.POR and
FSTAT.DNR cleared, HibCfg was restored, and ModelCfg.Refresh cleared. The
driver programmed dQAcc=0x002b and dPAcc=0x054b; the live dQAcc had moved to
0x002a after initialization. The other raw values and sysfs properties
matched the expected 4.2 V EZ Config result, with no MAX17055 initialization
errors.
v1: https://lore.kernel.org/all/20260406205759.493288-1-vincent.cloutier@icloud.com/
Vincent Cloutier (4):
power: supply: Add registration init callback
power: supply: max17042_battery: Initialize MAX17055 from battery info
power: supply: max17042_battery: Honor MAX17055 charge voltage
dt-bindings: power: supply: max17042: Allow monitored-battery
.../bindings/power/supply/maxim,max17042.yaml | 2 +
drivers/power/supply/max17042_battery.c | 373 ++++++++++++++++--
drivers/power/supply/power_supply_core.c | 8 +
include/linux/power/max17042_battery.h | 2 +
include/linux/power_supply.h | 8 +
5 files changed, 361 insertions(+), 32 deletions(-)
base-commit: f2ec6312bf711369561bdcb22f8a63c0b118c479
--
2.55.0
^ permalink raw reply
* [PATCH v13 2/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver
From: Bryan O'Donoghue @ 2026-07-20 1:11 UTC (permalink / raw)
To: Vinod Koul, Kishon Vijay Abraham I, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong
Cc: Bryan O'Donoghue, Vladimir Zapolskiy, linux-arm-msm,
linux-phy, linux-media, devicetree, linux-kernel,
Bryan O'Donoghue
In-Reply-To: <20260720-x1e-csi2-phy-v13-0-160c31958863@linaro.org>
Add a new MIPI CSI2 driver in DPHY mode initially. The entire set of
existing CAMSS CSI PHY init sequences are imported in order to save time
and effort in later patches.
The following devices are supported in this drop:
"qcom,x1e80100-csi2-phy"
In-line with other PHY drivers the process node is included in the name.
Data-lane and clock lane positioning and polarity selection via newly
amended struct phy_configure_opts_mipi_dphy{} is supported.
The Qualcomm 3PH class of PHYs can do both DPHY and CPHY mode. For now only
DPHY is supported.
In porting some of the logic over from camss-csiphy*.c to here its also
possible to rationalise some of the code.
In particular use of regulator_bulk and clk_bulk as well as dropping the
seemingly useless and unused interrupt handler.
The PHY sequences and a lot of the logic that goes with them are well
proven in CAMSS and mature so the main thing to watch out for here is how
to get the right sequencing of regulators, clocks and register-writes.
The register init sequence table is imported verbatim from the existing
CAMSS csiphy driver. A follow-up series will rework the table to extract
the repetitive per-lane pattern into a loop.
Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
---
MAINTAINERS | 10 +
drivers/phy/qualcomm/Kconfig | 15 +
drivers/phy/qualcomm/Makefile | 5 +
drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c | 385 ++++++++++++++++++
drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c | 449 +++++++++++++++++++++
drivers/phy/qualcomm/phy-qcom-mipi-csi2.h | 97 +++++
6 files changed, 961 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a99..a203b41475ea4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22296,6 +22296,16 @@ S: Maintained
F: Documentation/devicetree/bindings/media/qcom,*-iris.yaml
F: drivers/media/platform/qcom/iris/
+QUALCOMM MIPI CSI2 PHY DRIVER
+M: Bryan O'Donoghue <bod@kernel.org>
+L: linux-phy@lists.infradead.org
+L: linux-media@vger.kernel.org
+L: linux-arm-msm@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/phy/qcom,*-csi2-phy.yaml
+F: drivers/phy/qualcomm/phy-qcom-mipi-csi2*.c
+F: drivers/phy/qualcomm/phy-qcom-mipi-csi2*.h
+
QUALCOMM NAND CONTROLLER DRIVER
M: Manivannan Sadhasivam <mani@kernel.org>
L: linux-mtd@lists.infradead.org
diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
index 60a0ead127fa9..cd6959e69bef7 100644
--- a/drivers/phy/qualcomm/Kconfig
+++ b/drivers/phy/qualcomm/Kconfig
@@ -28,6 +28,21 @@ config PHY_QCOM_EDP
Enable this driver to support the Qualcomm eDP PHY found in various
Qualcomm chipsets.
+config PHY_QCOM_MIPI_CSI2
+ tristate "Qualcomm MIPI CSI2 PHY driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on OF
+ depends on PM
+ depends on PM_OPP
+ depends on COMMON_CLK
+ select GENERIC_PHY
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ Enable this to support the MIPI CSI2 PHY driver found in various
+ Qualcomm chipsets. This PHY is used to connect MIPI CSI2
+ camera sensors to the CSI Decoder in the Qualcomm Camera Subsystem
+ CAMSS.
+
config PHY_QCOM_IPQ4019_USB
tristate "Qualcomm IPQ4019 USB PHY driver"
depends on OF && (ARCH_QCOM || COMPILE_TEST)
diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile
index b71a6a0bed3f1..382cb594b06b6 100644
--- a/drivers/phy/qualcomm/Makefile
+++ b/drivers/phy/qualcomm/Makefile
@@ -6,6 +6,11 @@ obj-$(CONFIG_PHY_QCOM_IPQ4019_USB) += phy-qcom-ipq4019-usb.o
obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o
obj-$(CONFIG_PHY_QCOM_M31_USB) += phy-qcom-m31.o
obj-$(CONFIG_PHY_QCOM_M31_EUSB) += phy-qcom-m31-eusb2.o
+
+phy-qcom-mipi-csi2-objs += phy-qcom-mipi-csi2-core.o \
+ phy-qcom-mipi-csi2-3ph-dphy.o
+obj-$(CONFIG_PHY_QCOM_MIPI_CSI2) += phy-qcom-mipi-csi2.o
+
obj-$(CONFIG_PHY_QCOM_PCIE2) += phy-qcom-pcie2.o
obj-$(CONFIG_PHY_QCOM_QMP_COMBO) += phy-qcom-qmp-combo.o phy-qcom-qmp-usbc.o
diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c
new file mode 100644
index 0000000000000..966d79c98f9b2
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm MSM Camera Subsystem - CSIPHY Module 3phase v1.0
+ *
+ * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016-2026 Linaro Ltd.
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/time64.h>
+
+#include "phy-qcom-mipi-csi2.h"
+
+#define CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(offset, n) ((offset) + 0x4 * (n))
+#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL0_PHY_SW_RESET BIT(0)
+#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE BIT(7)
+#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_COMMON_PWRDN_B BIT(0)
+#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_SHOW_REV_ID BIT(1)
+#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL10_IRQ_CLEAR_CMD BIT(0)
+#define CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(offset, n) ((offset) + 0xb0 + 0x4 * (n))
+
+#define CSIPHY_2PH_LN_CSI_2PHASE_CTRL9n(n) ((0x200 * (n)) + 0x24)
+
+/*
+ * 3 phase CSI has 19 common status regs with only 0-10 being used
+ * and 11-18 being reserved.
+ */
+#define CSI_COMMON_STATUS_NUM 11
+/*
+ * There are a number of common control registers
+ * The offset to clear the CSIPHY IRQ status starts @ 22
+ * So to clear CSI_COMMON_STATUS0 this is CSI_COMMON_CONTROL22, STATUS1 is
+ * CONTROL23 and so on
+ */
+#define CSI_CTRL_STATUS_INDEX 22
+
+/*
+ * There are 43 COMMON_CTRL registers with regs after # 33 being reserved
+ */
+#define CSI_CTRL_MAX 33
+
+#define CSIPHY_DEFAULT_PARAMS 0
+#define CSIPHY_SETTLE_CNT_LOWER_BYTE 2
+#define CSIPHY_SKEW_CAL 7
+
+/* 4nm 2PH v 2.1.2 2p5Gbps 4 lane DPHY mode */
+static const struct
+mipi_csi2phy_lane_regs lane_regs_x1e80100[] = {
+ /* Power up lanes 2ph mode */
+ {.reg_addr = 0x101c, .reg_data = 0x7a, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x1018, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+
+ {.reg_addr = 0x0094, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x00a0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0090, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0098, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0094, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0030, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0000, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0038, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x002c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0034, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x001c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0014, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x003c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0004, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0020, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0008, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE},
+ {.reg_addr = 0x0010, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0094, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x005c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x0060, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x0064, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL},
+
+ {.reg_addr = 0x0e94, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0ea0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e90, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e98, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e94, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e30, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e28, .reg_data = 0x04, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e00, .reg_data = 0x80, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e0c, .reg_data = 0xff, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e38, .reg_data = 0x1f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e2c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e34, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e1c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e14, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e3c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e04, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e20, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0e08, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE},
+ {.reg_addr = 0x0e10, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS},
+
+ {.reg_addr = 0x0494, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x04a0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0490, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0498, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0494, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0430, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0400, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0438, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x042c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0434, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x041c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0414, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x043c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0404, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0420, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0408, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE},
+ {.reg_addr = 0x0410, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0494, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x045c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x0460, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x0464, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL},
+
+ {.reg_addr = 0x0894, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x08a0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0890, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0898, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0894, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0830, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0800, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0838, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x082c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0834, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x081c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0814, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x083c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0804, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0820, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0808, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE},
+ {.reg_addr = 0x0810, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0894, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x085c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x0860, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x0864, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL},
+
+ {.reg_addr = 0x0c94, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0ca0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c90, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c98, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c94, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c30, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c00, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c38, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c2c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c34, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c1c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c14, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c3c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c04, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c20, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c08, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE},
+ {.reg_addr = 0x0c10, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS},
+ {.reg_addr = 0x0c94, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x0c5c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x0c60, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL},
+ {.reg_addr = 0x0c64, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL},
+};
+
+static inline const struct mipi_csi2phy_device_regs *
+csi2phy_dev_to_regs(struct mipi_csi2phy_device *csi2phy)
+{
+ return &csi2phy->soc_cfg->reg_info;
+}
+
+static void phy_qcom_mipi_csi2_hw_version_read(struct mipi_csi2phy_device *csi2phy)
+{
+ const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy);
+ u32 tmp;
+
+ writel(CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_SHOW_REV_ID, csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 6));
+
+ tmp = readl_relaxed(csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 12));
+ csi2phy->hw_version = tmp;
+
+ tmp = readl_relaxed(csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 13));
+ csi2phy->hw_version |= (tmp << 8) & 0xFF00;
+
+ tmp = readl_relaxed(csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 14));
+ csi2phy->hw_version |= (tmp << 16) & 0xFF0000;
+
+ tmp = readl_relaxed(csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 15));
+ csi2phy->hw_version |= (tmp << 24) & 0xFF000000;
+
+ dev_dbg_once(csi2phy->dev, "CSIPHY 3PH HW Version = 0x%08x\n", csi2phy->hw_version);
+}
+
+/*
+ * phy_qcom_mipi_csi2_reset - Perform software reset on CSIPHY module
+ * @phy_qcom_mipi_csi2: CSIPHY device
+ */
+static void phy_qcom_mipi_csi2_reset(struct mipi_csi2phy_device *csi2phy)
+{
+ const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy);
+
+ writel(CSIPHY_3PH_CMN_CSI_COMMON_CTRL0_PHY_SW_RESET,
+ csi2phy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 0));
+ usleep_range(5000, 8000);
+ writel(0x0, csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 0));
+}
+
+/*
+ * phy_qcom_mipi_csi2_settle_cnt_calc - Calculate settle count value
+ *
+ * Helper function to calculate settle count value. This is
+ * based on the CSI2 T_hs_settle parameter which in turn
+ * is calculated based on the CSI2 transmitter link frequency.
+ *
+ * Return settle count.
+ */
+static u8 phy_qcom_mipi_csi2_settle_cnt_calc(s64 link_freq, u32 timer_clk_rate)
+{
+ u32 t_hs_prepare_max_ps;
+ u32 timer_period_ps;
+ u32 t_hs_settle_ps;
+ u8 settle_cnt;
+ u32 ui_ps;
+
+ ui_ps = div64_u64(PSEC_PER_SEC, link_freq);
+ ui_ps /= 2;
+ t_hs_prepare_max_ps = 85000 + 6 * ui_ps;
+ t_hs_settle_ps = t_hs_prepare_max_ps;
+
+ timer_period_ps = div_u64(PSEC_PER_SEC, timer_clk_rate);
+
+ if ((t_hs_settle_ps / timer_period_ps) < 6)
+ return 0;
+
+ settle_cnt = t_hs_settle_ps / timer_period_ps - 6;
+
+ return settle_cnt;
+}
+
+static void
+phy_qcom_mipi_csi2_gen2_config_lanes(struct mipi_csi2phy_device *csi2phy,
+ u8 settle_cnt)
+{
+ const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy);
+ const struct mipi_csi2phy_lane_regs *r = regs->init_seq;
+ int i, array_size = regs->lane_array_size;
+ u32 val;
+
+ for (i = 0; i < array_size; i++, r++) {
+ switch (r->param_type) {
+ case CSIPHY_SETTLE_CNT_LOWER_BYTE:
+ val = settle_cnt & 0xff;
+ break;
+ case CSIPHY_SKEW_CAL:
+ /* TODO: support application of skew from dt flag */
+ continue;
+ default:
+ val = r->reg_data;
+ break;
+ }
+ writel(val, csi2phy->base + r->reg_addr);
+ if (r->delay_us)
+ udelay(r->delay_us);
+ }
+}
+
+static int phy_qcom_mipi_csi2_lanes_enable(struct mipi_csi2phy_device *csi2phy,
+ struct mipi_csi2phy_stream_cfg *cfg)
+{
+ const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy);
+ struct mipi_csi2phy_lanes_cfg *lane_cfg = &cfg->lane_cfg;
+ u8 settle_cnt;
+ u8 val;
+ int i;
+
+ if (cfg->link_freq <= 0)
+ return -EINVAL;
+
+ settle_cnt = phy_qcom_mipi_csi2_settle_cnt_calc(cfg->link_freq, csi2phy->timer_clk_rate);
+ if (!settle_cnt)
+ return -ENODEV;
+
+ /*
+ * CSI_COMMON_CTRL5 is a physical lane power-up bitmap:
+ * - Bits [0,2,4,6] → D-PHY data lanes(LN0, LN2, LN4, LN6)
+ * - Bits [1,3,5] → C-PHY trio lanes(LN1, LN3, LN5)
+ * - Bit [7] → D-PHY clock lane(LNCK) dedicated clock enable
+ */
+ val = BIT(lane_cfg->clk.pos);
+ for (i = 0; i < cfg->num_data_lanes; i++)
+ val |= BIT(lane_cfg->data[i].pos * 2);
+
+ writel(val, csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 5));
+
+ /* Lane configuration for polarity @ CSIPHY-base + CTRL9 */
+ for (i = 0; i < cfg->num_data_lanes; i++) {
+ if (lane_cfg->data[i].pol) {
+ u8 pos = lane_cfg->data[i].pos;
+
+ writel(BIT(2), csi2phy->base + CSIPHY_2PH_LN_CSI_2PHASE_CTRL9n(pos * 2));
+ }
+ }
+
+ if (lane_cfg->clk.pol)
+ writel(BIT(2), csi2phy->base + CSIPHY_2PH_LN_CSI_2PHASE_CTRL9n(lane_cfg->clk.pos));
+
+ val = CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_COMMON_PWRDN_B;
+ writel(val, csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 6));
+
+ val = 0x02;
+ writel(val, csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 7));
+
+ val = 0x00;
+ writel(val, csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 0));
+
+ phy_qcom_mipi_csi2_gen2_config_lanes(csi2phy, settle_cnt);
+
+ /* IRQ_MASK registers - disable all interrupts */
+ for (i = CSI_COMMON_STATUS_NUM; i < CSI_CTRL_STATUS_INDEX; i++) {
+ writel(0, csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, i));
+ }
+
+ return 0;
+}
+
+static void
+phy_qcom_mipi_csi2_lanes_disable(struct mipi_csi2phy_device *csi2phy,
+ struct mipi_csi2phy_stream_cfg *cfg)
+{
+ const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy);
+
+ writel(0, csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 5));
+
+ writel(0, csi2phy->base +
+ CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 6));
+}
+
+static const struct mipi_csi2phy_hw_ops phy_qcom_mipi_csi2_ops_3ph_1_0 = {
+ .hw_version_read = phy_qcom_mipi_csi2_hw_version_read,
+ .reset = phy_qcom_mipi_csi2_reset,
+ .lanes_enable = phy_qcom_mipi_csi2_lanes_enable,
+ .lanes_disable = phy_qcom_mipi_csi2_lanes_disable,
+};
+
+static const char * const x1e_clks[] = {
+ "core",
+ "timer",
+ "ahb"
+};
+
+static const char * const x1e_supplies[] = {
+ "vdda-0p9",
+ "vdda-1p2"
+};
+
+static struct mipi_csi2_genpd x1e_genpds[] = {
+ { .name = "top", .scaled = false },
+ { .name = "mmcx", .scaled = true },
+ { .name = "mx", .scaled = true },
+};
+
+const struct mipi_csi2phy_soc_cfg mipi_csi2_dphy_4nm_x1e = {
+ .ops = &phy_qcom_mipi_csi2_ops_3ph_1_0,
+ .reg_info = {
+ .init_seq = lane_regs_x1e80100,
+ .lane_array_size = ARRAY_SIZE(lane_regs_x1e80100),
+ .common_regs_offset = 0x1000,
+ },
+ .supply_names = (const char **)x1e_supplies,
+ .num_supplies = ARRAY_SIZE(x1e_supplies),
+ .clk_names = (const char **)x1e_clks,
+ .num_clk = ARRAY_SIZE(x1e_clks),
+ .genpds = x1e_genpds,
+ .num_genpds = ARRAY_SIZE(x1e_genpds),
+};
diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c
new file mode 100644
index 0000000000000..aae049f0ec160
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c
@@ -0,0 +1,449 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026, Linaro Ltd.
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_opp.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include "phy-qcom-mipi-csi2.h"
+
+static int
+phy_qcom_mipi_csi2_set_clock_rates(struct mipi_csi2phy_device *csi2phy,
+ s64 link_freq)
+{
+ struct device *dev = csi2phy->dev;
+ unsigned long opp_rate = link_freq / 4;
+ struct dev_pm_opp *opp;
+ long timer_rate;
+ int i, pstate;
+ int ret;
+
+ opp = dev_pm_opp_find_freq_ceil(dev, &opp_rate);
+ if (IS_ERR(opp)) {
+ dev_err(csi2phy->dev, "Couldn't find ceiling for %lld Hz\n",
+ link_freq);
+ return PTR_ERR(opp);
+ }
+
+ pstate = 0;
+ for (i = 0; i < csi2phy->pd_list->num_pds; i++) {
+ unsigned int perf;
+
+ if (!csi2phy->soc_cfg->genpds[i].scaled)
+ continue;
+
+ perf = dev_pm_opp_get_required_pstate(opp, pstate);
+ pstate += 1;
+
+ ret = dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], perf);
+ if (ret) {
+ dev_err(csi2phy->dev, "Couldn't set perf state %u\n",
+ perf);
+ dev_pm_opp_put(opp);
+ goto unset_pstate;
+ }
+ }
+ dev_pm_opp_put(opp);
+
+ ret = dev_pm_opp_set_rate(dev, opp_rate);
+ if (ret) {
+ dev_err(csi2phy->dev, "dev_pm_opp_set_rate() fail\n");
+ goto unset_opp_rate;
+ }
+
+ timer_rate = clk_round_rate(csi2phy->timer_clk, link_freq / 4);
+ if (timer_rate <= 0) {
+ ret = -ENODEV;
+ goto unset_opp_rate;
+ }
+
+ ret = clk_set_rate(csi2phy->timer_clk, timer_rate);
+ if (ret)
+ goto unset_opp_rate;
+
+ csi2phy->timer_clk_rate = timer_rate;
+
+ return 0;
+
+unset_opp_rate:
+ dev_pm_opp_set_rate(dev, 0);
+
+unset_pstate:
+ while (i--) {
+ if (!csi2phy->soc_cfg->genpds[i].scaled)
+ continue;
+
+ dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], 0);
+ }
+
+ return ret;
+}
+
+static int phy_qcom_mipi_csi2_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy);
+ struct phy_configure_opts_mipi_dphy *dphy_cfg = &opts->mipi_dphy;
+ struct mipi_csi2phy_stream_cfg *stream_cfg = &csi2phy->stream_cfg;
+ int ret;
+
+ ret = phy_mipi_dphy_config_validate(dphy_cfg);
+ if (ret)
+ return ret;
+
+ if (dphy_cfg->lanes < 1 || dphy_cfg->lanes > CSI2_MAX_DATA_LANES)
+ return -EINVAL;
+
+ stream_cfg->link_freq = dphy_cfg->hs_clk_rate;
+
+ return 0;
+}
+
+static int phy_qcom_mipi_csi2_power_on(struct phy *phy)
+{
+ struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy);
+ const struct mipi_csi2phy_hw_ops *ops = csi2phy->soc_cfg->ops;
+ int i, ret;
+
+ ret = regulator_bulk_enable(csi2phy->soc_cfg->num_supplies,
+ csi2phy->supplies);
+ if (ret)
+ return ret;
+
+ ret = pm_runtime_resume_and_get(csi2phy->dev);
+ if (ret < 0)
+ goto disable_regulators;
+
+ ret = phy_qcom_mipi_csi2_set_clock_rates(csi2phy, csi2phy->stream_cfg.link_freq);
+ if (ret)
+ goto poweroff_phy;
+
+ ret = clk_bulk_prepare_enable(csi2phy->soc_cfg->num_clk,
+ csi2phy->clks);
+ if (ret) {
+ dev_err(csi2phy->dev, "failed to enable clocks, %d\n", ret);
+ goto unset_rate;
+ }
+
+ ops->reset(csi2phy);
+
+ ops->hw_version_read(csi2phy);
+
+ ret = ops->lanes_enable(csi2phy, &csi2phy->stream_cfg);
+ if (ret)
+ goto unset_clocks;
+
+ return 0;
+
+unset_clocks:
+ clk_bulk_disable_unprepare(csi2phy->soc_cfg->num_clk,
+ csi2phy->clks);
+
+unset_rate:
+ dev_pm_opp_set_rate(csi2phy->dev, 0);
+
+ for (i = 0; i < csi2phy->pd_list->num_pds; i++) {
+ if (!csi2phy->soc_cfg->genpds[i].scaled)
+ continue;
+
+ dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], 0);
+ }
+
+poweroff_phy:
+ pm_runtime_put_sync(csi2phy->dev);
+
+disable_regulators:
+ regulator_bulk_disable(csi2phy->soc_cfg->num_supplies,
+ csi2phy->supplies);
+
+ return ret;
+}
+
+static int phy_qcom_mipi_csi2_power_off(struct phy *phy)
+{
+ struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy);
+ const struct mipi_csi2phy_hw_ops *ops = csi2phy->soc_cfg->ops;
+ int i;
+
+ ops->lanes_disable(csi2phy, &csi2phy->stream_cfg);
+
+ clk_bulk_disable_unprepare(csi2phy->soc_cfg->num_clk,
+ csi2phy->clks);
+
+ dev_pm_opp_set_rate(csi2phy->dev, 0);
+
+ for (i = 0; i < csi2phy->pd_list->num_pds; i++) {
+ if (!csi2phy->soc_cfg->genpds[i].scaled)
+ continue;
+
+ dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], 0);
+ }
+
+ pm_runtime_put_sync(csi2phy->dev);
+
+ regulator_bulk_disable(csi2phy->soc_cfg->num_supplies,
+ csi2phy->supplies);
+
+ return 0;
+}
+
+static const struct phy_ops phy_qcom_mipi_csi2_ops = {
+ .configure = phy_qcom_mipi_csi2_configure,
+ .power_on = phy_qcom_mipi_csi2_power_on,
+ .power_off = phy_qcom_mipi_csi2_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *qcom_csi2_phy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct mipi_csi2phy_device *csi2phy = dev_get_drvdata(dev);
+
+ if (args->args_count < 1 || args->args[0] != PHY_TYPE_DPHY) {
+ dev_err(csi2phy->dev, "invalid phy mode in DTB\n");
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+
+ csi2phy->phy_mode = args->args[0];
+
+ return csi2phy->phy;
+}
+
+static int phy_qcom_mipi_csi2_attach_pm_domains(struct mipi_csi2phy_device *csi2phy)
+{
+ struct dev_pm_domain_attach_data pd_data = { 0 };
+ const char **pd_names;
+ int i;
+
+ pd_names = devm_kzalloc(csi2phy->dev,
+ sizeof(char *) * csi2phy->soc_cfg->num_genpds,
+ GFP_KERNEL);
+ if (!pd_names)
+ return -ENOMEM;
+
+ for (i = 0; i < csi2phy->soc_cfg->num_genpds; i++)
+ pd_names[i] = csi2phy->soc_cfg->genpds[i].name;
+
+ pd_data.pd_names = pd_names;
+ pd_data.num_pd_names = csi2phy->soc_cfg->num_genpds;
+
+ return devm_pm_domain_attach_list(csi2phy->dev, &pd_data,
+ &csi2phy->pd_list);
+}
+
+static int phy_qcom_mipi_csi2_parse_routing(struct mipi_csi2phy_device *csi2phy)
+{
+ struct mipi_csi2phy_stream_cfg *stream_cfg = &csi2phy->stream_cfg;
+ u32 lane_polarities[CSI2_MAX_DATA_LANES + 1];
+ u32 data_lanes[CSI2_MAX_DATA_LANES];
+ struct device *dev = csi2phy->dev;
+ struct fwnode_handle *ep;
+ int num_polarities;
+ int num_data_lanes;
+ int i, ret;
+
+ ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 1, 0);
+ if (ep) {
+ fwnode_handle_put(ep);
+ dev_err(dev, "DPHY split mode is not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
+ if (!ep) {
+ dev_err(dev, "Missing port@0\n");
+ return -ENODEV;
+ }
+
+ num_data_lanes = fwnode_property_count_u32(ep, "data-lanes");
+ if (num_data_lanes < 1 || num_data_lanes > CSI2_MAX_DATA_LANES) {
+ ret = -EINVAL;
+ dev_err(dev, "Invalid data-lanes count: %d\n", num_data_lanes);
+ goto out_put;
+ }
+ stream_cfg->num_data_lanes = num_data_lanes;
+
+ ret = fwnode_property_read_u32_array(ep, "data-lanes", data_lanes,
+ stream_cfg->num_data_lanes);
+ if (ret) {
+ dev_err(dev, "Failed to read data-lanes: %d\n", ret);
+ goto out_put;
+ }
+
+ /* lane-polarities: optional, up to num_data_lanes + 1 entries */
+ memset(lane_polarities, 0x00, sizeof(lane_polarities));
+ num_polarities = fwnode_property_count_u32(ep, "lane-polarities");
+ if (num_polarities > 0) {
+ if (num_polarities != stream_cfg->num_data_lanes + 1) {
+ ret = -EINVAL;
+ dev_err(dev, "clock+data-lane %d/polarities %d mismatch\n",
+ stream_cfg->num_data_lanes + 1, num_polarities);
+ goto out_put;
+ }
+
+ ret = fwnode_property_read_u32_array(ep, "lane-polarities", lane_polarities,
+ num_polarities);
+ if (ret) {
+ dev_err(dev, "Failed to read lane-polarities: %d\n", ret);
+ goto out_put;
+ }
+ }
+
+ csi2phy->stream_cfg.lane_cfg.clk.pos = CSI2_DEFAULT_CLK_LANE;
+ csi2phy->stream_cfg.lane_cfg.clk.pol = lane_polarities[0];
+
+ for (i = 0; i < csi2phy->stream_cfg.num_data_lanes; i++) {
+ if (data_lanes[i] >= CSI2_MAX_DATA_LANES) {
+ dev_err(dev, "Invalid lane %d\n", data_lanes[i]);
+ ret = -EINVAL;
+ goto out_put;
+ }
+ csi2phy->stream_cfg.lane_cfg.data[i].pos = data_lanes[i];
+ csi2phy->stream_cfg.lane_cfg.data[i].pol = lane_polarities[i + 1];
+ }
+
+ ret = 0;
+
+out_put:
+ fwnode_handle_put(ep);
+
+ return ret;
+}
+
+static int phy_qcom_mipi_csi2_probe(struct platform_device *pdev)
+{
+ unsigned int i, num_clk, num_supplies;
+ struct mipi_csi2phy_device *csi2phy;
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct phy *generic_phy;
+ int ret;
+
+ csi2phy = devm_kzalloc(dev, sizeof(*csi2phy), GFP_KERNEL);
+ if (!csi2phy)
+ return -ENOMEM;
+
+ csi2phy->dev = dev;
+ dev_set_drvdata(dev, csi2phy);
+
+ csi2phy->soc_cfg = device_get_match_data(&pdev->dev);
+
+ if (!csi2phy->soc_cfg)
+ return -EINVAL;
+
+ num_clk = csi2phy->soc_cfg->num_clk;
+
+ ret = phy_qcom_mipi_csi2_parse_routing(csi2phy);
+ if (ret)
+ return ret;
+
+ ret = phy_qcom_mipi_csi2_attach_pm_domains(csi2phy);
+ if (ret < 0 || csi2phy->pd_list == NULL) {
+ if (ret == 0)
+ ret = -ENODEV;
+ return dev_err_probe(dev, ret, "Failed to attach power-domain list\n");
+ }
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to enable pm runtime\n");
+
+ ret = devm_clk_bulk_get_all(dev, &csi2phy->clks);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to get clocks\n");
+
+ if (num_clk != ret) {
+ return dev_err_probe(dev, -ENODEV, "clock count %d expected %d\n",
+ ret, num_clk);
+ }
+
+ for (i = 0; i < num_clk; i++) {
+ if (!csi2phy->clks[i].id)
+ return dev_err_probe(dev, -EINVAL, "Missing clock-names\n");
+
+ if (!strcmp(csi2phy->clks[i].id, "timer")) {
+ csi2phy->timer_clk = csi2phy->clks[i].clk;
+ break;
+ }
+ }
+ if (!csi2phy->timer_clk)
+ return dev_err_probe(dev, -ENODEV, "no timer clock\n");
+
+ ret = devm_pm_opp_set_clkname(dev, "core");
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to set opp clkname\n");
+
+ ret = devm_pm_opp_of_add_table(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "invalid OPP table in device tree\n");
+
+ num_supplies = csi2phy->soc_cfg->num_supplies;
+ csi2phy->supplies = devm_kzalloc(dev, sizeof(*csi2phy->supplies) * num_supplies,
+ GFP_KERNEL);
+ if (!csi2phy->supplies)
+ return -ENOMEM;
+
+ for (i = 0; i < num_supplies; i++)
+ csi2phy->supplies[i].supply = csi2phy->soc_cfg->supply_names[i];
+
+ ret = devm_regulator_bulk_get(dev, num_supplies, csi2phy->supplies);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get regulator supplies\n");
+
+ csi2phy->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(csi2phy->base))
+ return PTR_ERR(csi2phy->base);
+
+ generic_phy = devm_phy_create(dev, NULL, &phy_qcom_mipi_csi2_ops);
+ if (IS_ERR(generic_phy)) {
+ ret = PTR_ERR(generic_phy);
+ return dev_err_probe(dev, ret, "failed to create phy\n");
+ }
+ csi2phy->phy = generic_phy;
+
+ phy_set_drvdata(generic_phy, csi2phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, qcom_csi2_phy_xlate);
+ if (!IS_ERR(phy_provider))
+ dev_dbg(dev, "Registered MIPI CSI2 PHY device\n");
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id phy_qcom_mipi_csi2_of_match_table[] = {
+ { .compatible = "qcom,x1e80100-csi2-phy", .data = &mipi_csi2_dphy_4nm_x1e },
+ { }
+};
+MODULE_DEVICE_TABLE(of, phy_qcom_mipi_csi2_of_match_table);
+
+static struct platform_driver phy_qcom_mipi_csi2_driver = {
+ .probe = phy_qcom_mipi_csi2_probe,
+ .driver = {
+ .name = "qcom-mipi-csi2-phy",
+ .of_match_table = phy_qcom_mipi_csi2_of_match_table,
+ },
+};
+
+module_platform_driver(phy_qcom_mipi_csi2_driver);
+
+MODULE_DESCRIPTION("Qualcomm MIPI CSI2 PHY driver");
+MODULE_AUTHOR("Bryan O'Donoghue <bod@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h b/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h
new file mode 100644
index 0000000000000..7e55ae0073704
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ *
+ * Qualcomm MIPI CSI2 CPHY/DPHY driver
+ *
+ * Copyright (C) 2025 Linaro Ltd.
+ */
+#ifndef __PHY_QCOM_MIPI_CSI2_H__
+#define __PHY_QCOM_MIPI_CSI2_H__
+
+#include <linux/phy/phy.h>
+
+#define CSI2_MAX_DATA_LANES 4
+#define CSI2_DEFAULT_CLK_LANE 7
+
+struct mipi_csi2phy_lane {
+ u8 pos;
+ u8 pol;
+};
+
+struct mipi_csi2phy_lanes_cfg {
+ struct mipi_csi2phy_lane data[CSI2_MAX_DATA_LANES];
+ struct mipi_csi2phy_lane clk;
+};
+
+struct mipi_csi2phy_stream_cfg {
+ s64 link_freq;
+ u8 num_data_lanes;
+ struct mipi_csi2phy_lanes_cfg lane_cfg;
+};
+
+struct mipi_csi2phy_device;
+
+struct mipi_csi2phy_hw_ops {
+ void (*hw_version_read)(struct mipi_csi2phy_device *csi2phy_dev);
+ void (*reset)(struct mipi_csi2phy_device *csi2phy_dev);
+ int (*lanes_enable)(struct mipi_csi2phy_device *csi2phy_dev,
+ struct mipi_csi2phy_stream_cfg *cfg);
+ void (*lanes_disable)(struct mipi_csi2phy_device *csi2phy_dev,
+ struct mipi_csi2phy_stream_cfg *cfg);
+};
+
+struct mipi_csi2phy_lane_regs {
+ const s32 reg_addr;
+ const s32 reg_data;
+ const u32 delay_us;
+ const u32 param_type;
+};
+
+struct mipi_csi2phy_device_regs {
+ const struct mipi_csi2phy_lane_regs *init_seq;
+ const int lane_array_size;
+ const u32 common_regs_offset;
+};
+
+struct mipi_csi2_genpd {
+ const char *name;
+ bool scaled;
+};
+
+struct mipi_csi2phy_soc_cfg {
+ const struct mipi_csi2phy_hw_ops *ops;
+ const struct mipi_csi2phy_device_regs reg_info;
+
+ const char ** const supply_names;
+ const unsigned int num_supplies;
+
+ const char ** const clk_names;
+ const unsigned int num_clk;
+
+ const struct mipi_csi2_genpd *genpds;
+ const unsigned int num_genpds;
+};
+
+struct mipi_csi2phy_device {
+ struct device *dev;
+ u8 phy_mode;
+
+ struct phy *phy;
+ void __iomem *base;
+
+ struct clk_bulk_data *clks;
+ struct clk *timer_clk;
+ u32 timer_clk_rate;
+
+ struct regulator_bulk_data *supplies;
+ struct dev_pm_domain_list *pd_list;
+
+ const struct mipi_csi2phy_soc_cfg *soc_cfg;
+ struct mipi_csi2phy_stream_cfg stream_cfg;
+
+ u32 hw_version;
+};
+
+extern const struct mipi_csi2phy_soc_cfg mipi_csi2_dphy_4nm_x1e;
+
+#endif /* __PHY_QCOM_MIPI_CSI2_H__ */
--
2.54.0
^ permalink raw reply related
* [PATCH v13 1/2] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema
From: Bryan O'Donoghue @ 2026-07-20 1:11 UTC (permalink / raw)
To: Vinod Koul, Kishon Vijay Abraham I, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong
Cc: Bryan O'Donoghue, Vladimir Zapolskiy, linux-arm-msm,
linux-phy, linux-media, devicetree, linux-kernel,
Bryan O'Donoghue, Krzysztof Kozlowski
In-Reply-To: <20260720-x1e-csi2-phy-v13-0-160c31958863@linaro.org>
Add a base schema for the MIPI CSI2 PHYs on Qualcomm SoCs. This PHY
supports both DPHY and CPHY operation. A special mode of DPHY operation -
called variously split-mode or combo-mode also allows for two sensors to be
connected to one PHY.
The submitted binding here describes the DPHY modes of operation only. CPHY
is left to future work.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
---
.../bindings/phy/qcom,x1e80100-csi2-phy.yaml | 195 +++++++++++++++++++++
1 file changed, 195 insertions(+)
diff --git a/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml
new file mode 100644
index 0000000000000..880fe602945cb
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml
@@ -0,0 +1,195 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/qcom,x1e80100-csi2-phy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm X1E80100 SoC CSI2 PHY
+
+maintainers:
+ - Bryan O'Donoghue <bod@kernel.org>
+
+description:
+ Qualcomm MIPI CSI2 C-PHY/D-PHY combination PHY. Connects MIPI CSI2 sensors
+ to Qualcomm's Camera CSI Decoder. The PHY supports both C-PHY and D-PHY
+ modes.
+
+properties:
+ compatible:
+ const: qcom,x1e80100-csi2-phy
+
+ reg:
+ maxItems: 1
+
+ "#phy-cells":
+ const: 1
+ description:
+ The single cell specifies the PHY operating mode.
+
+ clocks:
+ maxItems: 3
+
+ clock-names:
+ items:
+ - const: core
+ - const: timer
+ - const: ahb
+
+ interrupts:
+ maxItems: 1
+
+ operating-points-v2: true
+
+ opp-table:
+ type: object
+
+ power-domains:
+ items:
+ - description: Titan Top GDSC - Titan ISP Block, Global Distributed Switch Controller.
+ - description: MMCX voltage rail
+ - description: MXC or MXA voltage rail
+
+ power-domain-names:
+ items:
+ - const: top
+ - const: mmcx
+ - const: mx
+
+ vdda-0p9-supply:
+ description: Phandle to a 0.9V regulator supply to a PHY.
+
+ vdda-1p2-supply:
+ description: Phandle to 1.2V regulator supply to a PHY.
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description:
+ Sensor input. Always present. A single sensor is described by a
+ single endpoint with one to four data lanes. DPHY split mode,
+ where two independent sensors share the same PHY, is described
+ by two endpoints; endpoint@0 with exactly two data-lanes and
+ endpoint@1 with exactly one data-lane.
+ unevaluatedProperties: false
+
+ patternProperties:
+ "^endpoint(@[0-9a-f]+)?$":
+ $ref: /schemas/media/video-interfaces.yaml#
+ unevaluatedProperties: false
+ properties:
+ data-lanes:
+ minItems: 1
+ maxItems: 4
+
+ required:
+ - data-lanes
+ - remote-endpoint
+
+ allOf:
+ - if:
+ required:
+ - endpoint@1
+ then:
+ properties:
+ endpoint@0:
+ properties:
+ data-lanes:
+ minItems: 2
+ maxItems: 2
+ endpoint@1:
+ properties:
+ data-lanes:
+ maxItems: 1
+ required:
+ - endpoint@0
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Output to the CAMSS CSID controller.
+
+ required:
+ - port@0
+ - port@1
+
+required:
+ - compatible
+ - reg
+ - "#phy-cells"
+ - clocks
+ - clock-names
+ - interrupts
+ - operating-points-v2
+ - power-domains
+ - power-domain-names
+ - vdda-0p9-supply
+ - vdda-1p2-supply
+ - ports
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/qcom,x1e80100-camcc.h>
+ #include <dt-bindings/clock/qcom,x1e80100-gcc.h>
+ #include <dt-bindings/power/qcom,rpmhpd.h>
+
+ phy@ace4000 {
+ compatible = "qcom,x1e80100-csi2-phy";
+ reg = <0x0ace4000 0x2000>;
+ #phy-cells = <1>;
+
+ clocks = <&camcc CAM_CC_CSIPHY0_CLK>,
+ <&camcc CAM_CC_CSI0PHYTIMER_CLK>,
+ <&camcc CAM_CC_CORE_AHB_CLK>;
+ clock-names = "core",
+ "timer",
+ "ahb";
+
+ interrupts = <GIC_SPI 477 IRQ_TYPE_EDGE_RISING>;
+
+ operating-points-v2 = <&csiphy_opp_table>;
+
+ power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>,
+ <&rpmhpd RPMHPD_MMCX>,
+ <&rpmhpd RPMHPD_MX>;
+ power-domain-names = "top",
+ "mmcx",
+ "mx";
+
+ vdda-0p9-supply = <&vreg_l2c_0p9>;
+ vdda-1p2-supply = <&vreg_l1c_1p2>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ csiphy0_in: endpoint {
+ data-lanes = <0 1 2 3>;
+ remote-endpoint = <&sensor_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ csiphy0_out: endpoint {
+ remote-endpoint = <&csid_in>;
+ };
+ };
+ };
+
+ csiphy_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ opp-300000000 {
+ opp-hz = /bits/ 64 <300000000>;
+ required-opps = <&rpmhpd_opp_low_svs_d1>,
+ <&rpmhpd_opp_low_svs_d1>;
+ };
+ };
+ };
--
2.54.0
^ permalink raw reply related
* [PATCH v13 0/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver
From: Bryan O'Donoghue @ 2026-07-20 1:11 UTC (permalink / raw)
To: Vinod Koul, Kishon Vijay Abraham I, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong
Cc: Bryan O'Donoghue, Vladimir Zapolskiy, linux-arm-msm,
linux-phy, linux-media, devicetree, linux-kernel,
Bryan O'Donoghue, Krzysztof Kozlowski
This driver is very critical to several SoCs being merged and CPHY being
added to Qualcomm SoCs.
It would be very beneficial therefore to get this merged in this cycle. A
lot of other enablement code depends on it.
Vinod, Kishon what do you need to take this for 7.2 ?
Changes in v13:
- Fixed two sashiko "high" flagged items. Hopefully for the last time.
- Added PM_OPP to depends - Sashiko
- Link to v12: https://patch.msgid.link/20260719-x1e-csi2-phy-v12-0-cc7311326d1b@linaro.org
Changes in v12:
- Fixes indexing for data-lanes = <0 1 2 4> which is out of range - Sashiko
- The rest of Sashiko's review rebutted on list - bod
- Link to v11: https://patch.msgid.link/20260719-x1e-csi2-phy-v11-0-9d0a1ed0632d@linaro.org
Changes in v11:
- Adds RB - Krzysztof
- Fixes div_u64 to div64_u64 - Sashiko
- Fixes pos * 2 reg offset - Sashiko
- Fixes devm_pm_runtime_enable. Missed in v10 - Frank
- Link to v10: https://patch.msgid.link/20260718-x1e-csi2-phy-v10-0-5720a7888953@linaro.org
Changes in v10:
- Rewords commit log of yaml schema to read more organically - Krzysztof
- title: Qualcomm X1E80100 SoC CSI2 PHY - Krzysztof
- opp-table: object - Krzysztof
- Example opp-table in PHY node - Krzysztof
- port@1 uses stock graph.yaml - Krzysztof
- Dropped redundant remote-endpoint declaration - Krzysztof
- vdda-0p9 - SoC pins are vdda-0p9 rail is l2c_0p8. Named as with pin - Vlad, Konrad
- Retaining #phy-cells = 1.
Rob gave a steer to have the consumer configure this via DT. Not the way
Vlad suggests but I'm sticking to Rob's guidance for now - Rob/Vlad.
- data-lanes - retained as per CAMSS existing.
Nihal suggested, I agreed and Loic added his assent too. Vlad disagrees
with this. I accept Nihal's suggestion though we should remain
consistent. - Nihal, Loic, Vlad.
- pd_data = {} - Sashiko, Wengmeng
- lanes_enable() failure unwinds - Sashiko
- power_off and error paths drop OPP rate before performance state - Sashiko
- probe returns -ENODEV when pd_list is NULL with ret == 0 - Sashiko
- Move to devm_clk_bulk_get_all(), timer clock taken from the bulk,
clk_names indirection removed from soc_cfg - Frank
- Drop static CSI_COMMON_CTRL5 (0x1014) table row - Nihal
- Link to v9: https://patch.msgid.link/20260708-x1e-csi2-phy-v9-0-0210b90c04cf@linaro.org
Changes in v9:
Key advantages of implementing this logic as drivers/phy:
a) Per-PHY power rails become declarable and enforceable on a per-PHY
basis.
b) With future adaptations of CAMSS routing the PHY output to something
other than the CSI Decoders becomes possible.
c) Standard framework contract. phy_configure(mipi_dphy opts) / power_on / of_xlate.
d) Introduction of CPHY in this driver will elaborate CPHY for Linux in
general as there is currently no mipi_cphy_opts structure.
- TITAN_TOP_GDSC retained. Further elaboration in below link. - Vlad
Link: https://lore.kernel.org/all/d5407ab1-1af7-4678-ae67-5cf30ce8fa4b@kernel.org/
- Consumer selects PHY mode - Rob Herring
Link: https://lore.kernel.org/linux-media/20250710230846.GA44483-robh@kernel.org/
- Incorporates two comments by Krzysztof I missed previously - krzk
- Changes 0p9 rail name to 0p8 - Konrad, Sashiko
- Uses port@0 and port@1 to denote input/output - Vladimir
- Label phy@ instead of csiphy@ in example - Sashiko
- Drops additional entries in example opp one is enough - bod
- return 0 on timer_period_ps < 6. - Sashiko
- Use power-domain names and a "scaled" bool to flag if
a power-domain needs to be scaled or not. Allowing the code to
attached to all PDs, GDSC and RPMPD alike but only scale the
RPMPD. - Sashiko
- Fixes error cleanup flagged by Sashiko.
- Checks args->args_count. Sashiko
- Performs checks on pd_list == NULL. Sashiko
- Makes opp-table required. Sashiko
- Uses const "core" and "timer" when getting clocks. - Loic
- Adds AHB clock - Vijay
- Revert v7 lane-enable change, BIT(pos * 2) is correct: data-lanes
are logical indices, CSI_COMMON_CTRL5 is a physical bitmap - Nihal
- Adds a comment to explain clock-lane - Nihal
- Link to v8: https://patch.msgid.link/20260523-x1e-csi2-phy-v8-0-a85668459521@linaro.org
Changes in v8:
- Fixes two dt verification splats I missed by passing the wrong yaml file
to my checking script :( - Rob's bot
- Fixes the polarity offset error - thanks Sashiko.
- CONFIG_PM implies CONFIG_GENERIC_PM_DOMAINS no change - Sashiko, Bryan
- Implemented suggested unwinding by Sashiko
- Leaving the flagged settle_cnt alone. Requires invalid DT settings and in
addition the result is just a long settle count. Not real bug - Sashiko
- Link to v7: https://patch.msgid.link/20260522-x1e-csi2-phy-v7-0-79cb1280fad6@linaro.org
Changes in v7:
- Made CONFIG_PM a dependency. Sashiko commented on the pd_list being NULL
and suggested I check the pointer but, we need to ramp the rails when
switching clock rate so we need CONFIG_PM full stop. - Sashiko.ai, Bryan
- Added unwind operation for performance state error path - Sashiko
- Made clock-lanes genuinely optional for the supported use-case. - Sashiko
- Fixed the enable of lanes. Thus far we have had forever it seems
val |= BIT(lane.pos * 2) which I've never looked at much because it
has always worked. But looking at how to switch on polarities I realised
the relevant register is a linear bitmask without gaps so the correct
method is `val |= BIT(lane.pos)`.
This needs an update in the legacy PHY too in another series - Bryan
- I opted not to do any of the "but if DT send junk into your driver" fixes
from Sashiko since TBH I think the code would be Spaghetti afterwards.
We trust DT and if DT is wrong we fix it, we don't try to graph its
relative (in)sanity.
- Fixed my example in the yaml. Sashiko
- Link to v6: https://patch.msgid.link/20260521-x1e-csi2-phy-v6-0-9d73d9bd7d20@linaro.org
Changes in v6:
- Taking feedback from lively debate added ports and
endpoints to the PHY - Neil, Vlad
- Detection of split mode by way of which ports are declared.
port@0 is always a sensor input.
port@1 is optional and if present implies split-mode
port@2 is always the output. - Dmitry, Neil, Vlad.
- Split mode is left as -ENOTSUPP unless/until someone with the appropriate
hardware can take on responsibility to drive to completion.
- Extending phy_config_opts dropped.
I think this is a worthwhile extension but this series no longer depends
on it so dropped. - Bryan
- MX/MXC.
Two OPP tables one for CSIPHY0/1/2 scaling MXC one for CSIPHY4 keeping
MXA at LOWSVS_D1 - to be implemented in DT not here. Taniya, Konrad, Bryan
- Changed MAINTAINERS from Supported to Maintained.
Hobby time for me right now. - Bryan
- Link to v5: https://lore.kernel.org/r/20260326-x1e-csi2-phy-v5-0-0c0fc7f5c01b@linaro.org
v5:
- Adds support to apply passed parameters for clock/data position/polarity - Neil
- Drops GEN1/GEN2 differentiation this can be reconstituted if GEN1 ever
gets supported in this driver - Dmitry
- Drops camnoc_axi, cpas_ahb - Konrad
- Renames csiphy->core csiphy_timer->timer - Konrad
- Renames rail from 0p8 to 0p9 schematics say VDD_A_CSI_n_0P9 - Konrad
- TITAN_TOP_GDSC dropped - Konrad
- Passes PHY_QCOM_CSI2_MODE_{DPHY|CPHY|SPLIT_DPHY} with the controller
selecting the mode. Only DPHY mode is supported but the method to pass
CPHY or split-mode DPHY configuration is there.
Since split-mode is a Qualcomm specific mode the PHY modes are defined in
our binding instead of adding a new type to include/linux/phy/phy.h - bod
- Depends-on: https://lore.kernel.org/r/20260325-dphy-params-extension-v1-0-c6df5599284a@linaro.org
- Link to v4: https://lore.kernel.org/r/20260315-x1e-csi2-phy-v4-0-90c09203888d@linaro.org
v4:
- MMCX, MCX and MX/MXA power-domains added - Dmitry, Vijay, Konrad
- power-domain-names added as required - bod
- opp-tables amended to capture RPMHPD deps - Dmitry, Vijay
- Switched to dev_pm_opp_set_rate, dev_pm_domain_attach_by_name etc
dropped inherited CAMSS code - Dmitry
- Amended parameters structure to specify power-domain name list - bod
- Removed dead defines - Dmitry
- Noted in CSIPHY commit log intention to rework patterns of
PHY lane configs into loops/defines/bit-fields later - Dmitry, bod
- Lowercase hex throughout - Dmitry
- The yaml and code in this driver doesn't care if the node is a
sibling or a sub-node of CAMSS confirmed to work both ways - Dmitry, bod
- Link to v3: https://lore.kernel.org/r/20260226-x1e-csi2-phy-v3-0-11e608759410@linaro.org
v3:
- Resending this to make clear this submission is additive to x1e/Hamoa
The existing bindings and code will continue to work
Bindings are added only, nothing is subtracted from existing ABI.
- Link to v2: https://lore.kernel.org/r/20260225-x1e-csi2-phy-v2-0-7756edb67ea9@linaro.org
v2:
In this updated version
- Added operating-point support
The csiphy clock sets the OPP prior to setting the rate
for csiphy and csiphy_timer - Konrad
- Combo mode
Combo mode in CAMSS yaml has been added. Right now
no code has been changed in the PHY driver to support it as
I don't have hardware to test. In principle though it can
be supported. - Vladimir
- CSIPHY init sequences
I left these as their "magic number formats". With my diminished
status as a non-qcom VPN person - I can no longer see what the bits
map to. Moreover this is the situation any non-VPN community member
will be in when submitting CSIPHY sequences derived from downstream.
I think it is perfectly reasonable to take public CSIPHY init sequences
as magic numbers. If someone with bit-level access wants to enumerate
the bits that's fine but, it shouldn't gate in the interim. - Konrad/bod
- Sensor endpoints
I've stuck to the format used by every other CSIPHY in upstream.
Sensor endpoints hit the CAMSS/CSID endpoint not a endpoint in the PHY.
Given the proposed changes to CAMSS though to support "combo mode" I
think this should achieve the same outcome - multiple sensors on the one
PHY without introducing endpoints into the PHY that no other CSIPHY in
upstream currently has.
- Bitmask of enabled lanes
Work needs to be done in the v4l2 layer to really support this.
I propose making a separate series dedicated to non-linear bit
interpretation after merging this so as to contain the scope of the
series to something more bite (byte haha) sized. - Konrad/bod
- Link to v1: https://lore.kernel.org/r/20250710-x1e-csi2-phy-v1-0-74acbb5b162b@linaro.org
v1:
This short series adds a CSI2 MIPI PHY driver, initially supporting D-PHY
mode. The core logic and init sequences come directly from CAMSS and are
working on at least five separate x1e devices.
The rationale to instantiate CSI2 PHYs as standalone devices instead of as
sub-nodes of CAMSS is as follows.
1. Precedence
CAMSS has a dedicated I2C bus called CCI Camera Control Interface.
We model this controller as its own separate device in devicetree.
This makes sense and CCI/I2C is a well defined bus type already modelled
in Linux.
MIPI CSI2 PHY devices similarly fit into a well defined separate
bus/device structure.
Contrast to another CAMSS component such as VFE, CSID or TPG these
components only interact with other CAMSS inputs/outputs unlike CSIPHY
which interacts with non-SoC components.
2. Hardware pinouts and rails
The CSI2 PHY has its own data/clock lanes out from the SoC and indeed
has its own incoming power-rails.
3. Other devicetree schemas
There are several examples throughout the kernel of CSI PHYs modeled as
standalone devices which one assumes follows the same reasoning as given
above.
I've been working on this on-and-off since the end of April:
Link: https://lore.kernel.org/linux-media/c5cf0155-f839-4db9-b865-d39b56bb1e0a@linaro.org
There is another proposal to have the PHYs be subdevices of CAMSS but, I
believe we should go with a "full fat" PHY to match best practices in
drivers/phy/qualcomm/*.
Using the standard PHY API and the parameter passing that goes with it
allows us to move away from custom interfaces in CAMSS and to conform more
clearly to established PHY paradigms such as the QMP combo PHY.
Looking at existing compat strings I settled on
"qcom,x1e80100-mipi-csi2-combo-phy" deliberately omitting reference to the
fact the PHY is built on a four nano-meter process node, which seems to
match recent submissions to QMP PHY.
My first pass at this driver included support for the old two phase
devices:
Link: https://git.codelinaro.org/bryan.odonoghue/kernel/-/commit/a504c28d109296c93470340cfe7281231f573bcb#b6e59ed7db94c9da22e492bb03fcda6a4300983c
I realised that the device tree schema changes required to support a
comprehensive conversion of all CAMSS to this driver would be an
almost certainly be unacceptable ABI break or at the very least an enormous
amount of work and verification so I instead aimed to support just one new
SoC in the submission.
I've retained the callback indirections give us scope to add in another type of
future PHY including potentially adding in the 2PH later on.
This driver is tested and working on x1e/Hamoa and has been tested as not
breaking sc8280xp/Makena and sm8250/Kona.
Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
---
Bryan O'Donoghue (2):
dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema
phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver
.../bindings/phy/qcom,x1e80100-csi2-phy.yaml | 195 +++++++++
MAINTAINERS | 10 +
drivers/phy/qualcomm/Kconfig | 15 +
drivers/phy/qualcomm/Makefile | 5 +
drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c | 385 ++++++++++++++++++
drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c | 449 +++++++++++++++++++++
drivers/phy/qualcomm/phy-qcom-mipi-csi2.h | 97 +++++
7 files changed, 1156 insertions(+)
---
base-commit: 8dac27bfa2f994ecb11f01a63641527d17d48fc1
change-id: 20250710-x1e-csi2-phy-f6434b651d3a
Best regards,
--
Bryan O'Donoghue <bryan.odonoghue@linaro.org>
^ permalink raw reply
* Re: [PATCH 1/2] dt-bindings: iio: magnetometer: add QST QMC6308
From: Jonathan Cameron @ 2026-07-20 1:09 UTC (permalink / raw)
To: Jorijn van der Graaf
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Siratul Islam, Luca Weiss,
linux-iio, devicetree, linux-kernel
In-Reply-To: <20260714202842.340293-2-jorijnvdgraaf@catcrafts.net>
On Tue, 14 Jul 2026 22:28:41 +0200
Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net> wrote:
> Add device tree bindings for the QST QMC6308, a 3-axis AMR
> magnetometer. It is an I2C device in a 4-pin WLCSP package with a
> single supply and no interrupt pin, at I2C address 0x2c.
Hi Jorijn,
As below. Don't think we care about the package (and manufacturers
often decided to release in more packages if large customers ask for
them). I'm not sure what AMR means, so please spell that out.
A few really minor things below.
>
> Assisted-by: Claude:claude-fable-5
> Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
> ---
> .../iio/magnetometer/qstcorp,qmc6308.yaml | 48 +++++++++++++++++++
> MAINTAINERS | 6 +++
> 2 files changed, 54 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/magnetometer/qstcorp,qmc6308.yaml
>
> diff --git a/Documentation/devicetree/bindings/iio/magnetometer/qstcorp,qmc6308.yaml b/Documentation/devicetree/bindings/iio/magnetometer/qstcorp,qmc6308.yaml
> new file mode 100644
> index 000000000000..ced839d2aac3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/magnetometer/qstcorp,qmc6308.yaml
> @@ -0,0 +1,48 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/magnetometer/qstcorp,qmc6308.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: QST QMC6308 3-Axis Magnetic Sensor
> +
> +maintainers:
> + - Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
> +
> +description: |
> + QST QMC6308 3-Axis Magnetic Sensor on I2C bus. The sensor comes in a
> + 4-pin WLCSP package with a single supply and no interrupt pin.
Not sure we care about the package, but the rest is a good description.
> + https://qstcorp.com/upload/pdf/202202/13-52-15%20QMC6308%20Datasheet%20Rev.%20F(1).pdf
> +
> +properties:
> + compatible:
> + enum:
> + - qstcorp,qmc6308
Unless you plan to soon add other devices, can use the more compact.
compatible:
const: qstcorp,qmc6308
^ permalink raw reply
* Re: [PATCH 2/2] iio: magnetometer: add support for QST QMC6308
From: Jonathan Cameron @ 2026-07-20 1:06 UTC (permalink / raw)
To: Siratul Islam
Cc: Jorijn van der Graaf, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Luca Weiss, linux-iio, devicetree, linux-kernel
In-Reply-To: <ea6f4eaeb0535d607d77920515e9cb1739266624@linux.dev>
On Thu, 16 Jul 2026 19:06:51 +0000
"Siratul Islam" <siratul.islam@linux.dev> wrote:
> July 16, 2026 at 8:18 PM, "Jorijn van der Graaf" wrote:
>
> ...
> > > +#define QMC6308_AUTOSUSPEND_DELAY_MS 500
> > > You can call it QMC6308_SLEEP_DELAY_MS to align with other values.
> > >
> > I'd prefer to keep AUTOSUSPEND here: it names the mechanism the value
> > feeds (pm_runtime_set_autosuspend_delay()) and matches yamaha-yas530
> > and ak8974 in this directory.
> >
> I can find atleast 10 drivers in IIO that uses *_SLEEP_DELAY_MS postfix
> for autosuspend delay value. So it's not a strong reason not to use it.
This sort of thing tends to be author preference as both show up.
AUTOSUSPEND is clearer in purpose so if anything would be my slight
preference
> >
> ...
> > >
> > > Why not use a switch here instead of depending on 'i'. In it's
> > > current form, we would need to keep going back to check what the
> > > scales, odr, osr arrays look like.
> > > I would suggest following the same pattern as the QMC5883L driver.
> > >
> > > The arrays could also be flattened like this if you follow the
> > > above suggestion.
> > > +static const int qmc6308_odr_avail[] = { 10, 50, 100, 200 };
> > >
> > The arrays are designated-initializer tables indexed by the register
> > field code, and the same tables back read_raw(), write_raw() and
> > read_avail() - so the value <-> field-code mapping lives in one place
> > and the accepted writes stay in lockstep with the advertised
> > _available values. Switches would duplicate that mapping per
> > direction, and the flattened form only works once the tables stop
> > being indexed by field code. I'd prefer to keep the tables, but if
> > Jonathan wants the qmc5883l shape for consistency across the two
> > drivers I'm fine changing it.
> >
> I know what the code is doing here. But I'm emphasizing readability. You
> are using 'i' with double responsibility. You already have #define
> values that you are only ever using as array indecies. Using a switch
> here combining with the #define(s) would be more readable and explicit.
>
> But it is more about preferences and following existing conventions
> (i.e., the sibling driver). Jonathan, Andy, or David may have more idea
> about the "preferred" way to do this.
I'd prefer to keep them. Makes a clear association between the defaults
set (via use of defined registered value) and the value in the userspace
interface.
Jonathan
> >
> ...
> >
> > Thanks,
> > Jorijn
> >
>
> --
> Best regards,
> Sirat
^ permalink raw reply
* Re: [PATCH 2/2] iio: magnetometer: add support for QST QMC6308
From: Jonathan Cameron @ 2026-07-20 1:00 UTC (permalink / raw)
To: Jorijn van der Graaf
Cc: Uwe Kleine-König, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Siratul Islam, Luca Weiss, linux-iio, devicetree, linux-kernel
In-Reply-To: <20260715204657.46657-1-jorijnvdgraaf@catcrafts.net>
On Wed, 15 Jul 2026 22:46:57 +0200
Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net> wrote:
> On Tue, 14 Jul 2026 23:21:18 +0200, Uwe Kleine-König wrote:
> > On Tue, Jul 14, 2026 at 10:28:42PM +0200, Jorijn van der Graaf wrote:
> > > +#include <linux/i2c.h>
> > > +#include <linux/mod_devicetable.h>
> >
> > Please drop the include of <linux/mod_devicetable.h>. You need
> > i2c_device_id and of_device_id, both are provided by <linux/i2c.h>.
>
> Will do in v2, thanks,
> Jorijn
Hi Jorijn,
Welcome to IIO.
Small process thing. Don't send replies like this. They add
noise to the mailing list and we all have far too many emails
to read through! Instead put a note in the changelog for v2.
Basically we'll assume you have accepted any feedback you don't
reply to.
Note I send this email at least once a week to someone new!
Thanks,
Jonathan
^ permalink raw reply
* Re: [PATCH 1/2] dt-bindings: iio: magnetometer: add QST QMC6308
From: Jonathan Cameron @ 2026-07-20 0:58 UTC (permalink / raw)
To: Jorijn van der Graaf
Cc: Siratul Islam, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Luca Weiss,
linux-iio, devicetree, linux-kernel
In-Reply-To: <20260716141800.82761-1-jorijnvdgraaf@catcrafts.net>
On Thu, 16 Jul 2026 16:18:00 +0200
Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net> wrote:
> Hi Sirat,
>
> On Wed, 2026-07-15 at 22:16 +0000, Siratul Islam wrote:
> > > I'd lean towards keeping the separate file.
> > Wait for more feedback before deciding, since you have a few days
> > before you send v2. DT maintainers usually prefer reusing the same
> > file where possible.
>
> Right, my previous email wasn't meant to lock in a decision,
> just my preference. If the DT maintainers want it merged in v2
> I'll happily do that.
DT maintainers may hold a different view but to me it sounds like
the overlap is small enough that the complexity of combining them
may well outweigh the advantages.
Thanks,
Jonathan
>
> > Register compatibility is driver specific. Bindings are only for
> > describing hardware. So if there are reasons to use separate
> > bindings, this isn't one of them. A fallback-compatible
> > relationship isn't needed, as we are already using an enum to list
> > which different sensors the binding supports.
>
> Fair point on both counts; I'll drop that sentence. The lack of
> property overlap is the part that matters here.
>
> Thanks,
> Jorijn
^ permalink raw reply
* Re: [PATCH v4 1/3] clk: composite: Export devm composite pdata helper
From: sashiko-bot @ 2026-07-20 0:48 UTC (permalink / raw)
To: Jia Wang; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260720-ultrarisc-clock-v4-1-fe639d63bbac@ultrarisc.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Commit message inaccuracy: Non-devm pdata composite helpers are not actually exported, contradicting the commit message.
--
--- Patch [1]: [PATCH v4 1/3] clk: composite: Export devm composite pdata helper ---
commit 796e54251258da0c70103cd9b568babacd07c5ed
Author: Jia Wang <wangjia@ultrarisc.com>
clk: composite: Export devm composite pdata helper
Allow modular clock drivers to use devm_clk_hw_register_composite_pdata()
by exporting the helper.
> The non-devm composite helpers are already available to modules. Export
> this devm variant as well so users do not need to open-code devres
> cleanup.
[Severity: Medium]
Is this description accurate regarding the non-devm pdata helpers?
Looking at drivers/clk/clk-composite.c, the non-devm helper doesn't appear
to have an export annotation:
struct clk_hw *clk_hw_register_composite_pdata(struct device *dev,
const char *name,
const struct clk_parent_data *parent_data,
int num_parents,
struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
unsigned long flags)
{
return __clk_hw_register_composite(dev, name, NULL, parent_data,
num_parents, mux_hw, mux_ops,
rate_hw, rate_ops, gate_hw,
gate_ops, flags);
}
Could the commit message be updated, or should the non-devm variants be
exported as well to match the description?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720-ultrarisc-clock-v4-0-fe639d63bbac@ultrarisc.com?part=1
^ permalink raw reply
* [PATCH v4 3/3] clk: ultrarisc: Add DP1000 clock driver
From: Jia Wang via B4 Relay @ 2026-07-20 0:38 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-clk, devicetree, linux-kernel, Jia Wang
In-Reply-To: <20260720-ultrarisc-clock-v4-0-fe639d63bbac@ultrarisc.com>
From: Jia Wang <wangjia@ultrarisc.com>
Add a clock driver for the UltraRISC DP1000 SoC.
The clock tree is driven by a SYSPLL and provides fixed-factor clocks for
the subsystem and PCIe, divider-based root clocks for GMAC and the UART,
I2C, and SPI blocks, and per-instance gate clocks for UART0-3, I2C0-3,
and SPI0-1.
Signed-off-by: Jia Wang <wangjia@ultrarisc.com>
---
MAINTAINERS | 1 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/ultrarisc/Kconfig | 18 ++
drivers/clk/ultrarisc/Makefile | 4 +
drivers/clk/ultrarisc/clk-dp1000.c | 154 +++++++++++++
drivers/clk/ultrarisc/clk-ultrarisc.c | 399 ++++++++++++++++++++++++++++++++++
drivers/clk/ultrarisc/clk-ultrarisc.h | 71 ++++++
8 files changed, 649 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index d646707bb17f..d9a20d5ba13b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27676,6 +27676,7 @@ M: Jia Wang <wangjia@ultrarisc.com>
L: linux-clk@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml
+F: drivers/clk/ultrarisc/*
F: include/dt-bindings/clock/ultrarisc,dp1000-clk.h
ULTRARISC DP1000 PINCTRL DRIVER
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 1717ce75a907..b33a0d93d909 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -541,6 +541,7 @@ source "drivers/clk/tenstorrent/Kconfig"
source "drivers/clk/thead/Kconfig"
source "drivers/clk/stm32/Kconfig"
source "drivers/clk/ti/Kconfig"
+source "drivers/clk/ultrarisc/Kconfig"
source "drivers/clk/uniphier/Kconfig"
source "drivers/clk/visconti/Kconfig"
source "drivers/clk/x86/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index cc108a75a900..c66621a8b6cd 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -159,6 +159,7 @@ obj-$(CONFIG_ARCH_TEGRA) += tegra/
obj-y += tenstorrent/
obj-$(CONFIG_ARCH_THEAD) += thead/
obj-y += ti/
+obj-y += ultrarisc/
obj-$(CONFIG_CLK_UNIPHIER) += uniphier/
obj-$(CONFIG_ARCH_U8500) += ux500/
obj-y += versatile/
diff --git a/drivers/clk/ultrarisc/Kconfig b/drivers/clk/ultrarisc/Kconfig
new file mode 100644
index 000000000000..2eecc6ac3119
--- /dev/null
+++ b/drivers/clk/ultrarisc/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config CLK_ULTRARISC
+ tristate
+ depends on OF
+ depends on ARCH_ULTRARISC || COMPILE_TEST
+
+config CLK_ULTRARISC_DP1000
+ tristate "UltraRISC DP1000 clock controller"
+ select CLK_ULTRARISC
+ depends on OF && HAS_IOMEM
+ depends on ARCH_ULTRARISC || COMPILE_TEST
+ default ARCH_ULTRARISC
+ help
+ This driver provides the clock controller for the UltraRISC
+ DP1000 SoC. It exposes the PLL output, derived fixed-factor
+ clocks, programmable divider clocks, and peripheral gate
+ clocks to Linux consumers.
diff --git a/drivers/clk/ultrarisc/Makefile b/drivers/clk/ultrarisc/Makefile
new file mode 100644
index 000000000000..b013708c9444
--- /dev/null
+++ b/drivers/clk/ultrarisc/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_CLK_ULTRARISC) += clk-ultrarisc.o
+obj-$(CONFIG_CLK_ULTRARISC_DP1000) += clk-dp1000.o
diff --git a/drivers/clk/ultrarisc/clk-dp1000.c b/drivers/clk/ultrarisc/clk-dp1000.c
new file mode 100644
index 000000000000..ffea77c4cca0
--- /dev/null
+++ b/drivers/clk/ultrarisc/clk-dp1000.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
+ */
+
+#include <linux/module.h>
+
+#include <dt-bindings/clock/ultrarisc,dp1000-clk.h>
+
+#include "clk-ultrarisc.h"
+
+#define DP1000_PLL_CFG1_OFFSET 0x400
+#define DP1000_PLL_CFG2_OFFSET 0x404
+
+#define DP1000_CCR_UART_OFFSET 0x220
+#define DP1000_CCR_I2C_OFFSET 0x224
+#define DP1000_CCR_GMAC_OFFSET 0x228
+#define DP1000_CCR_SPI_OFFSET 0x22c
+#define DP1000_PERI_CLKENA_OFFSET 0x270
+
+#define DP1000_CCR_LOAD BIT(16)
+
+#define DP1000_PERI_MAX_RATE 62500000UL
+#define DP1000_CLK_NUM 21
+
+static const struct ultrarisc_pll_layout dp1000_pll_layout = {
+ .cfg1_offset = DP1000_PLL_CFG1_OFFSET,
+ .cfg2_offset = DP1000_PLL_CFG2_OFFSET,
+ .frac_mask = GENMASK(23, 0),
+ .m_mask = GENMASK(23, 16),
+ .n_mask = GENMASK(11, 6),
+ .oddiv1_mask = GENMASK(1, 0),
+ .oddiv2_mask = GENMASK(4, 3),
+};
+
+static const struct ultrarisc_pll_desc dp1000_plls[] = {
+ {
+ .id = DP1000_CLK_SYSPLL,
+ .name = "syspll_clk",
+ },
+};
+
+#define DP1000_FIXED_FACTOR(_id, _name, _parent, _mult, _div) \
+ { \
+ .id = (_id), \
+ .name = (_name), \
+ .parent_id = (_parent), \
+ .mult = (_mult), \
+ .div = (_div), \
+ }
+
+#define DP1000_DIV(_id, _name, _offset, _parent, _max_rate) \
+ { \
+ .id = (_id), \
+ .name = (_name), \
+ .offset = (_offset), \
+ .parent_id = (_parent), \
+ .max_rate = (_max_rate), \
+ .load_mask = DP1000_CCR_LOAD, \
+ .div_shift = 8, \
+ .div_width = 4, \
+ .gate_bit = 0, \
+ .divider_flags = CLK_DIVIDER_ONE_BASED, \
+ .gate_flags = 0, \
+ }
+
+#define DP1000_GATE(_id, _name, _parent, _bit) \
+ { \
+ .id = (_id), \
+ .name = (_name), \
+ .offset = DP1000_PERI_CLKENA_OFFSET, \
+ .parent_id = (_parent), \
+ .gate_bit = (_bit), \
+ .gate_flags = 0, \
+ }
+
+static const struct ultrarisc_fixed_factor_desc dp1000_fixed_factor_clks[] = {
+ DP1000_FIXED_FACTOR(DP1000_CLK_SYSPLL_DIV2, "syspll_div2_clk",
+ DP1000_CLK_SYSPLL, 1, 2),
+ DP1000_FIXED_FACTOR(DP1000_CLK_SUBSYS, "subsys_clk",
+ DP1000_CLK_SYSPLL_DIV2, 1, 2),
+ DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_DBI, "pcie_dbi_clk",
+ DP1000_CLK_SYSPLL, 1, 10),
+ DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX4_CORE, "pciex4_core_clk",
+ DP1000_CLK_SYSPLL, 1, 2),
+ DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX16_CORE, "pciex16_core_clk",
+ DP1000_CLK_SYSPLL, 1, 1),
+ DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_AUX, "pcie_aux_clk",
+ DP1000_CLK_SYSPLL, 1, 40),
+};
+
+static const struct ultrarisc_divider_desc dp1000_divider_clks[] = {
+ DP1000_DIV(DP1000_CLK_GMAC, "gmac_clk", DP1000_CCR_GMAC_OFFSET,
+ DP1000_CLK_SYSPLL_DIV2, 0),
+ DP1000_DIV(DP1000_CLK_UART_ROOT, "uart_root_clk",
+ DP1000_CCR_UART_OFFSET, DP1000_CLK_SUBSYS,
+ DP1000_PERI_MAX_RATE),
+ DP1000_DIV(DP1000_CLK_I2C_ROOT, "i2c_root_clk",
+ DP1000_CCR_I2C_OFFSET, DP1000_CLK_SUBSYS,
+ DP1000_PERI_MAX_RATE),
+ DP1000_DIV(DP1000_CLK_SPI_ROOT, "spi_root_clk",
+ DP1000_CCR_SPI_OFFSET, DP1000_CLK_SUBSYS,
+ DP1000_PERI_MAX_RATE),
+};
+
+static const struct ultrarisc_gate_desc dp1000_gate_clks[] = {
+ DP1000_GATE(DP1000_CLK_UART0, "uart0_clk", DP1000_CLK_UART_ROOT, 0),
+ DP1000_GATE(DP1000_CLK_UART1, "uart1_clk", DP1000_CLK_UART_ROOT, 1),
+ DP1000_GATE(DP1000_CLK_UART2, "uart2_clk", DP1000_CLK_UART_ROOT, 2),
+ DP1000_GATE(DP1000_CLK_UART3, "uart3_clk", DP1000_CLK_UART_ROOT, 3),
+ DP1000_GATE(DP1000_CLK_I2C0, "i2c0_clk", DP1000_CLK_I2C_ROOT, 4),
+ DP1000_GATE(DP1000_CLK_I2C1, "i2c1_clk", DP1000_CLK_I2C_ROOT, 5),
+ DP1000_GATE(DP1000_CLK_I2C2, "i2c2_clk", DP1000_CLK_I2C_ROOT, 6),
+ DP1000_GATE(DP1000_CLK_I2C3, "i2c3_clk", DP1000_CLK_I2C_ROOT, 7),
+ DP1000_GATE(DP1000_CLK_SPI0, "spi0_clk", DP1000_CLK_SPI_ROOT, 8),
+ DP1000_GATE(DP1000_CLK_SPI1, "spi1_clk", DP1000_CLK_SPI_ROOT, 9),
+};
+
+static const struct ultrarisc_clk_soc_data dp1000_clk_soc_data = {
+ .num_clks = DP1000_CLK_NUM,
+ .pll_layout = &dp1000_pll_layout,
+ .plls = dp1000_plls,
+ .num_plls = ARRAY_SIZE(dp1000_plls),
+ .fixed_factors = dp1000_fixed_factor_clks,
+ .num_fixed_factors = ARRAY_SIZE(dp1000_fixed_factor_clks),
+ .dividers = dp1000_divider_clks,
+ .num_dividers = ARRAY_SIZE(dp1000_divider_clks),
+ .gates = dp1000_gate_clks,
+ .num_gates = ARRAY_SIZE(dp1000_gate_clks),
+};
+
+static int dp1000_clk_probe(struct platform_device *pdev)
+{
+ return ultrarisc_clk_probe(pdev, &dp1000_clk_soc_data);
+}
+
+static const struct of_device_id dp1000_clk_of_match[] = {
+ { .compatible = "ultrarisc,dp1000-clk" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dp1000_clk_of_match);
+
+static struct platform_driver dp1000_clk_driver = {
+ .probe = dp1000_clk_probe,
+ .driver = {
+ .name = "ultrarisc-dp1000-clk",
+ .of_match_table = dp1000_clk_of_match,
+ },
+};
+module_platform_driver(dp1000_clk_driver);
+
+MODULE_IMPORT_NS("CLK_ULTRARISC");
+MODULE_DESCRIPTION("UltraRISC DP1000 clock controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.c b/drivers/clk/ultrarisc/clk-ultrarisc.c
new file mode 100644
index 000000000000..bbcd2234ea71
--- /dev/null
+++ b/drivers/clk/ultrarisc/clk-ultrarisc.c
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "clk-ultrarisc.h"
+
+struct ultrarisc_pll_clk {
+ struct clk_hw hw;
+ void __iomem *base;
+ const struct ultrarisc_pll_layout *layout;
+};
+
+struct ultrarisc_divider_clk {
+ struct clk_divider divider;
+ struct clk_gate gate;
+ u32 load_mask;
+};
+
+#define to_ultrarisc_pll_clk(_hw) \
+ container_of(_hw, struct ultrarisc_pll_clk, hw)
+
+static inline struct ultrarisc_divider_clk *to_ultrarisc_divider_clk(struct clk_hw *hw)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+
+ return container_of(divider, struct ultrarisc_divider_clk, divider);
+}
+
+static unsigned long ultrarisc_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct ultrarisc_pll_clk *pll = to_ultrarisc_pll_clk(hw);
+ const struct ultrarisc_pll_layout *layout = pll->layout;
+ u32 oddiv1_div, oddiv2_div;
+ u64 mult, rate, den;
+ u32 frac, m, n;
+ u32 cfg1, cfg2;
+
+ cfg1 = readl_relaxed(pll->base + layout->cfg1_offset);
+ cfg2 = readl_relaxed(pll->base + layout->cfg2_offset);
+
+ frac = field_get(layout->frac_mask, cfg1);
+ m = field_get(layout->m_mask, cfg2);
+ n = field_get(layout->n_mask, cfg2);
+ if (!n)
+ return 0;
+
+ oddiv1_div = 1U << field_get(layout->oddiv1_mask, cfg2);
+ oddiv2_div = 1U << field_get(layout->oddiv2_mask, cfg2);
+
+ /*
+ * The output frequency is calculated as:
+ * fvco = parent * (m + frac / 2^24) / n
+ * fout = fvco / (2^oddiv1_raw * 2^oddiv2_raw)
+ *
+ * The output divider values are derived from the raw register field values as:
+ * oddivX_div = 1 << oddivX_raw
+ */
+ mult = ((u64)m << 24) + frac;
+ rate = (u64)parent_rate * mult;
+ den = ((u64)n << 24) * oddiv1_div * oddiv2_div;
+
+ return div64_u64(rate + (den >> 1), den);
+}
+
+static const struct clk_ops ultrarisc_pll_ro_ops = {
+ .recalc_rate = ultrarisc_pll_recalc_rate,
+};
+
+static unsigned long ultrarisc_divider_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ u32 val;
+
+ val = readl_relaxed(divider->reg) >> divider->shift;
+ val &= clk_div_mask(divider->width);
+
+ return divider_recalc_rate(hw, parent_rate, val, divider->table,
+ divider->flags, divider->width);
+}
+
+static int ultrarisc_divider_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+
+ return divider_determine_rate(hw, req, divider->table, divider->width,
+ divider->flags);
+}
+
+static int ultrarisc_divider_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct ultrarisc_divider_clk *divider_clk = to_ultrarisc_divider_clk(hw);
+ struct clk_divider *divider = ÷r_clk->divider;
+ int value;
+ u32 val;
+
+ value = divider_get_val(rate, parent_rate, divider->table,
+ divider->width, divider->flags);
+ if (value < 0)
+ return value;
+
+ scoped_guard(spinlock_irqsave, divider->lock) {
+ val = readl_relaxed(divider->reg);
+ val &= ~(clk_div_mask(divider->width) << divider->shift);
+ val |= value << divider->shift;
+ writel_relaxed(val, divider->reg);
+
+ if (divider_clk->load_mask) {
+ /*
+ * Program the new divider field, then write 1 to the
+ * load bit to trigger the update. The load bit is
+ * write-triggered and reads back as 0 on this hardware.
+ */
+ writel_relaxed(val | divider_clk->load_mask, divider->reg);
+ }
+ }
+
+ return 0;
+}
+
+static const struct clk_ops ultrarisc_divider_ops = {
+ .recalc_rate = ultrarisc_divider_recalc_rate,
+ .determine_rate = ultrarisc_divider_determine_rate,
+ .set_rate = ultrarisc_divider_set_rate,
+};
+
+static struct clk_hw *ultrarisc_clk_register_pll(struct device *dev,
+ const struct ultrarisc_pll_desc *desc,
+ const struct ultrarisc_pll_layout *layout,
+ void __iomem *base)
+{
+ struct clk_parent_data pdata = { .index = 0 };
+ struct ultrarisc_pll_clk *pll;
+ struct clk_init_data init = {
+ .name = desc->name,
+ .ops = &ultrarisc_pll_ro_ops,
+ .parent_data = &pdata,
+ .num_parents = 1,
+ .flags = CLK_GET_RATE_NOCACHE,
+ };
+ int ret;
+
+ pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->base = base;
+ pll->layout = layout;
+ pll->hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &pll->hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &pll->hw;
+}
+
+static struct clk_hw *
+ultrarisc_clk_register_divider(struct device *dev,
+ const struct ultrarisc_divider_desc *desc,
+ struct clk_hw *parent_hw, void __iomem *base,
+ spinlock_t *lock)
+{
+ const struct clk_parent_data pdata = { .hw = parent_hw };
+ void __iomem *reg = base + desc->offset;
+ struct ultrarisc_divider_clk *divider;
+
+ if (!desc->div_width)
+ return ERR_PTR(-EINVAL);
+
+ if (!lock)
+ return ERR_PTR(-EINVAL);
+
+ divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL);
+ if (!divider)
+ return ERR_PTR(-ENOMEM);
+
+ divider->divider.reg = reg;
+ divider->divider.shift = desc->div_shift;
+ divider->divider.width = desc->div_width;
+ divider->divider.flags = desc->divider_flags;
+ divider->divider.lock = lock;
+ divider->load_mask = desc->load_mask;
+ divider->gate.reg = reg;
+ divider->gate.bit_idx = desc->gate_bit;
+ divider->gate.flags = desc->gate_flags;
+ divider->gate.lock = lock;
+
+ return devm_clk_hw_register_composite_pdata(dev, desc->name,
+ &pdata, 1, NULL, NULL,
+ ÷r->divider.hw,
+ &ultrarisc_divider_ops,
+ ÷r->gate.hw,
+ &clk_gate_ops, 0);
+}
+
+static int ultrarisc_clk_register_fixed_factors(struct device *dev,
+ struct clk_hw_onecell_data *clk_data,
+ const struct ultrarisc_clk_soc_data *soc_data)
+{
+ u32 i;
+
+ for (i = 0; i < soc_data->num_fixed_factors; i++) {
+ const struct ultrarisc_fixed_factor_desc *desc;
+ struct clk_hw *parent_hw;
+ struct clk_hw *hw;
+
+ desc = &soc_data->fixed_factors[i];
+ if (desc->id >= clk_data->num || desc->parent_id >= clk_data->num)
+ return -EINVAL;
+
+ parent_hw = clk_data->hws[desc->parent_id];
+ if (!parent_hw)
+ return -EINVAL;
+
+ hw = devm_clk_hw_register_fixed_factor_parent_hw(dev, desc->name,
+ parent_hw, 0,
+ desc->mult,
+ desc->div);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ clk_data->hws[desc->id] = hw;
+ }
+
+ return 0;
+}
+
+static int ultrarisc_clk_register_plls(struct platform_device *pdev,
+ struct clk_hw_onecell_data *clk_data,
+ const struct ultrarisc_clk_soc_data *soc_data,
+ void __iomem *base)
+{
+ struct device *dev = &pdev->dev;
+ u32 i;
+
+ for (i = 0; i < soc_data->num_plls; i++) {
+ const struct ultrarisc_pll_desc *desc = &soc_data->plls[i];
+ struct clk_hw *hw;
+
+ if (desc->id >= clk_data->num) {
+ dev_err(dev, "%s invalid clock ID %u >= %u\n",
+ desc->name, desc->id, clk_data->num);
+ return -EINVAL;
+ }
+
+ hw = ultrarisc_clk_register_pll(dev, desc, soc_data->pll_layout, base);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ clk_data->hws[desc->id] = hw;
+ }
+
+ return 0;
+}
+
+static int ultrarisc_clk_register_dividers(struct platform_device *pdev,
+ struct clk_hw_onecell_data *clk_data,
+ const struct ultrarisc_clk_soc_data *soc_data,
+ void __iomem *base,
+ spinlock_t *lock)
+{
+ struct device *dev = &pdev->dev;
+ u32 i;
+
+ for (i = 0; i < soc_data->num_dividers; i++) {
+ const struct ultrarisc_divider_desc *desc;
+ struct clk_hw *parent_hw;
+ struct clk_hw *hw;
+
+ desc = &soc_data->dividers[i];
+ if (desc->id >= clk_data->num || desc->parent_id >= clk_data->num)
+ return -EINVAL;
+
+ parent_hw = clk_data->hws[desc->parent_id];
+ if (!parent_hw)
+ return -EINVAL;
+
+ hw = ultrarisc_clk_register_divider(dev, desc, parent_hw, base,
+ lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ if (desc->max_rate)
+ clk_hw_set_rate_range(hw, 0, desc->max_rate);
+
+ clk_data->hws[desc->id] = hw;
+ }
+
+ return 0;
+}
+
+static int ultrarisc_clk_register_gates(struct platform_device *pdev,
+ struct clk_hw_onecell_data *clk_data,
+ const struct ultrarisc_clk_soc_data *soc_data,
+ void __iomem *base,
+ spinlock_t *lock)
+{
+ struct device *dev = &pdev->dev;
+ u32 i;
+
+ for (i = 0; i < soc_data->num_gates; i++) {
+ const struct ultrarisc_gate_desc *desc;
+ struct clk_hw *parent_hw;
+ struct clk_hw *hw;
+
+ desc = &soc_data->gates[i];
+ if (desc->id >= clk_data->num || desc->parent_id >= clk_data->num)
+ return -EINVAL;
+
+ parent_hw = clk_data->hws[desc->parent_id];
+ if (!parent_hw)
+ return -EINVAL;
+
+ hw = devm_clk_hw_register_gate_parent_hw(dev, desc->name,
+ parent_hw, 0,
+ base + desc->offset,
+ desc->gate_bit,
+ desc->gate_flags,
+ lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ clk_data->hws[desc->id] = hw;
+ }
+
+ return 0;
+}
+
+int ultrarisc_clk_probe(struct platform_device *pdev,
+ const struct ultrarisc_clk_soc_data *soc_data)
+{
+ struct clk_hw_onecell_data *clk_data;
+ struct device *dev = &pdev->dev;
+ void __iomem *base;
+ spinlock_t *lock;
+ int ret;
+
+ if (!soc_data)
+ return -EINVAL;
+
+ lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
+ if (!lock)
+ return -ENOMEM;
+
+ spin_lock_init(lock);
+
+ clk_data = devm_kzalloc(dev, struct_size(clk_data, hws,
+ soc_data->num_clks),
+ GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->num = soc_data->num_clks;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ ret = ultrarisc_clk_register_plls(pdev, clk_data, soc_data, base);
+ if (ret)
+ return ret;
+
+ ret = ultrarisc_clk_register_fixed_factors(dev, clk_data, soc_data);
+ if (ret)
+ return ret;
+
+ ret = ultrarisc_clk_register_dividers(pdev, clk_data, soc_data, base, lock);
+ if (ret)
+ return ret;
+
+ ret = ultrarisc_clk_register_gates(pdev, clk_data, soc_data, base, lock);
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < clk_data->num; i++) {
+ if (!clk_data->hws[i]) {
+ dev_err(dev, "missing clock ID %u\n", i);
+ return -EINVAL;
+ }
+ }
+
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
+}
+EXPORT_SYMBOL_NS_GPL(ultrarisc_clk_probe, "CLK_ULTRARISC");
+
+MODULE_DESCRIPTION("UltraRISC clock core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.h b/drivers/clk/ultrarisc/clk-ultrarisc.h
new file mode 100644
index 000000000000..8202b7ed0e31
--- /dev/null
+++ b/drivers/clk/ultrarisc/clk-ultrarisc.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __ULTRARISC_CLK_ULTRARISC_H
+#define __ULTRARISC_CLK_ULTRARISC_H
+
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+struct ultrarisc_pll_layout {
+ u32 cfg1_offset;
+ u32 cfg2_offset;
+ u32 frac_mask;
+ u32 m_mask;
+ u32 n_mask;
+ u32 oddiv1_mask;
+ u32 oddiv2_mask;
+};
+
+struct ultrarisc_pll_desc {
+ u32 id;
+ const char *name;
+};
+
+struct ultrarisc_fixed_factor_desc {
+ u32 id;
+ const char *name;
+ u32 parent_id;
+ u32 mult;
+ u32 div;
+};
+
+struct ultrarisc_divider_desc {
+ u32 id;
+ const char *name;
+ u32 offset;
+ u32 parent_id;
+ unsigned long max_rate;
+ u32 load_mask;
+ u8 div_shift;
+ u8 div_width;
+ u8 gate_bit;
+ u16 divider_flags;
+ u8 gate_flags;
+};
+
+struct ultrarisc_gate_desc {
+ u32 id;
+ const char *name;
+ u32 offset;
+ u32 parent_id;
+ u8 gate_bit;
+ u8 gate_flags;
+};
+
+struct ultrarisc_clk_soc_data {
+ const struct ultrarisc_pll_layout *pll_layout;
+ const struct ultrarisc_pll_desc *plls;
+ u32 num_plls;
+ const struct ultrarisc_fixed_factor_desc *fixed_factors;
+ u32 num_fixed_factors;
+ const struct ultrarisc_divider_desc *dividers;
+ u32 num_dividers;
+ const struct ultrarisc_gate_desc *gates;
+ u32 num_gates;
+ u32 num_clks;
+};
+
+int ultrarisc_clk_probe(struct platform_device *pdev,
+ const struct ultrarisc_clk_soc_data *soc_data);
+
+#endif /* __ULTRARISC_CLK_ULTRARISC_H */
--
2.34.1
^ permalink raw reply related
* [PATCH v4 2/3] dt-bindings: clock: ultrarisc: Add DP1000 Clock Controller
From: Jia Wang via B4 Relay @ 2026-07-20 0:38 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-clk, devicetree, linux-kernel, Jia Wang, Conor Dooley
In-Reply-To: <20260720-ultrarisc-clock-v4-0-fe639d63bbac@ultrarisc.com>
From: Jia Wang <wangjia@ultrarisc.com>
Add doc for the clock controller on the UltraRISC DP1000 RISC-V SoC.
Signed-off-by: Jia Wang <wangjia@ultrarisc.com>
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
---
.../bindings/clock/ultrarisc,dp1000-clk.yaml | 60 ++++++++++++++++++++++
MAINTAINERS | 7 +++
include/dt-bindings/clock/ultrarisc,dp1000-clk.h | 27 ++++++++++
3 files changed, 94 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml b/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml
new file mode 100644
index 000000000000..ede565ec440c
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/ultrarisc,dp1000-clk.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: UltraRISC DP1000 Clock Controller
+
+maintainers:
+ - Jia Wang <wangjia@ultrarisc.com>
+
+description: |
+ The UltraRISC DP1000 clock controller is driven from a single external
+ oscillator input. It provides a system PLL with fractional multiplier
+ and post-divider stages, several fixed-ratio derived clocks for
+ the on-chip subsystem, Clock Configuration Register (CCR) divider
+ outputs for GMAC and the UART, I2C, and SPI root clocks, and
+ per-instance gate clocks for UART0-3, I2C0-3, and SPI0-1.
+
+ All available clocks are defined as preprocessor macros in
+ include/dt-bindings/clock/ultrarisc,dp1000-clk.h
+
+properties:
+ compatible:
+ const: ultrarisc,dp1000-clk
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+ description:
+ External oscillator input clock used as the parent of the PLLs.
+
+ "#clock-cells":
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - "#clock-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/ultrarisc,dp1000-clk.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ clock-controller@11080000 {
+ compatible = "ultrarisc,dp1000-clk";
+ reg = <0x0 0x11080000 0x0 0x1000>;
+ clocks = <&osc>;
+ #clock-cells = <1>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 806bd2d80d15..d646707bb17f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27671,6 +27671,13 @@ S: Maintained
F: drivers/usb/common/ulpi.c
F: include/linux/ulpi/
+ULTRARISC DP1000 CLOCK DRIVER
+M: Jia Wang <wangjia@ultrarisc.com>
+L: linux-clk@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml
+F: include/dt-bindings/clock/ultrarisc,dp1000-clk.h
+
ULTRARISC DP1000 PINCTRL DRIVER
M: Jia Wang <wangjia@ultrarisc.com>
L: linux-gpio@vger.kernel.org
diff --git a/include/dt-bindings/clock/ultrarisc,dp1000-clk.h b/include/dt-bindings/clock/ultrarisc,dp1000-clk.h
new file mode 100644
index 000000000000..751125f99965
--- /dev/null
+++ b/include/dt-bindings/clock/ultrarisc,dp1000-clk.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+#ifndef _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H
+#define _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H
+
+#define DP1000_CLK_SYSPLL 0
+#define DP1000_CLK_SYSPLL_DIV2 1
+#define DP1000_CLK_SUBSYS 2
+#define DP1000_CLK_GMAC 3
+#define DP1000_CLK_UART_ROOT 4
+#define DP1000_CLK_I2C_ROOT 5
+#define DP1000_CLK_SPI_ROOT 6
+#define DP1000_CLK_PCIE_DBI 7
+#define DP1000_CLK_PCIEX4_CORE 8
+#define DP1000_CLK_PCIEX16_CORE 9
+#define DP1000_CLK_PCIE_AUX 10
+#define DP1000_CLK_UART0 11
+#define DP1000_CLK_UART1 12
+#define DP1000_CLK_UART2 13
+#define DP1000_CLK_UART3 14
+#define DP1000_CLK_I2C0 15
+#define DP1000_CLK_I2C1 16
+#define DP1000_CLK_I2C2 17
+#define DP1000_CLK_I2C3 18
+#define DP1000_CLK_SPI0 19
+#define DP1000_CLK_SPI1 20
+
+#endif /* _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H */
--
2.34.1
^ permalink raw reply related
* [PATCH v4 0/3] clk: ultrarisc: add DP1000 clock support
From: Jia Wang via B4 Relay @ 2026-07-20 0:38 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-clk, devicetree, linux-kernel, Jia Wang, Conor Dooley
This series exports a small common clock helper and adds the devicetree
binding and clock driver for the UltraRISC DP1000 SoC.
The clock tree is driven by a SYSPLL and provides fixed-factor clocks for
the subsystem and PCIe, divider-based root clocks for GMAC and the UART,
I2C, and SPI blocks, and per-instance gate clocks for UART0-3, I2C0-3,
and SPI0-1.
Testing:
- dt_binding_check and dtbs_check on ultrarisc,dp1000-clk
- Kernel build for RISC-V and boot-tested on DP1000
- Modules build test for CLK_ULTRARISC_DP1000
Signed-off-by: Jia Wang <wangjia@ultrarisc.com>
---
Changes in v4:
- Drop the initial clk_hw_get_rate() max-rate warning after setting the
divider rate range.
- Link to v3: https://patch.msgid.link/20260714-ultrarisc-clock-v3-0-4b1ccaa26b37@ultrarisc.com
Changes in v3:
- Add a preparatory clk core patch exporting
devm_clk_hw_register_composite_pdata().
- Use devm_clk_hw_register_composite_pdata() for DP1000 divider composite
clock registration.
- Export ultrarisc_clk_probe() in the CLK_ULTRARISC namespace and import it
from the DP1000 clock driver.
- Drop the local devres composite wrapper and use clk_parent_data for
divider parents.
- Remove the optional no-gate divider path and model DP1000 dividers as
divider+gate composites.
- Drop CLK_GET_RATE_NOCACHE from fixed-factor, gate, and divider composite
registrations where it is not needed.
- Link to v2: https://patch.msgid.link/20260617-ultrarisc-clock-v2-0-9cb16083e15e@ultrarisc.com
Changes in v2:
- Drop the redundant clock-names property from the clock binding.
- Move DP1000_CLK_NUM from the dt-bindings header into the driver.
- Clarify the divider load-bit handling in the common clock core.
- Validate that all advertised clock IDs are populated before registering
the onecell clock provider.
- Rework divider composite clock registration so the driver builds
correctly as a module.
- Link to v1: https://patch.msgid.link/20260611-ultrarisc-clock-v1-0-2d93ebb4cc13@ultrarisc.com
To: Michael Turquette <mturquette@baylibre.com>
To: Stephen Boyd <sboyd@kernel.org>
To: Brian Masney <bmasney@redhat.com>
To: Jia Wang <wangjia@ultrarisc.com>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
Cc: linux-clk@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: devicetree@vger.kernel.org
---
Jia Wang (3):
clk: composite: Export devm composite pdata helper
dt-bindings: clock: ultrarisc: Add DP1000 Clock Controller
clk: ultrarisc: Add DP1000 clock driver
.../bindings/clock/ultrarisc,dp1000-clk.yaml | 60 ++++
MAINTAINERS | 8 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-composite.c | 1 +
drivers/clk/ultrarisc/Kconfig | 18 +
drivers/clk/ultrarisc/Makefile | 4 +
drivers/clk/ultrarisc/clk-dp1000.c | 154 ++++++++
drivers/clk/ultrarisc/clk-ultrarisc.c | 399 +++++++++++++++++++++
drivers/clk/ultrarisc/clk-ultrarisc.h | 71 ++++
include/dt-bindings/clock/ultrarisc,dp1000-clk.h | 27 ++
11 files changed, 744 insertions(+)
---
base-commit: a13c140cc289c0b7b3770bce5b3ad42ab35074aa
change-id: 20260522-ultrarisc-clock-a1b7aa59f60b
Best regards,
--
Jia Wang <wangjia@ultrarisc.com>
^ permalink raw reply
* [PATCH v4 1/3] clk: composite: Export devm composite pdata helper
From: Jia Wang via B4 Relay @ 2026-07-20 0:38 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-clk, devicetree, linux-kernel, Jia Wang
In-Reply-To: <20260720-ultrarisc-clock-v4-0-fe639d63bbac@ultrarisc.com>
From: Jia Wang <wangjia@ultrarisc.com>
Allow modular clock drivers to use
devm_clk_hw_register_composite_pdata() by exporting the helper.
The non-devm composite helpers are already available to modules. Export
this devm variant as well so users do not need to open-code devres
cleanup.
Signed-off-by: Jia Wang <wangjia@ultrarisc.com>
Reviewed-by: Brian Masney <bmasney@redhat.com>
---
drivers/clk/clk-composite.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c
index 835b1e4e5869..11842bce5918 100644
--- a/drivers/clk/clk-composite.c
+++ b/drivers/clk/clk-composite.c
@@ -462,3 +462,4 @@ struct clk_hw *devm_clk_hw_register_composite_pdata(struct device *dev,
rate_hw, rate_ops, gate_hw,
gate_ops, flags);
}
+EXPORT_SYMBOL_GPL(devm_clk_hw_register_composite_pdata);
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v4 0/8] iio: adc: new ti-ads112c14 driver
From: Jonathan Cameron @ 2026-07-19 23:47 UTC (permalink / raw)
To: David Lechner
Cc: Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chris Hall, Patrick Edwards, Kurt Borja,
Nguyen Minh Tien, linux-iio, devicetree, linux-kernel,
Conor Dooley
In-Reply-To: <20260714-iio-adc-ti-ads122c14-v4-0-25f8e3084485@baylibre.com>
On Tue, 14 Jul 2026 18:21:22 -0500
David Lechner <dlechner@baylibre.com> wrote:
> This adds support for TI ADS112C14 and ADS122C14 ADC chips.
>
Applied with a couple of tweaks applied as called out in replies
to individual patches.
I thought about bouncing it back to you, but the changes were minor and
I really want to reduce the number of series in process!
Pushed out as testing. Please check it + maybe give those remaining
comments from Sashiko another look. I think we are fine, but best
to be sure.
Jonathan
> The closest thing we've seen to this in the kernel already is ads124s08.
> However, that has a completely different register map and the DT
> bindings are incomplete and the driver is extremely basic. So I've just
> started from scratch here.
>
> We've also had a similar submission recently for ADS1220 [1]. That chip
> is in a similar situation to ads124s08 in that it has a different
> register map (but the submitted DT bindings are better than the ones for
> ads124s08, even if still a bit incomplete). And literally as I was
> writing the previous sentence, another series [2] was sent for yet
> another similar family of chips (ADS1262). That one is even more complex
> in the feature set than the ones I am working on.
>
> [1]: https://lore.kernel.org/linux-iio/20260610151342.44274-1-zizuzacker@gmail.com/
> [2]: https://lore.kernel.org/linux-iio/20260612-ads126x-v1-0-894c788d03ed@gmail.com/
>
> All of these chips have in common that they are designed for use with
> RTDs and thermocouples and so they look very similar to each other in
> terms of wiring and feature set, even if the register maps are
> different. They are in the gray area where we could either keep them
> separate because they are just different enough, or we could do like
> we've done before with ad_sigma_delta and have a bit of an abstraction
> layer for the register differences and otherwise try to share as much
> code as possible. Normally, I would lean towards keeping them separate,
> but in this case, I'm considering trying to share code because the
> devicetree bindings for the inputs is complex and is going to be mostly
> the same across all of these chips.
>
> After seeing Kurt's v2 though (that doesn't attempt to share code), it
> seems like the chips are different enough that sharing code might be
> more complicated/messy than I initially thought. So I'm happy to keep
> going that route.
>
> This series includes just basic support for reading single measurements
> from the ADC and gain selection via the scale attribute. I plan to
> follow this up with additional series to add support for buffered reads,
> filtering/oversampling configuration, event support, gpio controller
> support, burnout support, DRDY interrupt support, DELAY support, CRC
> checking, external clock support.
>
> The most interesting part about this (that I alluded to above) is the
> way channels are handled. These are multipling ADCs with differential
> and single-ended inputs. But what sets them apart from other similar
> chips is that since they are designed for use with RTDs, there can also
> be a current output required to excite the RTD and this current output
> might be different for different channels. So the way I conceptualized
> the channels is that the devicetree specifies the conditions needed
> to take a particular measurement rather than being purely a physical
> channel.
>
> This makes things more flexible, but does make the driver a bit more
> complex. For example, knowing when the current output needs to be
> enabled or disabled. For now, I have chosen a lazy-enable where they
> are not turned on until the first measurement is taken that requires
> them, but then they stay on until another measurement is taken that
> doesn't require them. This can lead to some oddness with the diagnostic
> channels that may be measuring something that indirectly requires the
> current output (i.e. the external reference voltage when it is connected
> to a resistor rather than a power supply). This means you need to take
> a measurement that requires the current output to be enabled before the
> diagnostic channels will give accurate readings.
>
> I have also pushed a branch to [3] that contains the start of some
> documentation for this driver that can give some more insight into how
> the implementation works. It still needs some work and also documents
> some things that haven't been implemented yet, so I haven't included it
> in this series yet.
>
> [3]: https://github.com/dlech/linux/blob/b4/iio-adc-ti-ads122c14/Documentation/iio/ads112c14.rst
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> Changes in v4:
> - Kept the review tags on dt-bindings patchs, but made some changes to
> a few of them that are worth a quick look again just in case.
> - This didn't come up in review of this series, but in other mails on
> the list, Jonathan has been commenting on improper use of claiming
> direct mode, so I have added a mutex instead.
> - Fixed use of 64-bit scale storage on big-endian.
> - Removed burnout code (saving for later series).
> - Most other changes were minor/cosmetic. More details in each patch.
> - Link to v3: https://patch.msgid.link/20260710-iio-adc-ti-ads122c14-v3-0-746d52cbf1d0@baylibre.com
>
> Changes in v3:
> - Mostly cosmetic changes and a few bug fixes to address review feedback.
> - See individual patches for details of changes.
> - Link to v2: https://patch.msgid.link/20260625-iio-adc-ti-ads122c14-v2-0-ceb9b0b561cb@baylibre.com
>
> Changes in v2:
> - Added patches for adding properties to adc.yaml.
> - Some of these are coming from: https://lore.kernel.org/linux-iio/20260622-new-channel-props-v2-0-aafd5369f253@gmail.com/
> - For now, I have stuck with one channel per single-channel pin or
> diff-channels pin pair rather than some of the other ideas that were
> discussed. Handling burn out current enable will be handled in a later
> series. I'm leaning towards something like the _burnoutraw attribute
> that Jonathan suggested.
> - See individual patches for details of changes (mostly renaming DT
> properties, fixing some driver bugs and style issues).
> - Link to v1: https://patch.msgid.link/20260615-iio-adc-ti-ads122c14-v1-0-e6bdadf7cb2b@baylibre.com
>
> ---
> David Lechner (TI) (5):
> dt-bindings: iio: adc: add input-chopping property
> dt-bindings: iio: adc: add ti,ads122c14
> iio: adc: add ti-ads112c14 driver
> iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel
> iio: adc: ti-ads112c14: add measurement channel support
>
> Kurt Borja (3):
> dt-bindings: iio: adc: Add reference-sources property
> dt-bindings: iio: adc: Add excitation current sources properties
> dt-bindings: iio: adc: Add burn-out current properties
>
> Documentation/devicetree/bindings/iio/adc/adc.yaml | 41 +
> .../devicetree/bindings/iio/adc/ti,ads112c14.yaml | 217 ++++
> MAINTAINERS | 7 +
> drivers/iio/adc/Kconfig | 12 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/ti-ads112c14.c | 1206 ++++++++++++++++++++
> 6 files changed, 1484 insertions(+)
> ---
> base-commit: aa58ecc73466d0cb8c418de98e2225490bf600e3
> change-id: 20260514-iio-adc-ti-ads122c14-d0b92479334e
>
> Best regards,
> --
> David Lechner (TI) <dlechner@baylibre.com>
>
^ permalink raw reply
* Re: [PATCH v4 6/8] iio: adc: add ti-ads112c14 driver
From: Jonathan Cameron @ 2026-07-19 23:42 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chris Hall, Patrick Edwards, Kurt Borja,
Nguyen Minh Tien, linux-iio, devicetree, linux-kernel
In-Reply-To: <20260720000805.41022604@jic23-huawei>
On Mon, 20 Jul 2026 00:08:05 +0100
Jonathan Cameron <jonathan.cameron@oss.qualcomm.com> wrote:
> On Tue, 14 Jul 2026 18:21:28 -0500
> "David Lechner (TI)" <dlechner@baylibre.com> wrote:
>
> > Add a new driver for the TI ADS112C14/ADS122C14 ADC chips.
> >
> > This first step is adding a very basic driver that only supports power
> > on/reset and reading the system monitor channels.
> >
> > ADS112C14_SYS_MON_CHANNEL_SHORT is the last channel rather than being in
> > logical order by address to keep the voltage channels together and in
> > case we find we need to add variants of this channel with different
> > voltage reference later.
> >
> > Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
> I think sashiko is sending us on a wild goose chase on this one.
>
> "When ads112c14_single_conversion() calls i2c_smbus_read_i2c_block_data(), it
> returns the number of bytes read upon success. A short read (e.g. 1 or 2 bytes)
> would return a positive value, bypassing the (ret < 0) error check in
> ads112c14_read_raw()."
>
> An i2c_smbus_read_i2c_block_data() response doesn't contain a length
> unlike i2c_smbus_read_block_data() which does.
>
> Maybe there is a controller driver out there that messes with block[0]
> rather than returning an error on failure to do the read part of
> the sequence. I checked a few and didn't find one.
>
> It might make sense to make it clear this doesn't happen by
> adding checks in the i2c core. One to consider after the other
> ones on my list!
>
> However, one small related comment inline about keeping postive
> return values meaning success as local as possible in the code!
>
I'm having a perhaps optimistic go at tweaking this to resolve remaining
tiny issues and avoid need for a v5.
Tweak here is:
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index a69c595ab518..bc0fd6839b72 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -322,9 +322,13 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
if (ret)
return ret;
- return i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA,
- BITS_TO_BYTES(data->chip_info->resolution_bits),
- buf);
+ ret = i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA,
+ BITS_TO_BYTES(data->chip_info->resolution_bits),
+ buf);
+ if (ret < 0)
+ return ret;
+
+ return 0;
}
static int ads112c14_read_raw(struct iio_dev *indio_dev,
@@ -355,7 +359,7 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
return -EBUSY;
ret = ads112c14_single_conversion(data, chan, buf);
- if (ret < 0)
+ if (ret)
return ret;
switch (data->chip_info->resolution_bits) {
> Jonathan
>
>
>
>
> > diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> > new file mode 100644
> > index 000000000000..a69c595ab518
> > --- /dev/null
> > +++ b/drivers/iio/adc/ti-ads112c14.c
>
> ...
>
> > +
> > +static int ads112c14_single_conversion(struct ads112c14_data *data,
> > + const struct iio_chan_spec *chan,
> > + u8 *buf)
> > +{
> > + struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap));
> > + u32 reg_val;
> > + int ret;
> > +
> > + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
> > + /* Not implemented yet. */
> > + return -EINVAL;
> > + } else {
> > + ret = ads112c14_prepare_sys_mon_channel(data, chan);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
> > + ADS112C14_CONVERSION_CTRL_START);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_read_poll_timeout(data->regmap,
> > + ADS112C14_REG_STATUS_MSB, reg_val,
> > + FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val),
> > + 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC);
> > + if (ret)
> > + return ret;
> > +
> > + return i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA,
> > + BITS_TO_BYTES(data->chip_info->resolution_bits),
> > + buf);
> With all that stuff above about this not returning short, I'd still be tempted to do
> an if (ret < 0) return ret; return 0; sequence in here so we don't propogate
> confusing positive returns beyond where we can see their source.
>
> Having done that make the outer check if (ret)
>
>
> > +}
>
^ permalink raw reply related
* Re: [PATCH v4 5/8] dt-bindings: iio: adc: add ti,ads122c14
From: Jonathan Cameron @ 2026-07-19 23:39 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chris Hall, Patrick Edwards, Kurt Borja,
Nguyen Minh Tien, linux-iio, devicetree, linux-kernel,
Conor Dooley
In-Reply-To: <20260714-iio-adc-ti-ads122c14-v4-5-25f8e3084485@baylibre.com>
On Tue, 14 Jul 2026 18:21:27 -0500
"David Lechner (TI)" <dlechner@baylibre.com> wrote:
> Add new bindings for ti,ads122c14 and similar devices.
Given the binding is named ti,ads112c4 better to use that here and
in the patch title.
This briefly confused me when I was looking for the file to apply the
fix you called out.
Jonathan
^ permalink raw reply
* Re: [PATCH 0/3] Add UFS Host driver support for SpacemiT K3 SoC
From: Anirudh Srinivasan @ 2026-07-19 23:20 UTC (permalink / raw)
To: Yixun Lan
Cc: Alim Akhtar, Avri Altman, Bart Van Assche, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, James E.J. Bottomley,
Martin K. Petersen, Philipp Zabel, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, linux-scsi, devicetree, linux-riscv,
spacemit, linux-kernel
In-Reply-To: <20260713235742-GKE106000@kernel.org>
Hi Yixun,
On Mon, Jul 13, 2026 at 6:57 PM Yixun Lan <dlan@kernel.org> wrote:
>
> Hi Anirudh,
>
> On 09:56 Mon 13 Jul , Anirudh Srinivasan wrote:
> > Hi Yixun,
> >
> > On Mon, Jul 13, 2026 at 7:38 AM Yixun Lan <dlan@kernel.org> wrote:
> > >
> > > Hi Anirudh,
> > >
> > > On 22:40 Sun 12 Jul , Anirudh Srinivasan wrote:
> > > > Hi Yixun,
> > > >
> > > > On Thu, Jul 02, 2026 at 02:31:34AM +0000, Yixun Lan wrote:
> > > > > This series try to add UFS support for SpacemiT K3 SoC, the controller
> > > > > components consists of System Bus Interface Unit, UFS Host Controller
> > > > > Interface, UFS Transport Protocol Layer, UFS Host Registers, Device
> > > > > Management Entity (DME), Transport Layer, Network Layer, Data Link
> > > > > Layer, PHY Adapter Layer, and M-PHY Interface. A more detail functional
> > > > > block diagram can be found in SpacemiT website, chapter 9.7.3 [1]
> > > > >
> > > > > Please note, in order to test this driver, the UFS clock driver[2] here
> > > > > should be applied first as a prerequisite patch.
> > > > >
> > > > > One known issue is that the device will occasionally raise BKOPS interrupt
> > > > > when doing some high load test, log from dmesg shows
> > > > >
> > > > > [ 806.710763] ufshcd-spacemit c0e00000.ufshc: ufshcd_bkops_exception_event_handler: device raised urgent BKOPS exception for bkops status 1
> > > > >
> > > > > Link: https://spacemit.com/community/document/info?nodepath=hardware/key_stone/k3/k3_docs/k3_usermanual/09_memory_storage.md&lang=en [1]
> > > > > Link: https://lore.kernel.org/all/20260630-06-clk-ufs-support-v1-0-cf7521d1d0fe@kernel.org/ [2]
> > > > > Signed-off-by: Yixun Lan <dlan@kernel.org>
> > > >
> > > > I see this during probe on a k3-pico-itx. Does the UFS chip on board
> > > > have an RPMB block on it? Is this error of any concern.
> > > >
> > > It's probably true of having a RPMB block, but not used in K3 platform, so can ignore
> > >
> > > > [ 5.957864] ufshcd-spacemit c0e00000.ufshc: ufshcd_scsi_add_wlus: BOOT WLUN not found
> ..
> > > > [ 5.963319] bus_add_device: cannot add device 'ufs_rpmb0' to unregistered bus 'ufs_rpmb'
>
> Just checked code under drivers/ufs/, and couldn't find where ufs_rpmb bus is registered,
> so this kind of warning/error message is expected, or could UFS maintainer confirm this?
Seems like both of these are bugs with the existing UFS/RPMB code in
the kernel. The dev_err("BOOT WLUN not found") print should probably
be downgraded to a dev_warn. And like you mentiond above, the ufs_rpmb
bus doesn't seem to be registered before the ufs device is probed.
>
> > > > [ 5.971155] ufshcd-spacemit c0e00000.ufshc: Failed to register UFS RPMB device 0
> > > >
> > > Maybe disable CONFIG_RPMB to silent this? I've not tested this option locally
> >
> > I'm testing on a distro defconfig, so it has this option enabled.
> >
> > Is there anything we can do in the driver to have it ignore the RPMB?
>
> No, but why not disable CONFIG_RPMB in the first place
>
> > If the error is a red-herring, we shouldn't be displaying it at all in
> > the first place.
> >
>
> Either fix the code or just ignore it..
>
> --
> Yixun Lan (dlan)
^ permalink raw reply
* Re: [PATCH v4 8/8] iio: adc: ti-ads112c14: add measurement channel support
From: Jonathan Cameron @ 2026-07-19 23:18 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chris Hall, Patrick Edwards, Kurt Borja,
Nguyen Minh Tien, linux-iio, devicetree, linux-kernel
In-Reply-To: <20260714-iio-adc-ti-ads122c14-v4-8-25f8e3084485@baylibre.com>
On Tue, 14 Jul 2026 18:21:30 -0500
"David Lechner (TI)" <dlechner@baylibre.com> wrote:
> Add support for parsing devicetree properties for measurement channels
> and doing direct reads on these.
>
> There are quite a lot of conditions that have to be met for each
> measurement to be made, so quite a bit of state and algorithms are
> required to handle it.
>
> Channels are created dynamically since the number of possibilities is
> unreasonably large.
>
> Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
Huh. Sashiko had a field day on this one. Other than the earlier
mentioned 'maybe' case of the i2c_smbus stuff returning short, I'm not
seeing any of the ones I looked at being true. I particular enjoyed the
hold statement that something didn't fit in a 3 bit field. True, but
the field is 4 bits and there it does fit.
https://sashiko.dev/#/patchset/20260714-iio-adc-ti-ads122c14-v4-0-25f8e3084485%40baylibre.com
To me this all looks good.
Thanks
Jonathan
^ permalink raw reply
* Re: [PATCH v4 6/8] iio: adc: add ti-ads112c14 driver
From: Jonathan Cameron @ 2026-07-19 23:08 UTC (permalink / raw)
To: David Lechner (TI)
Cc: Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chris Hall, Patrick Edwards, Kurt Borja,
Nguyen Minh Tien, linux-iio, devicetree, linux-kernel
In-Reply-To: <20260714-iio-adc-ti-ads122c14-v4-6-25f8e3084485@baylibre.com>
On Tue, 14 Jul 2026 18:21:28 -0500
"David Lechner (TI)" <dlechner@baylibre.com> wrote:
> Add a new driver for the TI ADS112C14/ADS122C14 ADC chips.
>
> This first step is adding a very basic driver that only supports power
> on/reset and reading the system monitor channels.
>
> ADS112C14_SYS_MON_CHANNEL_SHORT is the last channel rather than being in
> logical order by address to keep the voltage channels together and in
> case we find we need to add variants of this channel with different
> voltage reference later.
>
> Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
I think sashiko is sending us on a wild goose chase on this one.
"When ads112c14_single_conversion() calls i2c_smbus_read_i2c_block_data(), it
returns the number of bytes read upon success. A short read (e.g. 1 or 2 bytes)
would return a positive value, bypassing the (ret < 0) error check in
ads112c14_read_raw()."
An i2c_smbus_read_i2c_block_data() response doesn't contain a length
unlike i2c_smbus_read_block_data() which does.
Maybe there is a controller driver out there that messes with block[0]
rather than returning an error on failure to do the read part of
the sequence. I checked a few and didn't find one.
It might make sense to make it clear this doesn't happen by
adding checks in the i2c core. One to consider after the other
ones on my list!
However, one small related comment inline about keeping postive
return values meaning success as local as possible in the code!
Jonathan
> diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> new file mode 100644
> index 000000000000..a69c595ab518
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads112c14.c
...
> +
> +static int ads112c14_single_conversion(struct ads112c14_data *data,
> + const struct iio_chan_spec *chan,
> + u8 *buf)
> +{
> + struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap));
> + u32 reg_val;
> + int ret;
> +
> + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
> + /* Not implemented yet. */
> + return -EINVAL;
> + } else {
> + ret = ads112c14_prepare_sys_mon_channel(data, chan);
> + if (ret)
> + return ret;
> + }
> +
> + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
> + ADS112C14_CONVERSION_CTRL_START);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read_poll_timeout(data->regmap,
> + ADS112C14_REG_STATUS_MSB, reg_val,
> + FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val),
> + 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC);
> + if (ret)
> + return ret;
> +
> + return i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA,
> + BITS_TO_BYTES(data->chip_info->resolution_bits),
> + buf);
With all that stuff above about this not returning short, I'd still be tempted to do
an if (ret < 0) return ret; return 0; sequence in here so we don't propogate
confusing positive returns beyond where we can see their source.
Having done that make the outer check if (ret)
> +}
^ permalink raw reply
* Re: [PATCH v1 02/11] spi: dw: update NDF while using enhanced spi mode
From: Mark Brown @ 2026-07-19 22:51 UTC (permalink / raw)
To: Changhuang Liang
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Sudip Mukherjee,
Serge Semin, linux-spi, linux-kernel, devicetree
In-Reply-To: <20260709055204.138168-3-changhuang.liang@starfivetech.com>
[-- Attachment #1: Type: text/plain, Size: 649 bytes --]
On Wed, Jul 08, 2026 at 10:51:55PM -0700, Changhuang Liang wrote:
> + else if (cfg->tmode == DW_SPI_CTRLR0_TMOD_TO &&
> + dws->caps & DW_SPI_CAP_EMODE)
> + dw_writel(dws, DW_SPI_CTRLR1, cfg->ndf);
This breaks the build:
/build/stage/linux/drivers/spi/spi-dw-core.c: In function ‘dw_spi_update_config’:
/build/stage/linux/drivers/spi/spi-dw-core.c:352:30: error: ‘DW_SPI_CAP_EMODE’ undeclared (first use in this function); did you mean ‘DW_SPI_CAP_DFS32’?
352 | dws->caps & DW_SPI_CAP_EMODE)
| ^~~~~~~~~~~~~~~~
| DW_SPI_CAP_DFS32
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH v4] dt-bindings: iio: proximity: move LIDAR-Lite out of trivial-devices
From: Jonathan Cameron @ 2026-07-19 22:41 UTC (permalink / raw)
To: David Lechner
Cc: Rodrigo Gobbi, nuno.sa, andy, robh, krzk+dt, conor+dt, mranostay,
~lkcamp/patches, linux-iio, devicetree, linux-kernel,
linux-kernel-mentees
In-Reply-To: <7b8973fd-385d-4532-8213-bb0a811081ae@baylibre.com>
> >
> > [1] https://lore.kernel.org/all/c39a2980-f5e5-44aa-9fd3-20e0658f62dc@gmail.com/
> >
> > Changelog:
> > v4: unify patches rather than using a series to keep the tree bisectable;
> > rewrite the commit msg, naming the devices explicitly;
>
> We lost the IIO driver patch though. The change adding a new compatible
> won't be accepted without it.
I think it is fully backwards compatible? If so we don't need to do
anything in the driver. We could tweak the name, but perhaps not worth
the bother in this case.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox