* [PATCH v4 10/10] pmdomain: samsung: implement domain-supply regulator
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik
In-Reply-To: <20260128-gs101-pd-v4-0-cbe7bd5a4060@linaro.org>
Some power domains on Exynos are fed by a regulator rail and therefore
regulator control needs be implemented for Exynos power domains.
On Google gs101, HSI0 (USB) is one example of such a power domain.
While at it, add a to_exynos_pd() to avoid direct use of
container_of() in various additional places, and update existing code
to use it.
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
drivers/pmdomain/samsung/exynos-pm-domains.c | 53 +++++++++++++++++++++++++---
1 file changed, 48 insertions(+), 5 deletions(-)
diff --git a/drivers/pmdomain/samsung/exynos-pm-domains.c b/drivers/pmdomain/samsung/exynos-pm-domains.c
index f59986b56213dfcc470d9cccc61a36a81954bdcc..ed7a5807555bcf86e4ec88166bfd5f6c06c9322c 100644
--- a/drivers/pmdomain/samsung/exynos-pm-domains.c
+++ b/drivers/pmdomain/samsung/exynos-pm-domains.c
@@ -20,12 +20,15 @@
#include <linux/of_address.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#define EXYNOS_SMC_CMD_PREPARE_PD_ONOFF 0x82000410
#define EXYNOS_GET_IN_PD_DOWN 0
#define EXYNOS_WAKEUP_PD_DOWN 1
#define EXYNOS_RUNTIME_PM_TZPC_GROUP 2
+#define to_exynos_pd(gpd) container_of_const(gpd, struct exynos_pm_domain, pd)
+
struct exynos_pm_domain_config {
/* Value for LOCAL_PWR_CFG and STATUS fields for each domain */
u32 local_pwr_cfg;
@@ -39,6 +42,7 @@ struct exynos_pm_domain_config {
struct exynos_pm_domain {
struct regmap *regmap;
struct device *dev;
+ struct regulator *supply;
struct generic_pm_domain pd;
const struct exynos_pm_domain_config *cfg;
u32 configuration_reg;
@@ -64,12 +68,10 @@ static int exynos_pd_access_controller_power(struct exynos_pm_domain *pd,
static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
{
- struct exynos_pm_domain *pd;
+ struct exynos_pm_domain *pd = to_exynos_pd(domain);
u32 timeout, pwr;
int err;
- pd = container_of(domain, struct exynos_pm_domain, pd);
-
if (!power_on) {
err = exynos_pd_access_controller_power(pd, power_on);
if (err) {
@@ -126,14 +128,45 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
return err;
}
+static int exynos_pd_regulator_enable(struct regulator *supply)
+{
+ return supply ? regulator_enable(supply) : 0;
+}
+
+static int exynos_pd_regulator_disable(struct regulator *supply)
+{
+ return supply ? regulator_disable(supply) : 0;
+}
+
static int exynos_pd_power_on(struct generic_pm_domain *domain)
{
- return exynos_pd_power(domain, true);
+ struct exynos_pm_domain *pd = to_exynos_pd(domain);
+ int ret;
+
+ ret = exynos_pd_regulator_enable(pd->supply);
+ if (ret)
+ return ret;
+
+ ret = exynos_pd_power(domain, true);
+ if (ret)
+ exynos_pd_regulator_disable(pd->supply);
+
+ return ret;
}
static int exynos_pd_power_off(struct generic_pm_domain *domain)
{
- return exynos_pd_power(domain, false);
+ struct exynos_pm_domain *pd = to_exynos_pd(domain);
+ int ret;
+
+ ret = exynos_pd_power(domain, false);
+ if (ret)
+ return ret;
+
+ /* Ignore regulator errors - the domain was disabled after all. */
+ exynos_pd_regulator_disable(pd->supply);
+
+ return 0;
}
static const struct exynos_pm_domain_config exynos4210_cfg = {
@@ -283,6 +316,16 @@ static int exynos_pd_probe(struct platform_device *pdev)
if (ret)
return ret;
+ /* get the domain power supply if required */
+ pd->supply = devm_regulator_get_optional(dev, "domain");
+ if (IS_ERR(pd->supply)) {
+ if (PTR_ERR(pd->supply) != -ENODEV)
+ return dev_err_probe(dev, PTR_ERR(pd->supply),
+ "failed to get domain supply");
+
+ pd->supply = NULL;
+ }
+
/*
* Some Samsung platforms with bootloaders turning on the splash-screen
* and handing it over to the kernel, requires the power-domains to be
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply related
* [PATCH v4 09/10] pmdomain: samsung: implement SMC to save / restore TZ config
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik
In-Reply-To: <20260128-gs101-pd-v4-0-cbe7bd5a4060@linaro.org>
Newer Exynos platforms have a Distributed Trust Zone Protection Control
(DTZPC) linked to each power domain. It controls the access permissions
to various registers from secure and non-secure world. An SMC call is
required to instruct the firmware that the power domain is about to be
turned off and again once it was turned on. This allows the firmware to
save and restore the DTZPC configuration. Without, register access to
various registers becomes impossible from Linux (causing SError), as
the PoR configuration doesn't allow access.
Neither the requirement for the SMC call, nor its arguments appear to
be specific to gs101, as at least Exynos E850 also uses the same as can
be seen in [1], hence prefix the new macros simply with EXYNOS_.
At least on gs101, this SMC call isn't implemented for all power
domains (e.g. it's missing for HSI2 (UFS)), therefore we issue a test
SMC to store the configuration during probe, and if it fails we mark a
domain as always-on to avoid the SErrors and to avoid unnecessarily
retrying for each domain on/off.
Link: https://lore.kernel.org/all/20230308233822.31180-4-semen.protsenko@linaro.org/ [1]
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
drivers/pmdomain/samsung/exynos-pm-domains.c | 96 ++++++++++++++++++++++++++--
1 file changed, 90 insertions(+), 6 deletions(-)
diff --git a/drivers/pmdomain/samsung/exynos-pm-domains.c b/drivers/pmdomain/samsung/exynos-pm-domains.c
index 41a232b3cdaf0f4be413b25d9373b99c6a3db602..f59986b56213dfcc470d9cccc61a36a81954bdcc 100644
--- a/drivers/pmdomain/samsung/exynos-pm-domains.c
+++ b/drivers/pmdomain/samsung/exynos-pm-domains.c
@@ -9,6 +9,7 @@
// conjunction with runtime-pm. Support for both device-tree and non-device-tree
// based power domain support is included.
+#include <linux/arm-smccc.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
@@ -16,12 +17,19 @@
#include <linux/pm_domain.h>
#include <linux/delay.h>
#include <linux/of.h>
+#include <linux/of_address.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
+#define EXYNOS_SMC_CMD_PREPARE_PD_ONOFF 0x82000410
+#define EXYNOS_GET_IN_PD_DOWN 0
+#define EXYNOS_WAKEUP_PD_DOWN 1
+#define EXYNOS_RUNTIME_PM_TZPC_GROUP 2
+
struct exynos_pm_domain_config {
/* Value for LOCAL_PWR_CFG and STATUS fields for each domain */
u32 local_pwr_cfg;
+ u32 smc_offset;
bool use_parent_regmap;
};
@@ -32,11 +40,28 @@ struct exynos_pm_domain {
struct regmap *regmap;
struct device *dev;
struct generic_pm_domain pd;
- u32 local_pwr_cfg;
+ const struct exynos_pm_domain_config *cfg;
u32 configuration_reg;
u32 status_reg;
+ phys_addr_t ac_pa;
};
+static int exynos_pd_access_controller_power(struct exynos_pm_domain *pd,
+ bool power_on)
+{
+ struct arm_smccc_res res;
+
+ if (!pd->ac_pa || !pd->cfg->smc_offset)
+ return 0;
+
+ arm_smccc_smc(EXYNOS_SMC_CMD_PREPARE_PD_ONOFF,
+ power_on ? EXYNOS_WAKEUP_PD_DOWN : EXYNOS_GET_IN_PD_DOWN,
+ pd->ac_pa + pd->cfg->smc_offset,
+ EXYNOS_RUNTIME_PM_TZPC_GROUP, 0, 0, 0, 0, &res);
+
+ return res.a0;
+}
+
static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
{
struct exynos_pm_domain *pd;
@@ -45,7 +70,17 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
pd = container_of(domain, struct exynos_pm_domain, pd);
- pwr = power_on ? pd->local_pwr_cfg : 0;
+ if (!power_on) {
+ err = exynos_pd_access_controller_power(pd, power_on);
+ if (err) {
+ dev_err(pd->dev,
+ "SMC for power domain %s %sable failed: %d\n",
+ domain->name, power_on ? "en" : "dis", err);
+ return err;
+ }
+ }
+
+ pwr = power_on ? pd->cfg->local_pwr_cfg : 0;
err = regmap_write(pd->regmap, pd->configuration_reg, pwr);
if (err) {
dev_err(pd->dev,
@@ -60,7 +95,7 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
unsigned int val;
err = regmap_read(pd->regmap, pd->status_reg, &val);
- if (err || ((val & pd->local_pwr_cfg) != pwr)) {
+ if (err || ((val & pd->cfg->local_pwr_cfg) != pwr)) {
cpu_relax();
usleep_range(80, 100);
continue;
@@ -72,9 +107,21 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
if (!timeout && !err)
/* Only return timeout if no other error also occurred. */
err = -ETIMEDOUT;
- if (err)
+ if (err) {
dev_err(pd->dev, "Power domain %s %sable failed: %d\n",
domain->name, power_on ? "en" : "dis", err);
+ return err;
+ }
+
+ if (power_on) {
+ err = exynos_pd_access_controller_power(pd, power_on);
+ if (err) {
+ dev_err(pd->dev,
+ "SMC for power domain %s %sable failed: %d\n",
+ domain->name, power_on ? "en" : "dis", err);
+ return err;
+ }
+ }
return err;
}
@@ -99,6 +146,7 @@ static const struct exynos_pm_domain_config exynos5433_cfg = {
static const struct exynos_pm_domain_config gs101_cfg = {
.local_pwr_cfg = BIT(0),
+ .smc_offset = 0x0204,
.use_parent_regmap = true,
};
@@ -126,6 +174,38 @@ static const char *exynos_get_domain_name(struct device *dev,
return devm_kstrdup_const(dev, name, GFP_KERNEL);
}
+static int exynos_pd_get_access_controller(struct exynos_pm_domain *pd)
+{
+ struct device_node *ac_np;
+ struct resource ac_res;
+ int ret;
+
+ ac_np = of_parse_phandle(pd->dev->of_node, "samsung,dtzpc", 0);
+ if (!ac_np)
+ return 0;
+
+ ret = of_address_to_resource(ac_np, 0, &ac_res);
+ of_node_put(ac_np);
+ if (ret)
+ return dev_err_probe(pd->dev, ret,
+ "failed to get access controller\n");
+
+ pd->ac_pa = ac_res.start;
+
+ /*
+ * For some domains, TZ save/restore might not be implemented. If that
+ * is the case, simply mark it as always on, as otherwise a power cycle
+ * will lead to lost TZ configuration, making it impossible to access
+ * registers from Linux afterwards.
+ */
+ if (exynos_pd_access_controller_power(pd, false) == -ENOENT) {
+ pd->ac_pa = 0;
+ pd->pd.flags |= GENPD_FLAG_ALWAYS_ON;
+ }
+
+ return 0;
+}
+
static int exynos_pd_probe(struct platform_device *pdev)
{
const struct exynos_pm_domain_config *pm_domain_cfg;
@@ -195,10 +275,14 @@ static int exynos_pd_probe(struct platform_device *pdev)
pd->pd.power_off = exynos_pd_power_off;
pd->pd.power_on = exynos_pd_power_on;
- pd->local_pwr_cfg = pm_domain_cfg->local_pwr_cfg;
+ pd->cfg = pm_domain_cfg;
pd->configuration_reg += 0;
pd->status_reg += 4;
+ ret = exynos_pd_get_access_controller(pd);
+ if (ret)
+ return ret;
+
/*
* Some Samsung platforms with bootloaders turning on the splash-screen
* and handing it over to the kernel, requires the power-domains to be
@@ -212,7 +296,7 @@ static int exynos_pd_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "failed to read status");
- on = val & pd->local_pwr_cfg;
+ on = val & pd->cfg->local_pwr_cfg;
pm_genpd_init(&pd->pd, NULL, !on);
ret = of_genpd_add_provider_simple(np, &pd->pd);
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply related
* [PATCH v4 08/10] pmdomain: samsung: use dev_err() instead of pr_err()
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik, Marek Szyprowski,
Krzysztof Kozlowski
In-Reply-To: <20260128-gs101-pd-v4-0-cbe7bd5a4060@linaro.org>
dev_err() gives us more consistent error messages, which include the
device. Switch to using dev_err().
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
drivers/pmdomain/samsung/exynos-pm-domains.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/drivers/pmdomain/samsung/exynos-pm-domains.c b/drivers/pmdomain/samsung/exynos-pm-domains.c
index 2214d9f32d5967b60e84f68f4e99a725d66a39eb..41a232b3cdaf0f4be413b25d9373b99c6a3db602 100644
--- a/drivers/pmdomain/samsung/exynos-pm-domains.c
+++ b/drivers/pmdomain/samsung/exynos-pm-domains.c
@@ -30,6 +30,7 @@ struct exynos_pm_domain_config {
*/
struct exynos_pm_domain {
struct regmap *regmap;
+ struct device *dev;
struct generic_pm_domain pd;
u32 local_pwr_cfg;
u32 configuration_reg;
@@ -47,8 +48,9 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
pwr = power_on ? pd->local_pwr_cfg : 0;
err = regmap_write(pd->regmap, pd->configuration_reg, pwr);
if (err) {
- pr_err("Regmap write for power domain %s %sable failed: %d\n",
- domain->name, power_on ? "en" : "dis", err);
+ dev_err(pd->dev,
+ "Regmap write for power domain %s %sable failed: %d\n",
+ domain->name, power_on ? "en" : "dis", err);
return err;
}
@@ -71,8 +73,8 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
/* Only return timeout if no other error also occurred. */
err = -ETIMEDOUT;
if (err)
- pr_err("Power domain %s %sable failed: %d\n", domain->name,
- power_on ? "en" : "dis", err);
+ dev_err(pd->dev, "Power domain %s %sable failed: %d\n",
+ domain->name, power_on ? "en" : "dis", err);
return err;
}
@@ -140,6 +142,8 @@ static int exynos_pd_probe(struct platform_device *pdev)
if (!pd)
return -ENOMEM;
+ pd->dev = dev;
+
pd->pd.name = exynos_get_domain_name(dev, np);
if (!pd->pd.name)
return -ENOMEM;
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply related
* [PATCH v4 07/10] pmdomain: samsung: add support for google,gs101-pd
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik, Marek Szyprowski
In-Reply-To: <20260128-gs101-pd-v4-0-cbe7bd5a4060@linaro.org>
On Google gs101, direct mmio register access to the PMU registers
doesn't work and access must happen via a regmap created by the PMU
driver instead.
Add a flag to the device match data to denote this case, and obtain
the regmap using the parent node in DT if true, while keeping to use
the traditional direct mmio regmap otherwise.
Additionally, the status is just one bit on gs101.
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
v4:
- add 'use_parent_regmap' flag instead of going by 'syscon' compatible
in parent, as it's not a given that the parent provides a syscon-
compatible regmap (it actually doesn't anymore after recent changes
on gs101)
I've still kept Marek's Tested-by from v3, as legacy Exynos code
doesn't change.
---
drivers/pmdomain/samsung/exynos-pm-domains.c | 66 +++++++++++++++++++---------
1 file changed, 46 insertions(+), 20 deletions(-)
diff --git a/drivers/pmdomain/samsung/exynos-pm-domains.c b/drivers/pmdomain/samsung/exynos-pm-domains.c
index 8df46b41f9bc8f0b2a03300169a4b52457faaf4d..2214d9f32d5967b60e84f68f4e99a725d66a39eb 100644
--- a/drivers/pmdomain/samsung/exynos-pm-domains.c
+++ b/drivers/pmdomain/samsung/exynos-pm-domains.c
@@ -12,6 +12,7 @@
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
+#include <linux/mfd/syscon.h>
#include <linux/pm_domain.h>
#include <linux/delay.h>
#include <linux/of.h>
@@ -21,6 +22,7 @@
struct exynos_pm_domain_config {
/* Value for LOCAL_PWR_CFG and STATUS fields for each domain */
u32 local_pwr_cfg;
+ bool use_parent_regmap;
};
/*
@@ -93,8 +95,16 @@ static const struct exynos_pm_domain_config exynos5433_cfg = {
.local_pwr_cfg = 0xf,
};
+static const struct exynos_pm_domain_config gs101_cfg = {
+ .local_pwr_cfg = BIT(0),
+ .use_parent_regmap = true,
+};
+
static const struct of_device_id exynos_pm_domain_of_match[] = {
{
+ .compatible = "google,gs101-pd",
+ .data = &gs101_cfg,
+ }, {
.compatible = "samsung,exynos4210-pd",
.data = &exynos4210_cfg,
}, {
@@ -122,17 +132,9 @@ static int exynos_pd_probe(struct platform_device *pdev)
struct of_phandle_args child, parent;
struct exynos_pm_domain *pd;
struct resource *res;
- void __iomem *base;
unsigned int val;
int on, ret;
- struct regmap_config reg_config = {
- .reg_bits = 32,
- .val_bits = 32,
- .reg_stride = 4,
- .use_relaxed_mmio = true,
- };
-
pm_domain_cfg = of_device_get_match_data(dev);
pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
if (!pd)
@@ -143,25 +145,49 @@ static int exynos_pd_probe(struct platform_device *pdev)
return -ENOMEM;
/*
- * The resource typically points into the address space of the PMU.
+ * The resource typically points into the address space of the PMU and
+ * we have to consider two cases:
+ * 1) some implementations require a custom regmap (from PMU parent)
+ * 2) this driver might map the same addresses as the PMU driver
* Therefore, avoid using devm_platform_get_and_ioremap_resource() and
- * instead use platform_get_resource() and devm_ioremap() to avoid
+ * instead use platform_get_resource() here, and below for case 1) use
+ * syscon_node_to_regmap() while for case 2) use devm_ioremap() to avoid
* conflicts due to address space overlap.
*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return dev_err_probe(dev, -ENXIO, "missing IO resources");
- base = devm_ioremap(dev, res->start, resource_size(res));
- if (!base)
- return dev_err_probe(dev, -ENOMEM,
- "failed to ioremap PMU registers");
-
- reg_config.max_register = resource_size(res) - reg_config.reg_stride;
- pd->regmap = devm_regmap_init_mmio(dev, base, ®_config);
- if (IS_ERR(pd->regmap))
- return dev_err_probe(dev, PTR_ERR(base),
- "failed to init regmap");
+ if (pm_domain_cfg->use_parent_regmap) {
+ pd->regmap = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(pd->regmap))
+ return dev_err_probe(dev, PTR_ERR(pd->regmap),
+ "failed to acquire PMU regmap");
+
+ pd->configuration_reg = res->start;
+ pd->status_reg = res->start;
+ } else {
+ void __iomem *base;
+
+ const struct regmap_config reg_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .use_relaxed_mmio = true,
+ .max_register = (resource_size(res)
+ - reg_config.reg_stride),
+ };
+
+ base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!base)
+ return dev_err_probe(dev, -ENOMEM,
+ "failed to ioremap PMU registers");
+
+ pd->regmap = devm_regmap_init_mmio(dev, base, ®_config);
+ if (IS_ERR(pd->regmap))
+ return dev_err_probe(dev, PTR_ERR(base),
+ "failed to init regmap");
+ }
pd->pd.power_off = exynos_pd_power_off;
pd->pd.power_on = exynos_pd_power_on;
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply related
* [PATCH v4 06/10] pmdomain: samsung: don't hard-code offset for registers to 0 and 4
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik, Marek Szyprowski
In-Reply-To: <20260128-gs101-pd-v4-0-cbe7bd5a4060@linaro.org>
On platforms such as Google gs101, direct mmio register access to the
PMU registers doesn't necessarily work and access must happen via a
regmap created by the PMU driver instead.
When such a regmap is used it will cover the complete PMU memory region
rather than individual power domains. This means the register offsets
for the configuration and status registers will have to take the power
domain offsets into account, rather than unconditionally hard-coding 0
and 4 respectively.
Update the code to allow that.
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
drivers/pmdomain/samsung/exynos-pm-domains.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/pmdomain/samsung/exynos-pm-domains.c b/drivers/pmdomain/samsung/exynos-pm-domains.c
index 3bcba7d38ac1572ef4bc2f4052c904dd6c4a7bfd..8df46b41f9bc8f0b2a03300169a4b52457faaf4d 100644
--- a/drivers/pmdomain/samsung/exynos-pm-domains.c
+++ b/drivers/pmdomain/samsung/exynos-pm-domains.c
@@ -30,6 +30,8 @@ struct exynos_pm_domain {
struct regmap *regmap;
struct generic_pm_domain pd;
u32 local_pwr_cfg;
+ u32 configuration_reg;
+ u32 status_reg;
};
static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
@@ -41,7 +43,7 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
pd = container_of(domain, struct exynos_pm_domain, pd);
pwr = power_on ? pd->local_pwr_cfg : 0;
- err = regmap_write(pd->regmap, 0, pwr);
+ err = regmap_write(pd->regmap, pd->configuration_reg, pwr);
if (err) {
pr_err("Regmap write for power domain %s %sable failed: %d\n",
domain->name, power_on ? "en" : "dis", err);
@@ -53,7 +55,7 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
while (timeout-- > 0) {
unsigned int val;
- err = regmap_read(pd->regmap, 0x4, &val);
+ err = regmap_read(pd->regmap, pd->status_reg, &val);
if (err || ((val & pd->local_pwr_cfg) != pwr)) {
cpu_relax();
usleep_range(80, 100);
@@ -164,6 +166,8 @@ static int exynos_pd_probe(struct platform_device *pdev)
pd->pd.power_off = exynos_pd_power_off;
pd->pd.power_on = exynos_pd_power_on;
pd->local_pwr_cfg = pm_domain_cfg->local_pwr_cfg;
+ pd->configuration_reg += 0;
+ pd->status_reg += 4;
/*
* Some Samsung platforms with bootloaders turning on the splash-screen
@@ -174,7 +178,7 @@ static int exynos_pd_probe(struct platform_device *pdev)
of_device_is_compatible(np, "samsung,exynos4210-pd"))
exynos_pd_power_off(&pd->pd);
- ret = regmap_read(pd->regmap, 0x4, &val);
+ ret = regmap_read(pd->regmap, pd->status_reg, &val);
if (ret)
return dev_err_probe(dev, ret, "failed to read status");
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply related
* [PATCH v4 05/10] pmdomain: samsung: convert to using regmap
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik, Marek Szyprowski
In-Reply-To: <20260128-gs101-pd-v4-0-cbe7bd5a4060@linaro.org>
On platforms such as Google gs101, direct mmio register access to the
PMU registers doesn't necessarily work and access must happen via a
regmap created by the PMU driver instead.
In preparation for supporting such SoCs convert the existing mmio
accesses to using a regmap wrapper.
With this change in place, a follow-up patch can update the driver to
optionally acquire the PMU-created regmap without having to change the
rest of the code.
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
There is one checkpatch warning, relating to the non-const
regmap_config. It can not easily be made const at this stage, but a
follow-up patch allows us to make it const and the warning will go away
anyway.
v4:
- rework the loop in exynos_pd_power() slightly, to not return 0 early
to allow more code to be run after pd on/off register write without
changing the loop again, required for gs101.
- add error message in case first regmap write in exynos_pd_power() fails
---
drivers/pmdomain/samsung/exynos-pm-domains.c | 83 +++++++++++++++++++++-------
1 file changed, 62 insertions(+), 21 deletions(-)
diff --git a/drivers/pmdomain/samsung/exynos-pm-domains.c b/drivers/pmdomain/samsung/exynos-pm-domains.c
index 5c3aa898308793d424fb761c58ca01ba2580aeb5..3bcba7d38ac1572ef4bc2f4052c904dd6c4a7bfd 100644
--- a/drivers/pmdomain/samsung/exynos-pm-domains.c
+++ b/drivers/pmdomain/samsung/exynos-pm-domains.c
@@ -9,15 +9,14 @@
// conjunction with runtime-pm. Support for both device-tree and non-device-tree
// based power domain support is included.
-#include <linux/io.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/pm_domain.h>
#include <linux/delay.h>
#include <linux/of.h>
-#include <linux/of_address.h>
#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
struct exynos_pm_domain_config {
/* Value for LOCAL_PWR_CFG and STATUS fields for each domain */
@@ -28,7 +27,7 @@ struct exynos_pm_domain_config {
* Exynos specific wrapper around the generic power domain
*/
struct exynos_pm_domain {
- void __iomem *base;
+ struct regmap *regmap;
struct generic_pm_domain pd;
u32 local_pwr_cfg;
};
@@ -36,31 +35,42 @@ struct exynos_pm_domain {
static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
{
struct exynos_pm_domain *pd;
- void __iomem *base;
u32 timeout, pwr;
- char *op;
+ int err;
pd = container_of(domain, struct exynos_pm_domain, pd);
- base = pd->base;
pwr = power_on ? pd->local_pwr_cfg : 0;
- writel_relaxed(pwr, base);
+ err = regmap_write(pd->regmap, 0, pwr);
+ if (err) {
+ pr_err("Regmap write for power domain %s %sable failed: %d\n",
+ domain->name, power_on ? "en" : "dis", err);
+ return err;
+ }
/* Wait max 1ms */
timeout = 10;
-
- while ((readl_relaxed(base + 0x4) & pd->local_pwr_cfg) != pwr) {
- if (!timeout) {
- op = (power_on) ? "enable" : "disable";
- pr_err("Power domain %s %s failed\n", domain->name, op);
- return -ETIMEDOUT;
+ while (timeout-- > 0) {
+ unsigned int val;
+
+ err = regmap_read(pd->regmap, 0x4, &val);
+ if (err || ((val & pd->local_pwr_cfg) != pwr)) {
+ cpu_relax();
+ usleep_range(80, 100);
+ continue;
}
- timeout--;
- cpu_relax();
- usleep_range(80, 100);
+
+ break;
}
- return 0;
+ if (!timeout && !err)
+ /* Only return timeout if no other error also occurred. */
+ err = -ETIMEDOUT;
+ if (err)
+ pr_err("Power domain %s %sable failed: %d\n", domain->name,
+ power_on ? "en" : "dis", err);
+
+ return err;
}
static int exynos_pd_power_on(struct generic_pm_domain *domain)
@@ -109,8 +119,18 @@ static int exynos_pd_probe(struct platform_device *pdev)
struct device_node *np = dev->of_node;
struct of_phandle_args child, parent;
struct exynos_pm_domain *pd;
+ struct resource *res;
+ void __iomem *base;
+ unsigned int val;
int on, ret;
+ struct regmap_config reg_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .use_relaxed_mmio = true,
+ };
+
pm_domain_cfg = of_device_get_match_data(dev);
pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
if (!pd)
@@ -120,9 +140,26 @@ static int exynos_pd_probe(struct platform_device *pdev)
if (!pd->pd.name)
return -ENOMEM;
- pd->base = of_iomap(np, 0);
- if (!pd->base)
- return -ENODEV;
+ /*
+ * The resource typically points into the address space of the PMU.
+ * Therefore, avoid using devm_platform_get_and_ioremap_resource() and
+ * instead use platform_get_resource() and devm_ioremap() to avoid
+ * conflicts due to address space overlap.
+ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return dev_err_probe(dev, -ENXIO, "missing IO resources");
+
+ base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!base)
+ return dev_err_probe(dev, -ENOMEM,
+ "failed to ioremap PMU registers");
+
+ reg_config.max_register = resource_size(res) - reg_config.reg_stride;
+ pd->regmap = devm_regmap_init_mmio(dev, base, ®_config);
+ if (IS_ERR(pd->regmap))
+ return dev_err_probe(dev, PTR_ERR(base),
+ "failed to init regmap");
pd->pd.power_off = exynos_pd_power_off;
pd->pd.power_on = exynos_pd_power_on;
@@ -137,7 +174,11 @@ static int exynos_pd_probe(struct platform_device *pdev)
of_device_is_compatible(np, "samsung,exynos4210-pd"))
exynos_pd_power_off(&pd->pd);
- on = readl_relaxed(pd->base + 0x4) & pd->local_pwr_cfg;
+ ret = regmap_read(pd->regmap, 0x4, &val);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read status");
+
+ on = val & pd->local_pwr_cfg;
pm_genpd_init(&pd->pd, NULL, !on);
ret = of_genpd_add_provider_simple(np, &pd->pd);
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply related
* [PATCH v4 04/10] dt-bindings: soc: google: gs101-pmu: allow power domains as children
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik, Marek Szyprowski
In-Reply-To: <20260128-gs101-pd-v4-0-cbe7bd5a4060@linaro.org>
The power domains are a property of / implemented in the PMU. As such,
they should be modelled as child nodes of the PMU.
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
v4:
- consistent quoting using " (Krzysztof)
- add samsung,dtzpc to example
Note: Ideally, the newly added properties (ranges, etc.) should only be
'required' if "^power-domain@[0-9a-f]+$" exists as a patternProperty,
as they're needed only in that case. As-is, this patch now causes
warnings for existing DTs as they don't specify the new properties (and
they shouldn't need to). Only if DTs are updated to include
power-domains, such an update should also add the new properties.
I've not been able to come up with the correct schema syntax to achieve
that. dependencies, dependentRequired, and dependentSchemas don't seem
to support patterns. Similarly,
- if:
required:
- ...
then:
required:
- ...
doesn't allow patterns in the 'if' block (or I didn't get the syntax
right).
Rob said in
https://lore.kernel.org/all/20251010141357.GA219719-robh@kernel.org/
that this is a known limitation in json-schema.
---
.../bindings/soc/google/google,gs101-pmu.yaml | 41 ++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/Documentation/devicetree/bindings/soc/google/google,gs101-pmu.yaml b/Documentation/devicetree/bindings/soc/google/google,gs101-pmu.yaml
index a06bd8ec3c20faf0b364d3d3ef1763502c2b09cf..c1ee9575092a3ab17873d228a88468addb62d838 100644
--- a/Documentation/devicetree/bindings/soc/google/google,gs101-pmu.yaml
+++ b/Documentation/devicetree/bindings/soc/google/google,gs101-pmu.yaml
@@ -16,6 +16,14 @@ properties:
reg:
maxItems: 1
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 1
+
+ ranges: true
+
reboot-mode:
$ref: /schemas/power/reset/syscon-reboot-mode.yaml
type: object
@@ -39,9 +47,23 @@ properties:
description:
Phandle to PMU interrupt generation interface.
+patternProperties:
+ "^power-domain@[0-9a-f]+$":
+ type: object
+ description: Child node describing one power domain within the PMU
+
+ additionalProperties: true
+
+ properties:
+ compatible:
+ const: google,gs101-pd
+
required:
- compatible
- reg
+ - '#address-cells'
+ - '#size-cells'
+ - ranges
- google,pmu-intr-gen-syscon
additionalProperties: false
@@ -51,6 +73,25 @@ examples:
system-controller@17460000 {
compatible = "google,gs101-pmu";
reg = <0x17460000 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
google,pmu-intr-gen-syscon = <&pmu_intr_gen>;
+
+ pd_g3d: power-domain@1e00 {
+ compatible = "google,gs101-pd";
+ reg = <0x1e00 0x80>;
+ #power-domain-cells = <0>;
+ label = "g3d";
+ samsung,dtzpc = <&pd_g3d>;
+ };
+
+ power-domain@2000 {
+ compatible = "google,gs101-pd";
+ reg = <0x2000 0x80>;
+ #power-domain-cells = <0>;
+ power-domains = <&pd_g3d>;
+ label = "embedded_g3d";
+ };
};
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply related
* [PATCH v4 03/10] dt-bindings: soc: samsung: exynos-pmu: move gs101-pmu into separate binding
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik, Marek Szyprowski
In-Reply-To: <20260128-gs101-pd-v4-0-cbe7bd5a4060@linaro.org>
The gs101-pmu binding is going to acquire various additional (pattern)
properties that don't apply to other PMUs supported by this binding.
To enable this, move google,gs101-pmu into a separate binding.
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
v4:
- update since 'syscon' was removed from gs101-pmu compatibles
- update since 'select:' was removed from google,gs101-pmu.yaml
v3:
- use additionalProperties, not unevaluatedProperties
- fix path in $id (Rob)
- drop comment around 'select' (Rob)
---
.../bindings/soc/google/google,gs101-pmu.yaml | 56 ++++++++++++++++++++++
.../bindings/soc/samsung/exynos-pmu.yaml | 20 --------
MAINTAINERS | 1 +
3 files changed, 57 insertions(+), 20 deletions(-)
diff --git a/Documentation/devicetree/bindings/soc/google/google,gs101-pmu.yaml b/Documentation/devicetree/bindings/soc/google/google,gs101-pmu.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a06bd8ec3c20faf0b364d3d3ef1763502c2b09cf
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/google/google,gs101-pmu.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/google/google,gs101-pmu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Google GS101 Power Management Unit (PMU)
+
+maintainers:
+ - André Draszik <andre.draszik@linaro.org>
+
+properties:
+ compatible:
+ const: google,gs101-pmu
+
+ reg:
+ maxItems: 1
+
+ reboot-mode:
+ $ref: /schemas/power/reset/syscon-reboot-mode.yaml
+ type: object
+ description:
+ Reboot mode to alter bootloader behavior for the next boot
+
+ syscon-poweroff:
+ $ref: /schemas/power/reset/syscon-poweroff.yaml#
+ type: object
+ description:
+ Node for power off method
+
+ syscon-reboot:
+ $ref: /schemas/power/reset/syscon-reboot.yaml#
+ type: object
+ description:
+ Node for reboot method
+
+ google,pmu-intr-gen-syscon:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to PMU interrupt generation interface.
+
+required:
+ - compatible
+ - reg
+ - google,pmu-intr-gen-syscon
+
+additionalProperties: false
+
+examples:
+ - |
+ system-controller@17460000 {
+ compatible = "google,gs101-pmu";
+ reg = <0x17460000 0x10000>;
+
+ google,pmu-intr-gen-syscon = <&pmu_intr_gen>;
+ };
diff --git a/Documentation/devicetree/bindings/soc/samsung/exynos-pmu.yaml b/Documentation/devicetree/bindings/soc/samsung/exynos-pmu.yaml
index 76ce7e98c10f3738e7de5579904260dd906507c9..09368dbb6de6898e2988c398b2506f2c2b4e73bc 100644
--- a/Documentation/devicetree/bindings/soc/samsung/exynos-pmu.yaml
+++ b/Documentation/devicetree/bindings/soc/samsung/exynos-pmu.yaml
@@ -12,8 +12,6 @@ maintainers:
properties:
compatible:
oneOf:
- - enum:
- - google,gs101-pmu
- items:
- enum:
- samsung,exynos3250-pmu
@@ -110,11 +108,6 @@ properties:
description:
Node for reboot method
- google,pmu-intr-gen-syscon:
- $ref: /schemas/types.yaml#/definitions/phandle
- description:
- Phandle to PMU interrupt generation interface.
-
required:
- compatible
- reg
@@ -176,19 +169,6 @@ allOf:
properties:
dp-phy: false
- - if:
- properties:
- compatible:
- contains:
- enum:
- - google,gs101-pmu
- then:
- required:
- - google,pmu-intr-gen-syscon
- else:
- properties:
- google,pmu-intr-gen-syscon: false
-
examples:
- |
#include <dt-bindings/clock/exynos5250.h>
diff --git a/MAINTAINERS b/MAINTAINERS
index 98b2ef47c809ac0232e6941c9483b19d7c798bb4..79a58769e9cb33d9bd6190c74d55461e77965af5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10834,6 +10834,7 @@ C: irc://irc.oftc.net/pixel6-kernel-dev
F: Documentation/devicetree/bindings/clock/google,gs101-clock.yaml
F: Documentation/devicetree/bindings/phy/google,lga-usb-phy.yaml
F: Documentation/devicetree/bindings/soc/google/google,gs101-dtzpc.yaml
+F: Documentation/devicetree/bindings/soc/google/google,gs101-pmu.yaml
F: Documentation/devicetree/bindings/soc/google/google,gs101-pmu-intr-gen.yaml
F: Documentation/devicetree/bindings/usb/google,lga-dwc3.yaml
F: arch/arm64/boot/dts/exynos/google/
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply related
* [PATCH v4 02/10] dt-bindings: power: samsung: add google,gs101-pd
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik
In-Reply-To: <20260128-gs101-pd-v4-0-cbe7bd5a4060@linaro.org>
Add support for the Google gs101 version of the Exynos power domains. A
new compatible is needed because register fields have changed and
because power domain operations involve interfacing with the TrustZone
protection control on newer Exynos SoCs.
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
v4:
- add new vendor property samsung,dtzpc
- drop previous tags due to that
---
.../devicetree/bindings/power/pd-samsung.yaml | 29 +++++++++++++++++++---
1 file changed, 26 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/power/pd-samsung.yaml b/Documentation/devicetree/bindings/power/pd-samsung.yaml
index 9c2c51133457112ca0098c043e123f0a02fa1291..4ba555e11b30e6a9aaed457bcb57765bf5b481e3 100644
--- a/Documentation/devicetree/bindings/power/pd-samsung.yaml
+++ b/Documentation/devicetree/bindings/power/pd-samsung.yaml
@@ -13,12 +13,10 @@ description: |+
Exynos processors include support for multiple power domains which are used
to gate power to one or more peripherals on the processor.
-allOf:
- - $ref: power-domain.yaml#
-
properties:
compatible:
enum:
+ - google,gs101-pd
- samsung,exynos4210-pd
- samsung,exynos5433-pd
@@ -44,11 +42,28 @@ properties:
power-domains:
maxItems: 1
+ samsung,dtzpc:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Distributed TrustZone Protection Control (DTZPC) node.
+
required:
- compatible
- "#power-domain-cells"
- reg
+allOf:
+ - $ref: power-domain.yaml#
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ const: google,gs101-pd
+ then:
+ properties:
+ samsung,dtzpc: false
+
unevaluatedProperties: false
examples:
@@ -66,3 +81,11 @@ examples:
#power-domain-cells = <0>;
label = "MFC";
};
+
+ power-domain@2080 {
+ compatible = "google,gs101-pd";
+ reg = <0x2080 0x80>;
+ #power-domain-cells = <0>;
+ label = "hsi0";
+ samsung,dtzpc = <&dtzpc_hsi0>;
+ };
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply related
* [PATCH v4 01/10] dt-bindings: soc: google: add google,gs101-dtzpc
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik
In-Reply-To: <20260128-gs101-pd-v4-0-cbe7bd5a4060@linaro.org>
The Exynos Distributed TruztZone Protection Control (D_TZPC) provides
an interface to the protection bits that are included in the TrustZone
design in a secure system. It configures each area of the memory as
secure or non-secure.
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
.../bindings/soc/google/google,gs101-dtzpc.yaml | 42 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 43 insertions(+)
diff --git a/Documentation/devicetree/bindings/soc/google/google,gs101-dtzpc.yaml b/Documentation/devicetree/bindings/soc/google/google,gs101-dtzpc.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a8c61ce069d6910c47753bf14a792eb58e6ae182
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/google/google,gs101-dtzpc.yaml
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/google/google,gs101-dtzpc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Samsung Exynos Distributed TruztZone Protection Control.
+
+description:
+ Distributed TrustZone Protection Control (D_TZPC) provides an interface to the
+ protection bits that are included in the TrustZone design in a secure system.
+ It configures each area of the memory as secure or non-secure.
+
+maintainers:
+ - André Draszik <andre.draszik@linaro.org>
+
+properties:
+ compatible:
+ const: google,gs101-dtzpc
+
+ clocks:
+ maxItems: 1
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - clocks
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/google,gs101.h>
+
+ dtzpc_hsi0: dtzpc@11010000 {
+ compatible = "google,gs101-dtzpc";
+ reg = <0x11010000 0x10000>;
+ clocks = <&cmu_hsi0 CLK_GOUT_HSI0_D_TZPC_HSI0_PCLK>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index a56f8f00aebb938aa765a8a6d66dfeb7f062dac8..98b2ef47c809ac0232e6941c9483b19d7c798bb4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10833,6 +10833,7 @@ P: Documentation/process/maintainer-soc-clean-dts.rst
C: irc://irc.oftc.net/pixel6-kernel-dev
F: Documentation/devicetree/bindings/clock/google,gs101-clock.yaml
F: Documentation/devicetree/bindings/phy/google,lga-usb-phy.yaml
+F: Documentation/devicetree/bindings/soc/google/google,gs101-dtzpc.yaml
F: Documentation/devicetree/bindings/soc/google/google,gs101-pmu-intr-gen.yaml
F: Documentation/devicetree/bindings/usb/google,lga-dwc3.yaml
F: arch/arm64/boot/dts/exynos/google/
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply related
* [PATCH v4 00/10] pmdomain: samsung: add support for Google GS101
From: André Draszik @ 2026-01-28 16:10 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alim Akhtar, Rob Herring, Conor Dooley,
Krzysztof Kozlowski, Ulf Hansson, Liam Girdwood, Mark Brown
Cc: Peter Griffin, Tudor Ambarus, Juan Yescas, Will McVicker,
kernel-team, linux-arm-kernel, linux-samsung-soc, devicetree,
linux-kernel, linux-pm, André Draszik, Marek Szyprowski,
Krzysztof Kozlowski
Hi,
This series adds support for the power domains on Google GS101.
There are a few differences compared to SoCs already supported by this
driver:
* register access does not work via plain ioremap() / readl() /
writel().
Instead, the regmap created by the PMU driver must be used (which
uses Arm SMCC calls under the hood).
* DTZPC: a call needs to be made before and after power domain off/on,
to inform the EL3 firmware of the request.
* power domains can and are fed by a regulator rail and therefore
regulator control needed be implemented.
Bullet points 2 and 3 are new compared to previous versions of this
series, and related changes are in patches 1, 2, 9, and 10. I can merge
patch 9 (SMC call) into the gs101 patch (patch 7) if preferred, but for
now I kept them independent to make it easier to see changes compared
to previous versions of this series, and because patch 8 actually
applies to not only gs101, but to many newer Exynos SoCs, and to make
the two patches themselves easier to review and reason about.
The DT update to add the new required properties on gs101 will be
posted separately.
Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
Changes in v4:
- drop unneeded or already merged patches
- drop patch "pmdomain: samsung: convert to regmap_read_poll_timeout()"
as Marek reported issues on some platforms
- rebase
- DTZPC related changes
- Link to v3: https://lore.kernel.org/r/20251016-gs101-pd-v3-0-7b30797396e7@linaro.org
Changes in v3:
- use additionalProperties, not unevaluatedProperties in patch 2
- fix path in $id in patch 2 (Rob)
- drop comment around 'select' in patch 2 (Rob)
- collect tags
- Link to v2: https://lore.kernel.org/r/20251009-gs101-pd-v2-0-3f4a6db2af39@linaro.org
Changes in v2:
- Krzysztof:
- move google,gs101-pmu binding into separate file
- mark devm_kstrdup_const() patch as fix
- use bool for need_early_sync_state
- merge patches 8 and 10 from v1 series into one patch
- collect tags
- Link to v1: https://lore.kernel.org/r/20251006-gs101-pd-v1-0-f0cb0c01ea7b@linaro.org
---
André Draszik (10):
dt-bindings: soc: google: add google,gs101-dtzpc
dt-bindings: power: samsung: add google,gs101-pd
dt-bindings: soc: samsung: exynos-pmu: move gs101-pmu into separate binding
dt-bindings: soc: google: gs101-pmu: allow power domains as children
pmdomain: samsung: convert to using regmap
pmdomain: samsung: don't hard-code offset for registers to 0 and 4
pmdomain: samsung: add support for google,gs101-pd
pmdomain: samsung: use dev_err() instead of pr_err()
pmdomain: samsung: implement SMC to save / restore TZ config
pmdomain: samsung: implement domain-supply regulator
.../devicetree/bindings/power/pd-samsung.yaml | 29 ++-
.../bindings/soc/google/google,gs101-dtzpc.yaml | 42 ++++
.../bindings/soc/google/google,gs101-pmu.yaml | 97 ++++++++
.../bindings/soc/samsung/exynos-pmu.yaml | 20 --
MAINTAINERS | 2 +
drivers/pmdomain/samsung/exynos-pm-domains.c | 254 ++++++++++++++++++---
6 files changed, 395 insertions(+), 49 deletions(-)
---
base-commit: e3b32dcb9f23e3c3927ef3eec6a5842a988fb574
change-id: 20251001-gs101-pd-d4dc97d70a84
Best regards,
--
André Draszik <andre.draszik@linaro.org>
^ permalink raw reply
* Re: [rafael-pm:bleeding-edge 172/186] kernel/time/hrtimer.c:946:48: error: implicit declaration of function 'tick_nohz_is_active'; did you mean 'tick_nohz_init'?
From: Frederic Weisbecker @ 2026-01-28 16:05 UTC (permalink / raw)
To: kernel test robot
Cc: oe-kbuild-all, linux-acpi, linux-pm, Rafael J. Wysocki,
Thomas Gleixner, Anna-Maria Behnsen
In-Reply-To: <202601240853.XfwHlHep-lkp@intel.com>
Le Sat, Jan 24, 2026 at 08:59:57AM +0800, kernel test robot a écrit :
> tree: https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git bleeding-edge
> head: 7e9b0371ed5b9bf9a80c59487f47fca0ba638f61
> commit: b0d640cf14148cbce9f1651fe6028c7586291cf5 [172/186] cpufreq: ondemand: Simplify idle cputime granularity test
> config: nios2-allnoconfig (https://download.01.org/0day-ci/archive/20260124/202601240853.XfwHlHep-lkp@intel.com/config)
> compiler: nios2-linux-gcc (GCC) 11.5.0
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260124/202601240853.XfwHlHep-lkp@intel.com/reproduce)
>
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Closes: https://lore.kernel.org/oe-kbuild-all/202601240853.XfwHlHep-lkp@intel.com/
>
> All errors (new ones prefixed by >>):
>
> kernel/time/hrtimer.c: In function 'clock_was_set':
> >> kernel/time/hrtimer.c:946:48: error: implicit declaration of function 'tick_nohz_is_active'; did you mean 'tick_nohz_init'? [-Werror=implicit-function-declaration]
> 946 | if (!hrtimer_hres_active(cpu_base) && !tick_nohz_is_active())
> | ^~~~~~~~~~~~~~~~~~~
> | tick_nohz_init
Oops, ok here is an update. Hopefully I got it right this time:
---
From: Frederic Weisbecker <frederic@kernel.org>
Date: Wed, 7 Jan 2026 17:25:09 +0100
Subject: [PATCH] cpufreq: ondemand: Simplify idle cputime granularity test
cpufreq calls get_cpu_idle_time_us() just to know if idle cputime
accounting has a nanoseconds granularity.
Use the appropriate indicator instead to make that deduction.
Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
Cc: "Rafael J. Wysocki" <rafael@kernel.org>
Cc: Viresh Kumar <viresh.kumar@linaro.org>
Cc: linux-pm@vger.kernel.org
---
drivers/cpufreq/cpufreq_ondemand.c | 7 +------
include/linux/tick.h | 2 ++
kernel/time/hrtimer.c | 2 +-
kernel/time/tick-internal.h | 2 --
kernel/time/tick-sched.c | 8 +++++++-
kernel/time/timer.c | 2 +-
6 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/drivers/cpufreq/cpufreq_ondemand.c b/drivers/cpufreq/cpufreq_ondemand.c
index a6ecc203f7b7..bb7db82930e4 100644
--- a/drivers/cpufreq/cpufreq_ondemand.c
+++ b/drivers/cpufreq/cpufreq_ondemand.c
@@ -334,17 +334,12 @@ static void od_free(struct policy_dbs_info *policy_dbs)
static int od_init(struct dbs_data *dbs_data)
{
struct od_dbs_tuners *tuners;
- u64 idle_time;
- int cpu;
tuners = kzalloc(sizeof(*tuners), GFP_KERNEL);
if (!tuners)
return -ENOMEM;
- cpu = get_cpu();
- idle_time = get_cpu_idle_time_us(cpu, NULL);
- put_cpu();
- if (idle_time != -1ULL) {
+ if (tick_nohz_is_active()) {
/* Idle micro accounting is supported. Use finer thresholds */
dbs_data->up_threshold = MICRO_FREQUENCY_UP_THRESHOLD;
} else {
diff --git a/include/linux/tick.h b/include/linux/tick.h
index ac76ae9fa36d..738007d6f577 100644
--- a/include/linux/tick.h
+++ b/include/linux/tick.h
@@ -126,6 +126,7 @@ enum tick_dep_bits {
#ifdef CONFIG_NO_HZ_COMMON
extern bool tick_nohz_enabled;
+extern bool tick_nohz_is_active(void);
extern bool tick_nohz_tick_stopped(void);
extern bool tick_nohz_tick_stopped_cpu(int cpu);
extern void tick_nohz_idle_stop_tick(void);
@@ -142,6 +143,7 @@ extern u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time);
extern u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time);
#else /* !CONFIG_NO_HZ_COMMON */
#define tick_nohz_enabled (0)
+static inline bool tick_nohz_is_active(void) { return false; }
static inline int tick_nohz_tick_stopped(void) { return 0; }
static inline int tick_nohz_tick_stopped_cpu(int cpu) { return 0; }
static inline void tick_nohz_idle_stop_tick(void) { }
diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c
index f8ea8c8fc895..e1bbf883dfa8 100644
--- a/kernel/time/hrtimer.c
+++ b/kernel/time/hrtimer.c
@@ -943,7 +943,7 @@ void clock_was_set(unsigned int bases)
cpumask_var_t mask;
int cpu;
- if (!hrtimer_hres_active(cpu_base) && !tick_nohz_active)
+ if (!hrtimer_hres_active(cpu_base) && !tick_nohz_is_active())
goto out_timerfd;
if (!zalloc_cpumask_var(&mask, GFP_KERNEL)) {
diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h
index 4e4f7bbe2a64..597d816d22e8 100644
--- a/kernel/time/tick-internal.h
+++ b/kernel/time/tick-internal.h
@@ -156,7 +156,6 @@ static inline void tick_nohz_init(void) { }
#endif
#ifdef CONFIG_NO_HZ_COMMON
-extern unsigned long tick_nohz_active;
extern void timers_update_nohz(void);
extern u64 get_jiffies_update(unsigned long *basej);
# ifdef CONFIG_SMP
@@ -171,7 +170,6 @@ extern void timer_expire_remote(unsigned int cpu);
# endif
#else /* CONFIG_NO_HZ_COMMON */
static inline void timers_update_nohz(void) { }
-#define tick_nohz_active (0)
#endif
DECLARE_PER_CPU(struct hrtimer_cpu_base, hrtimer_bases);
diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c
index 21ac561a8545..81c619bf662c 100644
--- a/kernel/time/tick-sched.c
+++ b/kernel/time/tick-sched.c
@@ -691,7 +691,7 @@ void __init tick_nohz_init(void)
* NO HZ enabled ?
*/
bool tick_nohz_enabled __read_mostly = true;
-unsigned long tick_nohz_active __read_mostly;
+static unsigned long tick_nohz_active __read_mostly;
/*
* Enable / Disable tickless mode
*/
@@ -702,6 +702,12 @@ static int __init setup_tick_nohz(char *str)
__setup("nohz=", setup_tick_nohz);
+bool tick_nohz_is_active(void)
+{
+ return tick_nohz_active;
+}
+EXPORT_SYMBOL_GPL(tick_nohz_is_active);
+
bool tick_nohz_tick_stopped(void)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
diff --git a/kernel/time/timer.c b/kernel/time/timer.c
index 1f2364126894..7e1e3bde6b8b 100644
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -281,7 +281,7 @@ DEFINE_STATIC_KEY_FALSE(timers_migration_enabled);
static void timers_update_migration(void)
{
- if (sysctl_timer_migration && tick_nohz_active)
+ if (sysctl_timer_migration && tick_nohz_is_active())
static_branch_enable(&timers_migration_enabled);
else
static_branch_disable(&timers_migration_enabled);
--
2.51.1
^ permalink raw reply related
* Re: [PATCH v5 3/7] cxl/region: Skip decoder reset on detach for autodiscovered regions
From: Alejandro Lucero Palau @ 2026-01-28 15:39 UTC (permalink / raw)
To: dan.j.williams, Koralahalli Channabasappa, Smita,
Jonathan Cameron, Smita Koralahalli
Cc: linux-cxl, linux-kernel, nvdimm, linux-fsdevel, linux-pm,
Ard Biesheuvel, Alison Schofield, Vishal Verma, Ira Weiny,
Yazen Ghannam, Dave Jiang, Davidlohr Bueso, Matthew Wilcox,
Jan Kara, Rafael J . Wysocki, Len Brown, Pavel Machek, Li Ming,
Jeff Johnson, Ying Huang, Yao Xingtao, Peter Zijlstra,
Greg Kroah-Hartman, Nathan Fontenot, Terry Bowman, Robert Richter,
Benjamin Cheatham, Zhijian Li, Borislav Petkov, Tomasz Wolski
In-Reply-To: <69794c2512bfc_1d3310087@dwillia2-mobl4.notmuch>
On 1/27/26 23:37, dan.j.williams@intel.com wrote:
> Koralahalli Channabasappa, Smita wrote:
> [..]
>> I’m re reading Dan’s note here:
>> https://lore.kernel.org/all/6930dacd6510f_198110020@dwillia2-mobl4.notmuch/
>>
>> Specifically this part:
>> "If the administrator actually wants to destroy and reclaim that
>> physical address space then they need to forcefully de-commit that
>> auto-assembled region via the @commit sysfs attribute. So that means
>> commit_store() needs to clear CXL_REGION_F_AUTO to get the decoder reset
>> to happen."
>>
>> Today the sysfs commit=0 path inside commit_store() resets decoders
>> without the AUTO check whereas the detach path now skips the reset when
>> CXL_REGION_F_AUTO is set.
>>
>> I think the same rationale should apply to the sysfs de-commit path as
>> well? I’m trying to understand the implications of not guarding the
>> reset with AUTO in commit_store().
> Linux tends to give the administrator the ability to know better than the
> kernel. So if the root forcefully decommits the region, root gets to
> keep the pieces.
I have been trying to figure out how to preserve the decoders for Type2
auto discover regions since, I think, this was demanded after v22 sent
upstream. This patch/change is what I was looking for, and although I
did implement it in another way requiring "consensus", this one seems
good enough and already discussed and approved, so all good.
However, I think it would be also interesting to give the Type2 driver
the option of resetting decoders as well, what I have been using for v22
and successfully tested. But this change will preclude that other
possibility, so, what about an option for clearing CXL_REGION_F_AUTO by
Type2 drivers? If you want this only to be done by admin/root, I guess a
module param would do it.
^ permalink raw reply
* [PATCH v7 2/2] PCI/pwrctrl: Create pwrctrl device if the graph port is found
From: Manivannan Sadhasivam @ 2026-01-28 15:37 UTC (permalink / raw)
To: Bjorn Helgaas, Manivannan Sadhasivam, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bartosz Golaszewski,
Damien Le Moal, Niklas Cassel, Linus Walleij, Bartosz Golaszewski
Cc: linux-kernel, linux-pci, devicetree, linux-arm-msm,
Stephan Gerhold, Dmitry Baryshkov, linux-pm, linux-ide,
Manivannan Sadhasivam, Bartosz Golaszewski
In-Reply-To: <20260128-pci-m2-v7-0-9b3a5fe3d244@oss.qualcomm.com>
The devicetree node of the PCIe Root Port/Slot could have the graph port
to link the PCIe M.2 connector node. Since the M.2 connectors are modelled
as Power Sequencing devices, they need to be controlled by the pwrctrl
driver as like the Root Port/Slot supplies.
Hence, create the pwrctrl device if the graph port is found in the node.
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
---
drivers/pci/pwrctrl/core.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c
index 1b91375738a0..6f7dea6746e0 100644
--- a/drivers/pci/pwrctrl/core.c
+++ b/drivers/pci/pwrctrl/core.c
@@ -9,6 +9,7 @@
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/of.h>
+#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/pci.h>
#include <linux/pci-pwrctrl.h>
@@ -295,10 +296,10 @@ static int pci_pwrctrl_create_device(struct device_node *np,
/*
* Check whether the pwrctrl device really needs to be created or not.
- * This is decided based on at least one of the power supplies being
- * defined in the devicetree node of the device.
+ * This is decided based on at least one of the power supplies defined
+ * in the devicetree node of the device or the graph property.
*/
- if (!of_pci_supply_present(np)) {
+ if (!of_pci_supply_present(np) && !of_graph_is_present(np)) {
dev_dbg(parent, "Skipping OF node: %s\n", np->name);
return 0;
}
--
2.51.0
^ permalink raw reply related
* [PATCH v7 1/2] PCI/pwrctrl: Add support for handling PCIe M.2 connectors
From: Manivannan Sadhasivam @ 2026-01-28 15:37 UTC (permalink / raw)
To: Bjorn Helgaas, Manivannan Sadhasivam, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bartosz Golaszewski,
Damien Le Moal, Niklas Cassel, Linus Walleij, Bartosz Golaszewski
Cc: linux-kernel, linux-pci, devicetree, linux-arm-msm,
Stephan Gerhold, Dmitry Baryshkov, linux-pm, linux-ide,
Manivannan Sadhasivam, Bartosz Golaszewski
In-Reply-To: <20260128-pci-m2-v7-0-9b3a5fe3d244@oss.qualcomm.com>
Add support for handling the PCIe M.2 connectors as Power Sequencing
devices. These connectors are exposed as the Power Sequencing devices
as they often support multiple interfaces like PCIe/SATA, USB/UART to the
host machine and each interfaces could be driven by different client
drivers at the same time.
This driver handles the PCIe interface of these connectors. It first checks
for the presence of the graph port in the Root Port node with the help of
of_graph_is_present() API, if present, it acquires/poweres ON the
corresponding pwrseq device.
Once the pwrseq device is powered ON, the driver will skip parsing the Root
Port/Slot resources and registers with the pwrctrl framework.
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
---
drivers/pci/pwrctrl/Kconfig | 1 +
drivers/pci/pwrctrl/slot.c | 31 +++++++++++++++++++++++++++----
2 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/drivers/pci/pwrctrl/Kconfig b/drivers/pci/pwrctrl/Kconfig
index e0f999f299bb..cd3aa15bad00 100644
--- a/drivers/pci/pwrctrl/Kconfig
+++ b/drivers/pci/pwrctrl/Kconfig
@@ -13,6 +13,7 @@ config PCI_PWRCTRL_PWRSEQ
config PCI_PWRCTRL_SLOT
tristate "PCI Power Control driver for PCI slots"
+ select POWER_SEQUENCING
select PCI_PWRCTRL
help
Say Y here to enable the PCI Power Control driver to control the power
diff --git a/drivers/pci/pwrctrl/slot.c b/drivers/pci/pwrctrl/slot.c
index 44eccbca793c..082af81efe25 100644
--- a/drivers/pci/pwrctrl/slot.c
+++ b/drivers/pci/pwrctrl/slot.c
@@ -8,8 +8,10 @@
#include <linux/device.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/of_graph.h>
#include <linux/pci-pwrctrl.h>
#include <linux/platform_device.h>
+#include <linux/pwrseq/consumer.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
@@ -18,6 +20,7 @@ struct slot_pwrctrl {
struct regulator_bulk_data *supplies;
int num_supplies;
struct clk *clk;
+ struct pwrseq_desc *pwrseq;
};
static int slot_pwrctrl_power_on(struct pci_pwrctrl *pwrctrl)
@@ -26,6 +29,11 @@ static int slot_pwrctrl_power_on(struct pci_pwrctrl *pwrctrl)
struct slot_pwrctrl, pwrctrl);
int ret;
+ if (slot->pwrseq) {
+ pwrseq_power_on(slot->pwrseq);
+ return 0;
+ }
+
ret = regulator_bulk_enable(slot->num_supplies, slot->supplies);
if (ret < 0) {
dev_err(slot->pwrctrl.dev, "Failed to enable slot regulators\n");
@@ -40,6 +48,11 @@ static int slot_pwrctrl_power_off(struct pci_pwrctrl *pwrctrl)
struct slot_pwrctrl *slot = container_of(pwrctrl,
struct slot_pwrctrl, pwrctrl);
+ if (slot->pwrseq) {
+ pwrseq_power_off(slot->pwrseq);
+ return 0;
+ }
+
regulator_bulk_disable(slot->num_supplies, slot->supplies);
clk_disable_unprepare(slot->clk);
@@ -64,6 +77,15 @@ static int slot_pwrctrl_probe(struct platform_device *pdev)
if (!slot)
return -ENOMEM;
+ if (of_graph_is_present(dev_of_node(dev))) {
+ slot->pwrseq = devm_pwrseq_get(dev, "pcie");
+ if (IS_ERR(slot->pwrseq))
+ return dev_err_probe(dev, PTR_ERR(slot->pwrseq),
+ "Failed to get the power sequencer\n");
+
+ goto skip_resources;
+ }
+
ret = of_regulator_bulk_get_all(dev, dev_of_node(dev),
&slot->supplies);
if (ret < 0) {
@@ -73,19 +95,20 @@ static int slot_pwrctrl_probe(struct platform_device *pdev)
slot->num_supplies = ret;
- ret = devm_add_action_or_reset(dev, devm_slot_pwrctrl_release, slot);
- if (ret)
- return ret;
-
slot->clk = devm_clk_get_optional(dev, NULL);
if (IS_ERR(slot->clk)) {
return dev_err_probe(dev, PTR_ERR(slot->clk),
"Failed to enable slot clock\n");
}
+skip_resources:
slot->pwrctrl.power_on = slot_pwrctrl_power_on;
slot->pwrctrl.power_off = slot_pwrctrl_power_off;
+ ret = devm_add_action_or_reset(dev, devm_slot_pwrctrl_release, slot);
+ if (ret)
+ return ret;
+
pci_pwrctrl_init(&slot->pwrctrl, dev);
ret = devm_pci_pwrctrl_device_set_ready(dev, &slot->pwrctrl);
--
2.51.0
^ permalink raw reply related
* [PATCH v7 0/2] PCI: Add initial support for handling PCIe M.2 connectors in devicetree
From: Manivannan Sadhasivam @ 2026-01-28 15:37 UTC (permalink / raw)
To: Bjorn Helgaas, Manivannan Sadhasivam, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bartosz Golaszewski,
Damien Le Moal, Niklas Cassel, Linus Walleij, Bartosz Golaszewski
Cc: linux-kernel, linux-pci, devicetree, linux-arm-msm,
Stephan Gerhold, Dmitry Baryshkov, linux-pm, linux-ide,
Manivannan Sadhasivam, Bartosz Golaszewski
Hi,
This series is an initial attempt to support the PCIe M.2 connectors in the
kernel and devicetree binding. The PCIe M.2 connectors as defined in the PCI
Express M.2 Specification are widely used in Notebooks/Tablet form factors (even
in PCs). On the ACPI platforms, power to these connectors are mostly handled by
the firmware/BIOS and the kernel never bothered to directly power manage them as
like other PCIe connectors. But on the devicetree platforms, the kernel needs to
power manage these connectors with the help of the devicetree description. But
so far, there is no proper representation of the M.2 connectors in devicetree
binding. This forced the developers to fake the M.2 connectors as PMU nodes [1]
and fixed regulators in devicetree.
So to properly support the M.2 connectors in devicetree platforms, this series
introduces the devicetree binding for Mechanical Key M connector as an example
and also the corresponding pwrseq driver and PCI changes in kernel to driver the
connector.
The Mechanical Key M connector is used to connect SSDs to the host machine over
PCIe/SATA interfaces. Due to the hardware constraints, this series only adds
support for driving the PCIe interface of the connector in the kernel.
Also, the optional interfaces supported by the Key M connectors are not
supported in the driver and left for the future enhancements.
Testing
=======
This series, together with the devicetree changes [2] [3] were tested on the
Qualcomm X1e based Lenovo Thinkpad T14s Laptop which has the NVMe SSD connected
over PCIe.
[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/arch/arm64/boot/dts/qcom/x1e80100-qcp.dts?h=v6.18-rc4&id=d09ab685a8f51ba412d37305ea62628a01cbea57
[2] https://github.com/Mani-Sadhasivam/linux/commit/40120d02219f34d2040ffa6328f0d406b1e4c04d
[3] https://github.com/Mani-Sadhasivam/linux/commit/ff6c3075836cc794a3700b0ec6a4a9eb21d14c6f
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
---
Changes in v7:
- Dropped the pwrseq and binding patches as they got applied
- Rebased on top of pci/pwrctrl branch
- Link to v6: https://lore.kernel.org/r/20260122-pci-m2-v6-0-575da9f97239@oss.qualcomm.com
Changes in v6:
- Used 'ports' to describe interfaces instead of endpoints in the binding
- Added GPIOs and USB to the example in binding
- Incorporated minor comments in the pwrseq driver
- Dropped the ata binding patch as it got applied
- Link to v5: https://lore.kernel.org/r/20260107-pci-m2-v5-0-8173d8a72641@oss.qualcomm.com
Changes in v5:
- used of_node_get() and devm_action to free regulators
- Link to v4: https://lore.kernel.org/r/20251228-pci-m2-v4-0-5684868b0d5f@oss.qualcomm.com
Changes in v4:
- Added graph property to SATA in this series and PCI to dtschema:
https://github.com/devicetree-org/dt-schema/pull/180
- Used 'i2c-parent' instead of SMBus port
- Reworded the -gpios property description
- Rebased on top of v6.19-rc1
- Link to v3: https://lore.kernel.org/r/20251125-pci-m2-v3-0-c528042aea47@oss.qualcomm.com
Changes in v3:
- Changed the VIO supply name as per dtschema
- Added explicit endpoint properties to port 0 node for host I/F
- Used scope based cleanup for OF node in pwrseq driver
- Collected review tags
- Link to v2: https://lore.kernel.org/r/20251108-pci-m2-v2-0-e8bc4d7bf42d@oss.qualcomm.com
Changes in v2:
- Incorporated comments from Bartosz and Frank for pwrseq and dt-binding
patches, especially adding the pwrseq match() code.
- Link to v1: https://lore.kernel.org/r/20251105-pci-m2-v1-0-84b5f1f1e5e8@oss.qualcomm.com
---
Manivannan Sadhasivam (2):
PCI/pwrctrl: Add support for handling PCIe M.2 connectors
PCI/pwrctrl: Create pwrctrl device if the graph port is found
drivers/pci/pwrctrl/Kconfig | 1 +
drivers/pci/pwrctrl/core.c | 7 ++++---
drivers/pci/pwrctrl/slot.c | 31 +++++++++++++++++++++++++++----
3 files changed, 32 insertions(+), 7 deletions(-)
---
base-commit: 3e7f562e20ee87a25e104ef4fce557d39d62fa85
change-id: 20251103-pci-m2-7633631b6faa
Best regards,
--
Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
^ permalink raw reply
* [PATCH] pmdomain: imx: Fix i.MX8MP VPU_H1 power up sequence
From: Peng Fan (OSS) @ 2026-01-28 15:11 UTC (permalink / raw)
To: Ulf Hansson, Shawn Guo, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Lucas Stach, Jacky Bai, Frank Li
Cc: linux-pm, imx, linux-arm-kernel, linux-kernel, Peng Fan, stable
From: Peng Fan <peng.fan@nxp.com>
Per errata:
ERR050531: VPU_NOC power down handshake may hang during VC8000E/VPUMIX
power up/down cycling.
Description: VC8000E reset de-assertion edge and AXI clock may have a
timing issue.
Workaround: Set bit2 (vc8000e_clk_en) of BLK_CLK_EN_CSR to 0 to gate off
both AXI clock and VC8000E clock sent to VC8000E and AXI clock sent to
VPU_NOC m_v_2 interface during VC8000E power up(VC8000E reset is
de-asserted by HW)
Add a bool variable is_errata_err050531 in
'struct imx8m_blk_ctrl_domain_data' to represent whether the workaround
is needed. If is_errata_err050531 is true, first clear the clk before
powering up gpc, then enable the clk after powering up gpc.
While at here, using imx8mm_vpu_power_notifier() is wrong, as it ungates
the VPU clocks to provide the ADB clock, which is necessary on i.MX8MM,
but on i.MX8MP there is a separate gate (bit 3) for the NoC. So add
imx8mp_vpu_power_notifier() for i.MX8MP.
Fixes: a1a5f15f7f6cb ("soc: imx: imx8m-blk-ctrl: add i.MX8MP VPU blk ctrl")
Cc: stable@vger.kernel.org
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
drivers/pmdomain/imx/imx8m-blk-ctrl.c | 37 +++++++++++++++++++++++++++++++++--
1 file changed, 35 insertions(+), 2 deletions(-)
diff --git a/drivers/pmdomain/imx/imx8m-blk-ctrl.c b/drivers/pmdomain/imx/imx8m-blk-ctrl.c
index 74bf4936991d7c36346797d8b646dad40085fc2d..5b26b7c2c43172817d5e407a7d85eb6c5400d5a8 100644
--- a/drivers/pmdomain/imx/imx8m-blk-ctrl.c
+++ b/drivers/pmdomain/imx/imx8m-blk-ctrl.c
@@ -54,6 +54,7 @@ struct imx8m_blk_ctrl_domain_data {
* register.
*/
u32 mipi_phy_rst_mask;
+ bool is_errata_err050531;
};
#define DOMAIN_MAX_CLKS 4
@@ -108,7 +109,11 @@ static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd)
dev_err(bc->dev, "failed to enable clocks\n");
goto bus_put;
}
- regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);
+
+ if (data->is_errata_err050531)
+ regmap_clear_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);
+ else
+ regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);
/* power up upstream GPC domain */
ret = pm_runtime_get_sync(domain->power_dev);
@@ -117,6 +122,9 @@ static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd)
goto clk_disable;
}
+ if (data->is_errata_err050531)
+ regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask);
+
/* wait for reset to propagate */
udelay(5);
@@ -514,9 +522,34 @@ static const struct imx8m_blk_ctrl_domain_data imx8mp_vpu_blk_ctl_domain_data[]
},
};
+static int imx8mp_vpu_power_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct imx8m_blk_ctrl *bc = container_of(nb, struct imx8m_blk_ctrl,
+ power_nb);
+
+ if (action == GENPD_NOTIFY_ON) {
+ /*
+ * On power up we have no software backchannel to the GPC to
+ * wait for the ADB handshake to happen, so we just delay for a
+ * bit. On power down the GPC driver waits for the handshake.
+ */
+
+ udelay(5);
+
+ /* set "fuse" bits to enable the VPUs */
+ regmap_set_bits(bc->regmap, 0x8, 0xffffffff);
+ regmap_set_bits(bc->regmap, 0xc, 0xffffffff);
+ regmap_set_bits(bc->regmap, 0x10, 0xffffffff);
+ regmap_set_bits(bc->regmap, 0x14, 0xffffffff);
+ }
+
+ return NOTIFY_OK;
+}
+
static const struct imx8m_blk_ctrl_data imx8mp_vpu_blk_ctl_dev_data = {
.max_reg = 0x18,
- .power_notifier_fn = imx8mm_vpu_power_notifier,
+ .power_notifier_fn = imx8mp_vpu_power_notifier,
.domains = imx8mp_vpu_blk_ctl_domain_data,
.num_domains = ARRAY_SIZE(imx8mp_vpu_blk_ctl_domain_data),
};
---
base-commit: 4f938c7d3b25d87b356af4106c2682caf8c835a2
change-id: 20260128-imx8mp-vc8000e-pm-4278e6d48b54
Best regards,
--
Peng Fan <peng.fan@nxp.com>
^ permalink raw reply related
* [rafael-pm:bleeding-edge] BUILD REGRESSION 4802892d9bdbe890394af0856826bb87b9a6f8c3
From: kernel test robot @ 2026-01-28 14:51 UTC (permalink / raw)
To: Rafael J. Wysocki; +Cc: linux-acpi, linux-pm
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git bleeding-edge
branch HEAD: 4802892d9bdbe890394af0856826bb87b9a6f8c3 Merge branch 'acpi-sysfs' into bleeding-edge
Error/Warning (recently discovered and may have been fixed):
https://lore.kernel.org/oe-kbuild-all/202601240833.jYdOreP4-lkp@intel.com
https://lore.kernel.org/oe-kbuild-all/202601240853.XfwHlHep-lkp@intel.com
kernel/time/hrtimer.c:946:41: error: call to undeclared function 'tick_nohz_is_active'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
kernel/time/hrtimer.c:946:48: error: implicit declaration of function 'tick_nohz_is_active'; did you mean 'tick_nohz_init'? [-Werror=implicit-function-declaration]
kernel/time/hrtimer.c:946:48: error: implicit declaration of function 'tick_nohz_is_active'; did you mean 'tick_nohz_init'? [-Wimplicit-function-declaration]
Error/Warning ids grouped by kconfigs:
recent_errors
|-- alpha-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- arc-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- arm-allnoconfig
| `-- kernel-time-hrtimer.c:error:call-to-undeclared-function-tick_nohz_is_active-ISO-C99-and-later-do-not-support-implicit-function-declarations
|-- arm64-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- csky-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- hexagon-allnoconfig
| `-- kernel-time-hrtimer.c:error:call-to-undeclared-function-tick_nohz_is_active-ISO-C99-and-later-do-not-support-implicit-function-declarations
|-- i386-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- loongarch-allnoconfig
| `-- kernel-time-hrtimer.c:error:call-to-undeclared-function-tick_nohz_is_active-ISO-C99-and-later-do-not-support-implicit-function-declarations
|-- m68k-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- microblaze-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- mips-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- nios2-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- openrisc-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- parisc-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- powerpc-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- riscv-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- s390-allnoconfig
| `-- kernel-time-hrtimer.c:error:call-to-undeclared-function-tick_nohz_is_active-ISO-C99-and-later-do-not-support-implicit-function-declarations
|-- sh-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- sparc-allnoconfig
| `-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
|-- um-allnoconfig
| `-- kernel-time-hrtimer.c:error:call-to-undeclared-function-tick_nohz_is_active-ISO-C99-and-later-do-not-support-implicit-function-declarations
|-- x86_64-allnoconfig
| `-- kernel-time-hrtimer.c:error:call-to-undeclared-function-tick_nohz_is_active-ISO-C99-and-later-do-not-support-implicit-function-declarations
`-- xtensa-allnoconfig
`-- kernel-time-hrtimer.c:error:implicit-declaration-of-function-tick_nohz_is_active
elapsed time: 1059m
configs tested: 217
configs skipped: 5
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-22
arc allyesconfig gcc-15.2.0
arc axs103_smp_defconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc haps_hs_smp_defconfig clang-22
arc hsdk_defconfig clang-22
arc randconfig-001-20260128 gcc-8.5.0
arc randconfig-002-20260128 gcc-8.5.0
arm allnoconfig clang-22
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm allyesconfig gcc-15.2.0
arm aspeed_g4_defconfig clang-22
arm at91_dt_defconfig clang-22
arm bcm2835_defconfig gcc-15.2.0
arm defconfig gcc-15.2.0
arm neponset_defconfig gcc-15.2.0
arm pxa3xx_defconfig gcc-15.2.0
arm pxa910_defconfig gcc-15.2.0
arm randconfig-001-20260128 gcc-8.5.0
arm randconfig-002-20260128 gcc-8.5.0
arm randconfig-003-20260128 gcc-8.5.0
arm randconfig-004-20260128 gcc-8.5.0
arm rpc_defconfig gcc-15.2.0
arm64 allmodconfig clang-22
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260128 gcc-14.3.0
arm64 randconfig-002-20260128 gcc-14.3.0
arm64 randconfig-003-20260128 gcc-14.3.0
arm64 randconfig-004-20260128 gcc-14.3.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260128 gcc-14.3.0
csky randconfig-002-20260128 gcc-14.3.0
hexagon allmodconfig clang-17
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig clang-22
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260128 clang-22
hexagon randconfig-002-20260128 clang-22
i386 allmodconfig clang-20
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 allyesconfig gcc-14
i386 buildonly-randconfig-001-20260128 clang-20
i386 buildonly-randconfig-002-20260128 clang-20
i386 buildonly-randconfig-003-20260128 clang-20
i386 buildonly-randconfig-004-20260128 clang-20
i386 buildonly-randconfig-005-20260128 clang-20
i386 buildonly-randconfig-006-20260128 clang-20
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260128 gcc-14
i386 randconfig-002-20260128 gcc-14
i386 randconfig-003-20260128 gcc-14
i386 randconfig-004-20260128 gcc-14
i386 randconfig-005-20260128 gcc-14
i386 randconfig-006-20260128 gcc-14
i386 randconfig-007-20260128 gcc-14
i386 randconfig-011-20260128 clang-20
i386 randconfig-012-20260128 clang-20
i386 randconfig-013-20260128 clang-20
i386 randconfig-014-20260128 clang-20
i386 randconfig-015-20260128 clang-20
i386 randconfig-016-20260128 clang-20
i386 randconfig-017-20260128 clang-20
loongarch allmodconfig clang-22
loongarch allnoconfig clang-22
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260128 clang-22
loongarch randconfig-002-20260128 clang-22
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k allyesconfig gcc-15.2.0
m68k defconfig clang-19
m68k m5275evb_defconfig clang-22
m68k m5407c3_defconfig gcc-15.2.0
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips ip27_defconfig gcc-15.2.0
mips ip30_defconfig gcc-15.2.0
nios2 allmodconfig clang-22
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig clang-22
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
nios2 randconfig-001-20260128 clang-22
nios2 randconfig-002-20260128 clang-22
openrisc allmodconfig clang-22
openrisc allmodconfig gcc-15.2.0
openrisc allnoconfig clang-22
openrisc allnoconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
openrisc or1klitex_defconfig clang-22
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-22
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc allyesconfig gcc-15.2.0
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260128 gcc-11.5.0
parisc randconfig-002-20260128 gcc-11.5.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-22
powerpc allnoconfig gcc-15.2.0
powerpc fsp2_defconfig gcc-15.2.0
powerpc linkstation_defconfig clang-22
powerpc mgcoge_defconfig gcc-15.2.0
powerpc mpc83xx_defconfig gcc-15.2.0
powerpc mvme5100_defconfig gcc-15.2.0
powerpc pasemi_defconfig gcc-15.2.0
powerpc randconfig-001-20260128 gcc-11.5.0
powerpc randconfig-002-20260128 gcc-11.5.0
powerpc64 randconfig-001-20260128 gcc-11.5.0
powerpc64 randconfig-002-20260128 gcc-11.5.0
riscv allmodconfig clang-22
riscv allnoconfig clang-22
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260128 gcc-13.4.0
riscv randconfig-002-20260128 gcc-13.4.0
s390 allmodconfig clang-18
s390 allmodconfig clang-19
s390 allnoconfig clang-22
s390 allyesconfig gcc-15.2.0
s390 debug_defconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260128 gcc-13.4.0
s390 randconfig-002-20260128 gcc-13.4.0
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-22
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh allyesconfig gcc-15.2.0
sh defconfig gcc-14
sh randconfig-001-20260128 gcc-13.4.0
sh randconfig-002-20260128 gcc-13.4.0
sparc allnoconfig clang-22
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260128 gcc-11.5.0
sparc randconfig-002-20260128 gcc-11.5.0
sparc64 allmodconfig clang-22
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260128 gcc-11.5.0
sparc64 randconfig-002-20260128 gcc-11.5.0
um allmodconfig clang-19
um allnoconfig clang-22
um allyesconfig gcc-14
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260128 gcc-11.5.0
um randconfig-002-20260128 gcc-11.5.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-22
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260128 gcc-14
x86_64 buildonly-randconfig-002-20260128 gcc-14
x86_64 buildonly-randconfig-003-20260128 gcc-14
x86_64 buildonly-randconfig-004-20260128 gcc-14
x86_64 buildonly-randconfig-005-20260128 gcc-14
x86_64 buildonly-randconfig-006-20260128 gcc-14
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260128 gcc-13
x86_64 randconfig-002-20260128 gcc-13
x86_64 randconfig-003-20260128 gcc-13
x86_64 randconfig-004-20260128 gcc-13
x86_64 randconfig-005-20260128 gcc-13
x86_64 randconfig-006-20260128 gcc-13
x86_64 randconfig-011-20260128 clang-20
x86_64 randconfig-012-20260128 clang-20
x86_64 randconfig-013-20260128 clang-20
x86_64 randconfig-014-20260128 clang-20
x86_64 randconfig-015-20260128 clang-20
x86_64 randconfig-016-20260128 clang-20
x86_64 randconfig-071-20260128 clang-20
x86_64 randconfig-072-20260128 clang-20
x86_64 randconfig-073-20260128 clang-20
x86_64 randconfig-074-20260128 clang-20
x86_64 randconfig-075-20260128 clang-20
x86_64 randconfig-076-20260128 clang-20
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-22
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-22
xtensa allyesconfig gcc-15.2.0
xtensa randconfig-001-20260128 gcc-11.5.0
xtensa randconfig-002-20260128 gcc-11.5.0
xtensa xip_kc705_defconfig clang-22
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH] arm64: topology: Fix false warning in counters_read_on_cpu() for same-CPU reads
From: Will Deacon @ 2026-01-28 13:04 UTC (permalink / raw)
To: Jie Zhan
Cc: Sumit Gupta, catalin.marinas, zhenglifeng1, viresh.kumar, rafael,
beata.michalska, pierre.gondois, ionela.voinescu,
linux-arm-kernel, linux-pm, linux-kernel, linux-tegra, treding,
jonathanh, bbasu
In-Reply-To: <3c6d58b2-b4c9-4dc7-a705-b7626e127f33@hisilicon.com>
On Wed, Jan 28, 2026 at 06:50:42PM +0800, Jie Zhan wrote:
> On 1/27/2026 4:07 PM, Sumit Gupta wrote:
> > The counters_read_on_cpu() function warns when called with IRQs disabled
> > to prevent deadlock in smp_call_function_single(). However, this warning
> > is spurious when reading counters on the current CPU since no IPI is
> > needed for same-CPU reads.
> >
> > Commit 12eb8f4fff24 ("cpufreq: CPPC: Update FIE arch_freq_scale in ticks
> > for non-PCC regs") changed the CPPC Frequency Invariance Engine to read
> > AMU counters directly from the scheduler tick for non-PCC register
> > spaces (like FFH), instead of deferring to a kthread. This means
> > counters_read_on_cpu() is now called with IRQs disabled from the tick
> > handler, triggering the warning:
> >
> > | WARNING: arch/arm64/kernel/topology.c:410 at counters_read_on_cpu
> > | ...
> > | Call trace:
> > | counters_read_on_cpu+0x88/0xa8 (P)
> > | cpc_read_ffh+0xdc/0x148
> > | cpc_read+0x260/0x518
> > | cppc_get_perf_ctrs+0xf0/0x398
> > | __cppc_scale_freq_tick+0x4c/0x148 [cppc_cpufreq]
> > | cppc_scale_freq_tick+0x44/0x88 [cppc_cpufreq]
> > | topology_scale_freq_tick+0x34/0x58
> > | sched_tick+0x58/0x300
> > | update_process_times+0xcc/0x120
> > | tick_nohz_handler+0xa8/0x260
> > | __hrtimer_run_queues+0x154/0x360
> > | hrtimer_interrupt+0xf4/0x2b0
> > | arch_timer_handler_phys+0x4c/0x78
> > | ....
> > | CPPC Cpufreq:__cppc_scale_freq_tick: failed to read perf counters
> > | ....
> >
> > Fix this by calling the counter read function directly for same-CPU
> > case, bypassing smp_call_function_single() entirely. Use get_cpu() to
> > disable preemption as the counter read functions call this_cpu_has_cap()
> > which requires a non-preemptible context.
> >
> > Fixes: 12eb8f4fff24 ("cpufreq: CPPC: Update FIE arch_freq_scale in ticks for non-PCC regs")
> > Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
>
> Reviewed-by: Jie Zhan <zhanjie9@hisilicon.com>
>
> Looks fine for me except for the minor comment wrapping.
>
> Thanks for spotting this.
> I may have missed the warning log in the FFH test.
>
> This happens during the short window in cpufreq_policy_online() between
> driver->init() and the CREATE_POLICY notifier that gets AMU FIE ready.
> After that, CPPC FIE will be stopped.
>
> Ideally this can be merged together with Viresh's PR since the CPPC FIE
> changes are there.
> https://lore.kernel.org/all/j4qdid7iqmng4gzb5ozefemjkep3wx2b5z2yki5tnqc3vzvzf4@kvrnarvdod5p/
Right, looks like this should go via Rafael but if it doesn't make the merge
window then I can pick it up at -rc1 (please remind me :)
Will
^ permalink raw reply
* Re: [PATCH v2 1/8] thermal: Add Remote Proc cooling driver
From: Krzysztof Kozlowski @ 2026-01-28 11:36 UTC (permalink / raw)
To: Gaurav Kohli
Cc: andersson, mathieu.poirier, robh, krzk+dt, conor+dt, rui.zhang,
lukasz.luba, konradybcio, mani, casey.connolly, amit.kucheria,
linux-arm-msm, devicetree, linux-kernel, linux-pm,
manaf.pallikunhi
In-Reply-To: <20260127155722.2797783-2-gaurav.kohli@oss.qualcomm.com>
On Tue, Jan 27, 2026 at 09:27:15PM +0530, Gaurav Kohli wrote:
> + if (!name || !ops)
> + return ERR_PTR(-EINVAL);
> +
> + rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
> + if (!rproc_cdev)
> + return ERR_PTR(-ENOMEM);
> +
> + rproc_cdev->ops = ops;
> + rproc_cdev->devdata = devdata;
> + mutex_init(&rproc_cdev->lock);
> +
> + char *rproc_name __free(kfree) =
> + kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
Ah, you keep ignoring review and sending the same buggy code.
There is no point to spend any time here. It's also fastest way to get
your future contributions ignored or NAKed.
Well, ignoring review is obviously:
NAK
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2 1/8] thermal: Add Remote Proc cooling driver
From: Krzysztof Kozlowski @ 2026-01-28 11:32 UTC (permalink / raw)
To: Gaurav Kohli
Cc: andersson, mathieu.poirier, robh, krzk+dt, conor+dt, rui.zhang,
lukasz.luba, konradybcio, mani, casey.connolly, amit.kucheria,
linux-arm-msm, devicetree, linux-kernel, linux-pm,
manaf.pallikunhi
In-Reply-To: <20260127155722.2797783-2-gaurav.kohli@oss.qualcomm.com>
On Tue, Jan 27, 2026 at 09:27:15PM +0530, Gaurav Kohli wrote:
> Add a new generic driver for thermal cooling devices that control
There is no driver here. You did not a single driver entry point.
> remote processors (modem, DSP, etc.) through various communication
> channels.
>
> This driver provides an abstraction layer between the thermal
Please read coding style how much we like abstraction layers.
> subsystem and vendor-specific remote processor communication
> mechanisms.
>
> Advantage of this to avoid duplicating vendor-specific logic
> in the thermal subsystem and make it easier for different vendors
> to plug in their own cooling mechanisms via callbacks.
>
> Suggested-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> ---
> MAINTAINERS | 7 ++
> drivers/thermal/Kconfig | 10 ++
> drivers/thermal/Makefile | 2 +
> drivers/thermal/remoteproc_cooling.c | 143 +++++++++++++++++++++++++++
> include/linux/remoteproc_cooling.h | 52 ++++++++++
> 5 files changed, 214 insertions(+)
> create mode 100644 drivers/thermal/remoteproc_cooling.c
> create mode 100644 include/linux/remoteproc_cooling.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 414f44093269..5ebc7819d2cf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -26169,6 +26169,13 @@ F: drivers/thermal/cpufreq_cooling.c
> F: drivers/thermal/cpuidle_cooling.c
> F: include/linux/cpu_cooling.h
>
> +THERMAL/REMOTEPROC_COOLING
> +M: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> +L: linux-pm@vger.kernel.org
> +S: Supported
> +F: drivers/thermal/remoteproc_cooling.c
> +F: include/linux/remoteproc_cooling.h
> +
> THERMAL/POWER_ALLOCATOR
Please beginning of this file. P < R.
> M: Lukasz Luba <lukasz.luba@arm.com>
> L: linux-pm@vger.kernel.org
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index b10080d61860..dfc52eed64de 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -229,6 +229,16 @@ config PCIE_THERMAL
>
> If you want this support, you should say Y here.
>
> +config REMOTEPROC_THERMAL
> + tristate "Remote processor cooling support"
> + help
> + This implements a generic cooling mechanism for remote processors
> + (modem, DSP, etc.) that allows vendor-specific implementations to
> + register thermal cooling devices and provide callbacks for thermal
> + mitigation.
> +
> + If you want this support, you should say Y here.
> +
> config THERMAL_EMULATION
> bool "Thermal emulation mode support"
> help
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index bb21e7ea7fc6..ae747dde54fe 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -34,6 +34,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
>
> thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_cooling.o
>
> +thermal_sys-$(CONFIG_REMOTEPROC_THERMAL) += remoteproc_cooling.o
> +
> obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
> # platform thermal drivers
> obj-y += broadcom/
> diff --git a/drivers/thermal/remoteproc_cooling.c b/drivers/thermal/remoteproc_cooling.c
> new file mode 100644
> index 000000000000..f958efa691b3
> --- /dev/null
> +++ b/drivers/thermal/remoteproc_cooling.c
> @@ -0,0 +1,143 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Remote Processor Cooling Device
> + *
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
Where do you use it?
> +#include <linux/slab.h>
> +#include <linux/thermal.h>
> +
> +#define REMOTEPROC_PREFIX "rproc_"
> +
> +struct remoteproc_cooling_ops {
> + int (*get_max_level)(void *devdata, unsigned long *level);
> + int (*get_cur_level)(void *devdata, unsigned long *level);
> + int (*set_cur_level)(void *devdata, unsigned long level);
> +};
> +
> +/**
> + * struct remoteproc_cdev - Remote processor cooling device
> + * @cdev: Thermal cooling device handle
> + * @ops: Vendor-specific operation callbacks
> + * @devdata: Private data for vendor implementation
> + * @np: Device tree node associated with this cooling device
> + * @lock: Mutex to protect cooling device operations
> + */
> +struct remoteproc_cdev {
> + struct thermal_cooling_device *cdev;
> + const struct remoteproc_cooling_ops *ops;
> + void *devdata;
> + struct mutex lock;
> +};
> +
> +/* Thermal cooling device callbacks */
> +
> +static int remoteproc_get_max_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->get_max_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static int remoteproc_get_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->get_cur_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static int remoteproc_set_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long state)
> +{
> + struct remoteproc_cdev *rproc_cdev = cdev->devdata;
> + int ret;
> +
> + mutex_lock(&rproc_cdev->lock);
> + ret = rproc_cdev->ops->set_cur_level(rproc_cdev->devdata, state);
> + mutex_unlock(&rproc_cdev->lock);
> +
> + return ret;
> +}
> +
> +static const struct thermal_cooling_device_ops remoteproc_cooling_ops = {
> + .get_max_state = remoteproc_get_max_state,
> + .get_cur_state = remoteproc_get_cur_state,
> + .set_cur_state = remoteproc_set_cur_state,
> +};
> +
> +struct remoteproc_cdev *
> +remoteproc_cooling_register(struct device_node *np,
> + const char *name, const struct remoteproc_cooling_ops *ops,
> + void *devdata)
> +{
> + struct remoteproc_cdev *rproc_cdev;
> + struct thermal_cooling_device *cdev;
> + int ret;
> +
> + if (!name || !ops)
> + return ERR_PTR(-EINVAL);
> +
> + rproc_cdev = kzalloc(sizeof(*rproc_cdev), GFP_KERNEL);
> + if (!rproc_cdev)
> + return ERR_PTR(-ENOMEM);
> +
> + rproc_cdev->ops = ops;
> + rproc_cdev->devdata = devdata;
> + mutex_init(&rproc_cdev->lock);
> +
> + char *rproc_name __free(kfree) =
> + kasprintf(GFP_KERNEL, REMOTEPROC_PREFIX "%s", name);
> + /* Register with thermal framework */
> + if (np)
> + cdev = thermal_of_cooling_device_register(np, rproc_name, rproc_cdev,
> + &remoteproc_cooling_ops);
> + else
> + cdev = thermal_cooling_device_register(rproc_name, rproc_cdev,
> + &remoteproc_cooling_ops);
> +
> + if (IS_ERR(cdev)) {
> + ret = PTR_ERR(cdev);
> + goto free_rproc_cdev;
> + }
> +
> + rproc_cdev->cdev = cdev;
> +
> + return rproc_cdev;
> +
> +free_rproc_cdev:
> + kfree(rproc_cdev);
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(remoteproc_cooling_register);
> +
> +void remoteproc_cooling_unregister(struct remoteproc_cdev *rproc_cdev)
> +{
> + if (!rproc_cdev)
> + return;
> +
> + thermal_cooling_device_unregister(rproc_cdev->cdev);
> + mutex_destroy(&rproc_cdev->lock);
> + kfree(rproc_cdev);
> +}
> +EXPORT_SYMBOL_GPL(remoteproc_cooling_unregister);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Remote Processor Cooling Device");
I do not see any driver here, just bunch of exported functions. I do not
see point in this abstraction/wrapping layer.
Another abstraction layer, NAK.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2 2/8] dt-bindings: thermal: Add qcom,qmi-cooling yaml bindings
From: Krzysztof Kozlowski @ 2026-01-28 11:28 UTC (permalink / raw)
To: Gaurav Kohli
Cc: andersson, mathieu.poirier, robh, krzk+dt, conor+dt, rui.zhang,
lukasz.luba, konradybcio, mani, casey.connolly, amit.kucheria,
linux-arm-msm, devicetree, linux-kernel, linux-pm,
manaf.pallikunhi
In-Reply-To: <20260127155722.2797783-3-gaurav.kohli@oss.qualcomm.com>
On Tue, Jan 27, 2026 at 09:27:16PM +0530, Gaurav Kohli wrote:
> The cooling subnode of a remoteproc represents a client of the Thermal
> Mitigation Device QMI service running on it. Each subnode of the cooling
> node represents a single control exposed by the service.
Subject - almost bingo, you hit two out of three which you should not
use.
A nit, subject: drop second/last, redundant "bindings yaml" and whatever
else is duplicating. The "dt-bindings" prefix is already stating that
these are bindings.
See also:
https://elixir.bootlin.com/linux/v6.17-rc3/source/Documentation/devicetree/bindings/submitting-patches.rst#L18
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2 2/8] dt-bindings: thermal: Add qcom,qmi-cooling yaml bindings
From: Krzysztof Kozlowski @ 2026-01-28 11:27 UTC (permalink / raw)
To: Gaurav Kohli
Cc: andersson, mathieu.poirier, robh, krzk+dt, conor+dt, rui.zhang,
lukasz.luba, konradybcio, mani, casey.connolly, amit.kucheria,
linux-arm-msm, devicetree, linux-kernel, linux-pm,
manaf.pallikunhi
In-Reply-To: <20260127155722.2797783-3-gaurav.kohli@oss.qualcomm.com>
On Tue, Jan 27, 2026 at 09:27:16PM +0530, Gaurav Kohli wrote:
> The cooling subnode of a remoteproc represents a client of the Thermal
> Mitigation Device QMI service running on it. Each subnode of the cooling
> node represents a single control exposed by the service.
>
> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> ---
> .../bindings/remoteproc/qcom,pas-common.yaml | 6 ++
> .../bindings/thermal/qcom,qmi-cooling.yaml | 72 +++++++++++++++++++
> 2 files changed, 78 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/thermal/qcom,qmi-cooling.yaml
>
> diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml
> index 68c17bf18987..6a736161d5ae 100644
> --- a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml
> +++ b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml
> @@ -80,6 +80,12 @@ properties:
> and devices related to the ADSP.
> unevaluatedProperties: false
>
> + cooling:
> + $ref: /schemas/thermal/qcom,qmi-cooling.yaml#
> + description:
> + Cooling subnode which represents the cooling devices exposed by the Modem.
I do not see the reason why you need 3 (!!!) children here. Everything
should be folded here.
> + unevaluatedProperties: false
> +
> required:
> - clocks
> - clock-names
> diff --git a/Documentation/devicetree/bindings/thermal/qcom,qmi-cooling.yaml b/Documentation/devicetree/bindings/thermal/qcom,qmi-cooling.yaml
> new file mode 100644
> index 000000000000..0dd3bd84c176
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/thermal/qcom,qmi-cooling.yaml
> @@ -0,0 +1,72 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/thermal/qcom,qmi-cooling.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm QMI based thermal mitigation (TMD) cooling devices
> +
> +maintainers:
> + - Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> +
> +description:
> + Qualcomm QMI-based TMD cooling devices are used to mitigate thermal conditions
> + across multiple remote subsystems. These devices operate based on junction
> + temperature sensors (TSENS) associated with thermal zones for each subsystem.
> +
> +properties:
> + compatible:
> + enum:
> + - qcom,qmi-cooling-cdsp
> + - qcom,qmi-cooling-cdsp1
What are the differences between them?
Why these are not SoC specific?
> +
> +patternProperties:
> + "cdsp-tmd[0-9]*$":
> + type: object
No, you do not need childnode. See writing bindings (covers exactly this
case).
> +
> + description:
> + Each subnode which represents qmi communication to CDSP.
> +
> + properties:
> + label:
> + maxItems: 1
> +
> + "#cooling-cells":
> + $ref: /schemas/thermal/thermal-cooling-devices.yaml#/properties/#cooling-cells
> +
> + required:
> + - label
> + - "#cooling-cells"
> +
> + additionalProperties: false
> +
> +required:
> + - compatible
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + remoteproc-cdsp {
> + cooling {
> + compatible = "qcom,qmi-cooling-cdsp";
> +
> + cdsp_tmd0: cdsp-tmd0 {
> + label = "cdsp_sw";
> + #cooling-cells = <2>;
> + };
> + };
> + };
> +
> + - |
> + remoteproc-cdsp1 {
No, don't create unnecessary examples. Please read some slides from
earlier talks so you won't need 10 iterations.
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH V9 4/4] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring
From: Jishnu Prakash @ 2026-01-28 11:24 UTC (permalink / raw)
To: jic23, robh, krzk+dt, conor+dt, agross, andersson, lumag,
dmitry.baryshkov, konradybcio, daniel.lezcano, sboyd, amitk,
thara.gopinath, lee, rafael, subbaraman.narayanamurthy,
david.collins, anjelique.melendez, kamal.wadhwa
Cc: rui.zhang, lukasz.luba, devicetree, linux-arm-msm, linux-iio,
linux-kernel, linux-pm, cros-qcom-dts-watchers, jishnu.prakash,
quic_kotarake, neil.armstrong, stephan.gerhold
In-Reply-To: <20260128112420.695518-1-jishnu.prakash@oss.qualcomm.com>
Add support for ADC_TM part of PMIC5 Gen3.
This is an auxiliary driver under the Gen3 ADC driver, which implements the
threshold setting and interrupt generating functionalities of QCOM ADC_TM
drivers, used to support thermal trip points.
Signed-off-by: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
---
Changes since v8:
- Made following changes to address Dmitry's comment to use module_auxiliary_driver():
- Dropped the wrapper struct containing the auxiliary driver (struct adc_tm5_auxiliary_drv)
which was originally meant to expose the TM interrupt callback to be called by
main driver and replaced it with standalone definition of the auxiliary_driver struct.
- Added call to adc5_gen3_register_tm_event_notifier() in probe to initialize the
TM callback for main driver.
- Replaced the module_init() and module_exit() calls with module_auxiliary_driver().
- Made following changes to address Jonathan's comments:
- Updated header files included in drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
to follow IWYU (include-what-you-use) principles.
- Added a DEFINE_GUARD() definition for mutex lock/unlock functions and replaced
their existing calls with guard() and scoped_guard() statements using this definition.
- Moved some variable declarations in tm_handler_work() to inside the for() loop.
- Fixed if() check condition for low_temp in adc_tm5_gen3_set_trip_temp().
- Dropped the wrapper function adc_tm5_gen3_disable_channel() around
_adc_tm5_gen3_disable_channel() as it only calls the inner function with no other actions.
- Replaced a pr_debug() call with dev_dbg() in tm_handler_work().
Changes since v7:
- Addressed following comments from Jonathan:
- Replaced {0} with { } in tm_handler_work()
- Simplified logic for setting upper_set and lower_set into
a single line each, in tm_handler_work()
- Cleaned up local variable declarations and high/low threshold
check in adc_tm5_gen3_configure()
- Moved cleanup action to disable all ADC_TM channels to probe
end and added comment to describe it.
- Fixed { } formatting in adctm5_auxiliary_id_table[].
Changes since v6:
- Addressed following comments from Jonathan:
- Added error check for devm_thermal_add_hwmon_sysfs() call.
- Used local variable `dev` in multiple places in adc_tm5_probe().
in place of `&aux_dev->dev` and `adc_tm5->dev`.
- Added a comment to explain cleanup action calling adc5_gen3_clear_work()
near probe end.
- Fixed return statement at probe end to return last called API's
return value directly.
Changes since v5:
- Addressed following comments from Jonathan:
- Corrected all files to follow kernel-doc formatting fully.
- Cleaned up formatting in struct definitions.
- Used sizeof() to specify length in register read/write calls
instead of using integers directly.
- Added comments in adc_tm5_probe() for skipping first SDAM for
IRQ request and for usage of auxiliary_set_drvdata().
- Corrected line wrap length driver file.
- Moved INIT_WORK() and auxiliary_set_drvdata() to earlier
locations to ensure they are ready when needed.
Changes since v4:
- Fixed a compilation error and updated dependencies in config as suggested
by Krzysztof.
drivers/thermal/qcom/Kconfig | 9 +
drivers/thermal/qcom/Makefile | 1 +
drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c | 512 ++++++++++++++++++
3 files changed, 522 insertions(+)
create mode 100644 drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
index a6bb01082ec6..1acb11e4ac80 100644
--- a/drivers/thermal/qcom/Kconfig
+++ b/drivers/thermal/qcom/Kconfig
@@ -21,6 +21,15 @@ config QCOM_SPMI_ADC_TM5
Thermal client sets threshold temperature for both warm and cool and
gets updated when a threshold is reached.
+config QCOM_SPMI_ADC_TM5_GEN3
+ tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5 Gen3"
+ depends on QCOM_SPMI_ADC5_GEN3
+ help
+ This enables the auxiliary thermal driver for the ADC5 Gen3 thermal
+ monitoring device. It shows up as a thermal zone with multiple trip points.
+ Thermal client sets threshold temperature for both warm and cool and
+ gets updated when a threshold is reached.
+
config QCOM_SPMI_TEMP_ALARM
tristate "Qualcomm SPMI PMIC Temperature Alarm"
depends on OF && SPMI && IIO
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
index 0fa2512042e7..828d9e7bc797 100644
--- a/drivers/thermal/qcom/Makefile
+++ b/drivers/thermal/qcom/Makefile
@@ -4,5 +4,6 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
tsens-8960.o
obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o
+obj-$(CONFIG_QCOM_SPMI_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.o
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
obj-$(CONFIG_QCOM_LMH) += lmh.o
diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
new file mode 100644
index 000000000000..b72fd7ee36c0
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
@@ -0,0 +1,512 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/device/devres.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/iio/adc/qcom-adc5-gen3-common.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+#include <linux/unaligned.h>
+
+#include "../thermal_hwmon.h"
+
+struct adc_tm5_gen3_chip;
+
+/**
+ * struct adc_tm5_gen3_channel_props - ADC_TM channel structure
+ * @timer: time period of recurring TM measurement.
+ * @tm_chan_index: TM channel number used (ranging from 1-7).
+ * @sdam_index: SDAM on which this TM channel lies.
+ * @common_props: structure with common ADC channel properties.
+ * @high_thr_en: TM high threshold crossing detection enabled.
+ * @low_thr_en: TM low threshold crossing detection enabled.
+ * @chip: ADC TM device.
+ * @tzd: pointer to thermal device corresponding to TM channel.
+ * @last_temp: last temperature that caused threshold violation,
+ * or a thermal TM channel.
+ * @last_temp_set: indicates if last_temp is stored.
+ */
+struct adc_tm5_gen3_channel_props {
+ unsigned int timer;
+ unsigned int tm_chan_index;
+ unsigned int sdam_index;
+ struct adc5_channel_common_prop common_props;
+ bool high_thr_en;
+ bool low_thr_en;
+ struct adc_tm5_gen3_chip *chip;
+ struct thermal_zone_device *tzd;
+ int last_temp;
+ bool last_temp_set;
+};
+
+/**
+ * struct adc_tm5_gen3_chip - ADC Thermal Monitoring device structure
+ * @dev_data: Top-level ADC device data.
+ * @chan_props: Array of ADC_TM channel structures.
+ * @nchannels: number of TM channels allocated
+ * @dev: SPMI ADC5 Gen3 device.
+ * @tm_handler_work: handler for TM interrupt for threshold violation.
+ */
+struct adc_tm5_gen3_chip {
+ struct adc5_device_data *dev_data;
+ struct adc_tm5_gen3_channel_props *chan_props;
+ unsigned int nchannels;
+ struct device *dev;
+ struct work_struct tm_handler_work;
+};
+
+DEFINE_GUARD(adc5_gen3, struct adc_tm5_gen3_chip *, adc5_gen3_mutex_lock(_T->dev),
+ adc5_gen3_mutex_unlock(_T->dev))
+
+static int get_sdam_from_irq(struct adc_tm5_gen3_chip *adc_tm5, int irq)
+{
+ int i;
+
+ for (i = 0; i < adc_tm5->dev_data->num_sdams; i++) {
+ if (adc_tm5->dev_data->base[i].irq == irq)
+ return i;
+ }
+ return -ENOENT;
+}
+
+static irqreturn_t adctm5_gen3_isr(int irq, void *dev_id)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = dev_id;
+ int ret, sdam_num;
+ u8 tm_status[2];
+ u8 status, val;
+
+ sdam_num = get_sdam_from_irq(adc_tm5, irq);
+ if (sdam_num < 0) {
+ dev_err(adc_tm5->dev, "adc irq %d not associated with an sdam\n",
+ irq);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1,
+ &status, sizeof(status));
+ if (ret) {
+ dev_err(adc_tm5->dev, "adc read status1 failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ if (status & ADC5_GEN3_STATUS1_CONV_FAULT) {
+ dev_err_ratelimited(adc_tm5->dev,
+ "Unexpected conversion fault, status:%#x\n",
+ status);
+ val = ADC5_GEN3_CONV_ERR_CLR_REQ;
+ adc5_gen3_status_clear(adc_tm5->dev_data, sdam_num,
+ ADC5_GEN3_CONV_ERR_CLR, &val, 1);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_TM_HIGH_STS,
+ tm_status, sizeof(tm_status));
+ if (ret) {
+ dev_err(adc_tm5->dev, "adc read TM status failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ if (tm_status[0] || tm_status[1])
+ schedule_work(&adc_tm5->tm_handler_work);
+
+ dev_dbg(adc_tm5->dev, "Interrupt status:%#x, high:%#x, low:%#x\n",
+ status, tm_status[0], tm_status[1]);
+
+ return IRQ_HANDLED;
+}
+
+static int adc5_gen3_tm_status_check(struct adc_tm5_gen3_chip *adc_tm5,
+ int sdam_index, u8 *tm_status, u8 *buf)
+{
+ int ret;
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS,
+ tm_status, 2);
+ if (ret) {
+ dev_err(adc_tm5->dev, "adc read TM status failed with %d\n", ret);
+ return ret;
+ }
+
+ ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS_CLR,
+ tm_status, 2);
+ if (ret) {
+ dev_err(adc_tm5->dev, "adc status clear conv_req failed with %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_CH_DATA0(0),
+ buf, 16);
+ if (ret)
+ dev_err(adc_tm5->dev, "adc read data failed with %d\n", ret);
+
+ return ret;
+}
+
+static void tm_handler_work(struct work_struct *work)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = container_of(work, struct adc_tm5_gen3_chip,
+ tm_handler_work);
+ int sdam_index = -1;
+ u8 tm_status[2] = { };
+ u8 buf[16] = { };
+
+ for (int i = 0; i < adc_tm5->nchannels; i++) {
+ struct adc_tm5_gen3_channel_props *chan_prop = &adc_tm5->chan_props[i];
+ int offset = chan_prop->tm_chan_index;
+ bool upper_set, lower_set;
+ int ret, temp;
+ u16 code;
+
+ scoped_guard(adc5_gen3, adc_tm5) {
+ if (chan_prop->sdam_index != sdam_index) {
+ sdam_index = chan_prop->sdam_index;
+ ret = adc5_gen3_tm_status_check(adc_tm5, sdam_index,
+ tm_status, buf);
+ if (ret)
+ break;
+ }
+
+ upper_set = ((tm_status[0] & BIT(offset)) && chan_prop->high_thr_en);
+ lower_set = ((tm_status[1] & BIT(offset)) && chan_prop->low_thr_en);
+ }
+
+ if (!(upper_set || lower_set))
+ continue;
+
+ code = get_unaligned_le16(&buf[2 * offset]);
+ dev_dbg(adc_tm5->dev, "ADC_TM threshold code:%#x\n", code);
+
+ ret = adc5_gen3_therm_code_to_temp(adc_tm5->dev,
+ &chan_prop->common_props,
+ code, &temp);
+ if (ret) {
+ dev_err(adc_tm5->dev,
+ "Invalid temperature reading, ret = %d, code=%#x\n",
+ ret, code);
+ continue;
+ }
+
+ chan_prop->last_temp = temp;
+ chan_prop->last_temp_set = true;
+ thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED);
+ }
+}
+
+static int adc_tm5_gen3_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz);
+ struct adc_tm5_gen3_chip *adc_tm5;
+
+ if (!prop || !prop->chip)
+ return -EINVAL;
+
+ adc_tm5 = prop->chip;
+
+ if (prop->last_temp_set) {
+ pr_debug("last_temp: %d\n", prop->last_temp);
+ prop->last_temp_set = false;
+ *temp = prop->last_temp;
+ return 0;
+ }
+
+ return adc5_gen3_get_scaled_reading(adc_tm5->dev, &prop->common_props,
+ temp);
+}
+
+static int adc_tm5_gen3_disable_channel(struct adc_tm5_gen3_channel_props *prop)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = prop->chip;
+ int ret;
+ u8 val;
+
+ prop->high_thr_en = false;
+ prop->low_thr_en = false;
+
+ ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index);
+ if (ret)
+ return ret;
+
+ val = BIT(prop->tm_chan_index);
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_TM_HIGH_STS_CLR, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ val = MEAS_INT_DISABLE;
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_TIMER_SEL, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ /* To indicate there is an actual conversion request */
+ val = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index;
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_PERPH_CH, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ val = ADC5_GEN3_CONV_REQ_REQ;
+ return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_CONV_REQ, &val, sizeof(val));
+}
+
+#define ADC_TM5_GEN3_CONFIG_REGS 12
+
+static int adc_tm5_gen3_configure(struct adc_tm5_gen3_channel_props *prop,
+ int low_temp, int high_temp)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = prop->chip;
+ u8 buf[ADC_TM5_GEN3_CONFIG_REGS];
+ u8 conv_req;
+ u16 adc_code;
+ int ret;
+
+ ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index);
+ if (ret < 0)
+ return ret;
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_SID, buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ /* Write SID */
+ buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->common_props.sid);
+
+ /* Select TM channel and indicate there is an actual conversion request */
+ buf[1] = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index;
+
+ buf[2] = prop->timer;
+
+ /* Digital param selection */
+ adc5_gen3_update_dig_param(&prop->common_props, &buf[3]);
+
+ /* Update fast average sample value */
+ buf[4] &= ~ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK;
+ buf[4] |= prop->common_props.avg_samples | ADC5_GEN3_FAST_AVG_CTL_EN;
+
+ /* Select ADC channel */
+ buf[5] = prop->common_props.channel;
+
+ /* Select HW settle delay for channel */
+ buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK,
+ prop->common_props.hw_settle_time_us);
+
+ /* High temperature corresponds to low voltage threshold */
+ prop->low_thr_en = (high_temp != INT_MAX);
+ if (prop->low_thr_en) {
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(high_temp);
+ put_unaligned_le16(adc_code, &buf[8]);
+ }
+
+ /* Low temperature corresponds to high voltage threshold */
+ prop->high_thr_en = (low_temp != -INT_MAX);
+ if (prop->high_thr_en) {
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(low_temp);
+ put_unaligned_le16(adc_code, &buf[10]);
+ }
+
+ buf[7] = 0;
+ if (prop->high_thr_en)
+ buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN;
+ if (prop->low_thr_en)
+ buf[7] |= ADC5_GEN3_LOW_THR_INT_EN;
+
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, ADC5_GEN3_SID,
+ buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ conv_req = ADC5_GEN3_CONV_REQ_REQ;
+ return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req));
+}
+
+static int adc_tm5_gen3_set_trip_temp(struct thermal_zone_device *tz,
+ int low_temp, int high_temp)
+{
+ struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz);
+ struct adc_tm5_gen3_chip *adc_tm5;
+
+ if (!prop || !prop->chip)
+ return -EINVAL;
+
+ adc_tm5 = prop->chip;
+
+ dev_dbg(adc_tm5->dev, "channel:%s, low_temp(mdegC):%d, high_temp(mdegC):%d\n",
+ prop->common_props.label, low_temp, high_temp);
+
+ guard(adc5_gen3)(adc_tm5);
+ if (high_temp == INT_MAX && low_temp == -INT_MAX)
+ return adc_tm5_gen3_disable_channel(prop);
+
+ return adc_tm5_gen3_configure(prop, low_temp, high_temp);
+}
+
+static const struct thermal_zone_device_ops adc_tm_ops = {
+ .get_temp = adc_tm5_gen3_get_temp,
+ .set_trips = adc_tm5_gen3_set_trip_temp,
+};
+
+static int adc_tm5_register_tzd(struct adc_tm5_gen3_chip *adc_tm5)
+{
+ unsigned int i, channel;
+ struct thermal_zone_device *tzd;
+ int ret;
+
+ for (i = 0; i < adc_tm5->nchannels; i++) {
+ channel = ADC5_GEN3_V_CHAN(adc_tm5->chan_props[i].common_props);
+ tzd = devm_thermal_of_zone_register(adc_tm5->dev, channel,
+ &adc_tm5->chan_props[i],
+ &adc_tm_ops);
+
+ if (IS_ERR(tzd)) {
+ if (PTR_ERR(tzd) == -ENODEV) {
+ dev_warn(adc_tm5->dev,
+ "thermal sensor on channel %d is not used\n",
+ channel);
+ continue;
+ }
+ return dev_err_probe(adc_tm5->dev, PTR_ERR(tzd),
+ "Error registering TZ zone:%ld for channel:%d\n",
+ PTR_ERR(tzd), channel);
+ }
+ adc_tm5->chan_props[i].tzd = tzd;
+ ret = devm_thermal_add_hwmon_sysfs(adc_tm5->dev, tzd);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static void adc5_gen3_clear_work(void *data)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = data;
+
+ cancel_work_sync(&adc_tm5->tm_handler_work);
+}
+
+static void adc5_gen3_disable(void *data)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = data;
+ int i;
+
+ guard(adc5_gen3)(adc_tm5);
+ /* Disable all available TM channels */
+ for (i = 0; i < adc_tm5->nchannels; i++)
+ adc_tm5_gen3_disable_channel(&adc_tm5->chan_props[i]);
+}
+
+static void adctm_event_handler(struct auxiliary_device *adev)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = auxiliary_get_drvdata(adev);
+
+ schedule_work(&adc_tm5->tm_handler_work);
+}
+
+static int adc_tm5_probe(struct auxiliary_device *aux_dev,
+ const struct auxiliary_device_id *id)
+{
+ struct adc_tm5_gen3_chip *adc_tm5;
+ struct tm5_aux_dev_wrapper *aux_dev_wrapper;
+ struct device *dev = &aux_dev->dev;
+ int i, ret;
+
+ adc_tm5 = devm_kzalloc(dev, sizeof(*adc_tm5), GFP_KERNEL);
+ if (!adc_tm5)
+ return -ENOMEM;
+
+ aux_dev_wrapper = container_of(aux_dev, struct tm5_aux_dev_wrapper,
+ aux_dev);
+
+ adc_tm5->dev = dev;
+ adc_tm5->dev_data = aux_dev_wrapper->dev_data;
+ adc_tm5->nchannels = aux_dev_wrapper->n_tm_channels;
+ adc_tm5->chan_props = devm_kcalloc(dev, aux_dev_wrapper->n_tm_channels,
+ sizeof(*adc_tm5->chan_props), GFP_KERNEL);
+ if (!adc_tm5->chan_props)
+ return -ENOMEM;
+
+ for (i = 0; i < adc_tm5->nchannels; i++) {
+ adc_tm5->chan_props[i].common_props = aux_dev_wrapper->tm_props[i];
+ adc_tm5->chan_props[i].timer = MEAS_INT_1S;
+ adc_tm5->chan_props[i].sdam_index = (i + 1) / 8;
+ adc_tm5->chan_props[i].tm_chan_index = (i + 1) % 8;
+ adc_tm5->chan_props[i].chip = adc_tm5;
+ }
+
+ INIT_WORK(&adc_tm5->tm_handler_work, tm_handler_work);
+
+ /*
+ * Skipping first SDAM IRQ as it is requested in parent driver.
+ * If there is a TM violation on that IRQ, the parent driver calls
+ * the notifier (adctm_event_handler) exposed from this driver to handle it.
+ */
+ for (i = 1; i < adc_tm5->dev_data->num_sdams; i++) {
+ ret = devm_request_threaded_irq(dev,
+ adc_tm5->dev_data->base[i].irq,
+ NULL, adctm5_gen3_isr, IRQF_ONESHOT,
+ adc_tm5->dev_data->base[i].irq_name,
+ adc_tm5);
+ if (ret < 0)
+ return ret;
+ }
+
+ /*
+ * This drvdata is only used in the function (adctm_event_handler)
+ * called by parent ADC driver in case of TM violation on the first SDAM.
+ */
+ auxiliary_set_drvdata(aux_dev, adc_tm5);
+
+ adc5_gen3_register_tm_event_notifier(dev, adctm_event_handler);
+
+ /*
+ * This is to cancel any instances of tm_handler_work scheduled by
+ * TM interrupt, at the time of module removal.
+ */
+
+ ret = devm_add_action(dev, adc5_gen3_clear_work, adc_tm5);
+ if (ret)
+ return ret;
+
+ ret = adc_tm5_register_tzd(adc_tm5);
+ if (ret)
+ return ret;
+
+ /* This is to disable all ADC_TM channels in case of probe failure. */
+
+ return devm_add_action(dev, adc5_gen3_disable, adc_tm5);
+}
+
+static const struct auxiliary_device_id adctm5_auxiliary_id_table[] = {
+ { .name = "qcom_spmi_adc5_gen3.adc5_tm_gen3", },
+ { }
+};
+
+MODULE_DEVICE_TABLE(auxiliary, adctm5_auxiliary_id_table);
+
+static struct auxiliary_driver adctm5gen3_auxiliary_driver = {
+ .id_table = adctm5_auxiliary_id_table,
+ .probe = adc_tm5_probe,
+};
+
+module_auxiliary_driver(adctm5gen3_auxiliary_driver);
+
+MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3");
--
2.25.1
^ permalink raw reply related
* [PATCH V9 3/4] iio: adc: Add support for QCOM PMIC5 Gen3 ADC
From: Jishnu Prakash @ 2026-01-28 11:24 UTC (permalink / raw)
To: jic23, robh, krzk+dt, conor+dt, agross, andersson, lumag,
dmitry.baryshkov, konradybcio, daniel.lezcano, sboyd, amitk,
thara.gopinath, lee, rafael, subbaraman.narayanamurthy,
david.collins, anjelique.melendez, kamal.wadhwa
Cc: rui.zhang, lukasz.luba, devicetree, linux-arm-msm, linux-iio,
linux-kernel, linux-pm, cros-qcom-dts-watchers, jishnu.prakash,
quic_kotarake, neil.armstrong, stephan.gerhold
In-Reply-To: <20260128112420.695518-1-jishnu.prakash@oss.qualcomm.com>
The ADC architecture on PMIC5 Gen3 is similar to that on PMIC5 Gen2,
with all SW communication to ADC going through PMK8550 which
communicates with other PMICs through PBS.
One major difference is that the register interface used here is that
of an SDAM (Shared Direct Access Memory) peripheral present on PMK8550.
There may be more than one SDAM used for ADC5 Gen3 and each has eight
channels, which may be used for either immediate reads (same functionality
as previous PMIC5 and PMIC5 Gen2 ADC peripherals) or recurring measurements
(same as ADC_TM functionality).
By convention, we reserve the first channel of the first SDAM for all
immediate reads and use the remaining channels across all SDAMs for
ADC_TM monitoring functionality.
Add support for PMIC5 Gen3 ADC driver for immediate read functionality.
ADC_TM is implemented as an auxiliary thermal driver under this ADC
driver.
Signed-off-by: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
---
Changes since v8:
- Dropped the common module (drivers/iio/adc/qcom-adc5-gen3-common.c) and moved
all of its contents to drivers/iio/adc/qcom-spmi-adc5-gen3.c as suggested by Dmitry.
- Made following changes to address Dmitry's comment to use module_auxiliary_driver()
in auxiliary driver patch, by simplifying auxiliary device structures:
- Added function pointer for TM interrupt handler callback under struct adc5_chip
(to be called in case of TM interrupt on first SDAM), to replace the
tm_event_notify() callback.
- Add new exported function (adc5_gen3_register_tm_event_notifier()) to be called
by TM auxiliary driver in its probe to initialize the above callback function.
- Updated adc5_gen3_isr() to call this TM callback function instead of
tm_event_notify() callback from the wrapper struct adc_tm5_auxiliary_drv.
- Completely dropped the above wrapper struct definition.
- Made following changes to address Jonathan's comments:
- Updated header files included in drivers/iio/adc/qcom-spmi-adc5-gen3.c and
include/linux/iio/adc/qcom-adc5-gen3-common.h to follow IWYU (include-what-you-use)
principles.
- Dropped comment inside adc5_chip struct definition describing mutex lock.
- Dropped ADC5_GEN3_TEMP_ALARM_LITE channel as it had some inaccuracy issue, which
is being debugged internally. Will add it in a separate patch along with channel user.
- Replaced dev_err() with dev_err_probe() in adc5_get_fw_data.
Changes since v7:
- Addressed following comments from Jonathan:
- Included regmap header file in drivers/iio/adc/qcom-adc5-gen3-common.c.
- Increased comment wrap length in adc5_gen3_configure() and
struct adc5_chip definition.
- Updated error checks in adc5_gen3_isr() to remove NULL check for
adrv_tm and keep (!adrv_tm->tm_event_notify) error check alone
within if() condition.
- Removed sid initialization in adc5_gen3_get_fw_channel_data()
- Added definitions for ADC channel macros used in adc5_gen3_chans_pmic[]
in include/linux/iio/adc/qcom-adc5-gen3-common.h instead of
include/dt-bindings/iio/adc/qcom,spmi-vadc.h, as this latter file
will be moved out of bindings folder in a separate change. Also
removed its inclusion in drivers/iio/adc/qcom-spmi-adc5-gen3.c.
- Cleaned up local variable declarations in adc5_gen3_isr() and
adc5_gen3_get_fw_channel_data() and added local variable for
adc->dev in adc5_get_fw_data().
- Fixed error message after platform_get_irq() call in adc5_gen3_probe()
to print IRQ number correctly.
- Added a check in adc5_gen3_get_fw_channel_data() to exit with error
if ADC channel value obtained from `reg` channel property is not
among the supported ones in the array adc5_gen3_chans_pmic[].
- Corrected the value used in checking for max valid ADC channel value,
in adc5_gen3_get_fw_channel_data().
Changes since v6:
- Addressed following comments from Jonathan:
- Moved functions exported in drivers/iio/adc/qcom-adc5-gen3-common.c
into namespace "QCOM_SPMI_ADC5_GEN3".
- Increased line wrap length for comments.
- Added local variable for adc->dev in adc5_gen3_isr().
- Shifted debug print showing IRQ status registers in adc5_gen3_isr()
to before tm_status[] check.
- Fixed indentation and brackets in adc5_gen3_get_fw_channel_data().
- Cleaned up array formatting in adc5_gen3_data_pmic struct.
- Used scoped variant of device_for_each_child_node() in adc5_get_fw_data().
- Updated auxiliary device cleanup handling to fix memory freeing
issues, by adding empty auxiliary device release function.
- Used devm_mutex_init() in adc5_gen3_probe().
- Updated virtual channel macro name from V_CHAN to ADC5_GEN3_V_CHAN.
- Set IIO device name to "spmi-adc5-gen3".
- Added __acquires and __releases macros for exported mutex lock
and unlock functions in drivers/iio/adc/qcom-spmi-adc5-gen3.c.
- Added error check to fail probe in case adding auxiliary TM device fails.
- Replaced 2025 copyright in newly added files with yearless copyright,
following new internal guidelines.
Changes since v5:
- Addressed following comments from Jonathan:
- Corrected line wrap length in Kconfig and driver files.
- Replaced usleep_range() with fsleep() in adc5_gen3_poll_wait_hs()
- Corrected all files to follow kernel-doc formatting fully.
- Removed IIO_CHAN_INFO_RAW case in adc5_gen3_read_raw()
- Cleaned up formatting in adc5_gen3_data_pmic struct and in other
struct definitions.
- Updated adc5_gen3_add_aux_tm_device() to keep errors alone out of line.
- Split mutex function exported to ADC_TM driver into separate functions
for acquiring and releasing mutex.
- Removed num_sdams member from struct adc5_chip.
- Fixed dev_err_probe() print in adc5_gen3_probe().
- Updated logic for acquiring IRQ numbers to account for removing
"interrupt-names" DT property.
- Included bitfield.h header file in drivers/iio/adc/qcom-adc5-gen3-common.c
to fix kernel bot error.
Changes since v4:
- Moved out common funtions from newly added .h file into a separate .c
file to avoid duplicating them and updated interrupt name, as suggested
by Krzysztof. Updated namespace export symbol statement to have a string
as second argument to follow framework change.
Changes since v3:
- Split out TM functionality into auxiliary driver in separate patch and
added required changes in main driver, as suggested by Dmitry.
- Addressed other reviewer comments in main driver patch.
Changes since v1:
- Removed datashet_name usage and implemented read_label() function
- In probe, updated channel property in iio_chan_spec from individual
channel to virtual channel and set indexed property to 1, due to the
above change.
- Updated order of checks in ISR
- Removed the driver remove callback and replaced with callbacks in a
devm_add_action call in probe.
- Addressed other comments from reviewers.
drivers/iio/adc/Kconfig | 26 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/qcom-spmi-adc5-gen3.c | 860 ++++++++++++++++++
include/linux/iio/adc/qcom-adc5-gen3-common.h | 211 +++++
4 files changed, 1098 insertions(+)
create mode 100644 drivers/iio/adc/qcom-spmi-adc5-gen3.c
create mode 100644 include/linux/iio/adc/qcom-adc5-gen3-common.h
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 58da8255525e..5300e9236ba8 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1329,6 +1329,32 @@ config QCOM_SPMI_ADC5
To compile this driver as a module, choose M here: the module will
be called qcom-spmi-adc5.
+config QCOM_SPMI_ADC5_GEN3
+ tristate "Qualcomm Technologies Inc. SPMI PMIC5 GEN3 ADC"
+ depends on SPMI && THERMAL
+ select REGMAP_SPMI
+ select QCOM_VADC_COMMON
+ select AUXILIARY_BUS
+ help
+ IIO Voltage PMIC5 Gen3 ADC driver for Qualcomm Technologies Inc.
+
+ The driver supports reading multiple channels. The ADC is a 16-bit
+ sigma-delta ADC. The hardware supports calibrated results for
+ conversion requests and clients include reading phone power supply
+ voltage, on board system thermistors connected to the PMIC ADC,
+ PMIC die temperature, charger temperature, battery current, USB
+ voltage input and voltage signals connected to supported PMIC GPIO
+ pins. The hardware supports internal pull-up for thermistors and can
+ choose between a 30k, 100k or 400k ohm pull up using the ADC channels.
+
+ In addition, the same driver supports ADC thermal monitoring devices
+ too. They appear as thermal zones with multiple trip points. A thermal
+ client sets threshold temperature for both warm and cool trips and
+ gets updated when a threshold is reached.
+
+ To compile this driver as a module, choose M here: the module will
+ be called qcom-spmi-adc5-gen3.
+
config RCAR_GYRO_ADC
tristate "Renesas R-Car GyroADC driver"
depends on ARCH_RCAR_GEN2 || COMPILE_TEST
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 7cc8f9a12f76..cb1874fd7912 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -113,6 +113,7 @@ obj-$(CONFIG_PAC1934) += pac1934.o
obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
obj-$(CONFIG_QCOM_PM8XXX_XOADC) += qcom-pm8xxx-xoadc.o
obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
+obj-$(CONFIG_QCOM_SPMI_ADC5_GEN3) += qcom-spmi-adc5-gen3.o
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
obj-$(CONFIG_QCOM_SPMI_RRADC) += qcom-spmi-rradc.o
obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c
new file mode 100644
index 000000000000..f8168a14b907
--- /dev/null
+++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c
@@ -0,0 +1,860 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/device/devres.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/iio/adc/qcom-adc5-gen3-common.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#define ADC5_GEN3_VADC_SDAM 0x0
+
+struct adc5_chip;
+
+/**
+ * struct adc5_channel_prop - ADC channel structure
+ * @common_props: structure with ADC channel properties (common to TM usage).
+ * @adc_tm: indicates TM type if the channel is used for TM measurements.
+ * @chip: pointer to top-level ADC device structure.
+ */
+struct adc5_channel_prop {
+ struct adc5_channel_common_prop common_props;
+ int adc_tm;
+ struct adc5_chip *chip;
+};
+
+/**
+ * struct adc5_chip - ADC private structure.
+ * @dev: SPMI ADC5 Gen3 device.
+ * @dev_data: Top-level ADC device data.
+ * @nchannels: number of ADC channels.
+ * @chan_props: array of ADC channel properties.
+ * @iio_chans: array of IIO channels specification.
+ * @complete: ADC result notification after interrupt is received.
+ * @lock: ADC lock for access to the peripheral, to prevent concurrent
+ * requests from multiple clients.
+ * @data: software configuration data.
+ * @n_tm_channels: number of ADC channels used for TM measurements.
+ * @handler: TM callback to be called for threshold violation interrupt
+ * on first SDAM.
+ * @tm_aux: pointer to auxiliary TM device.
+ */
+struct adc5_chip {
+ struct device *dev;
+ struct adc5_device_data dev_data;
+ unsigned int nchannels;
+ struct adc5_channel_prop *chan_props;
+ struct iio_chan_spec *iio_chans;
+ struct completion complete;
+ struct mutex lock;
+ const struct adc5_data *data;
+ unsigned int n_tm_channels;
+ void (*handler)(struct auxiliary_device *tm_aux);
+ struct auxiliary_device *tm_aux;
+};
+
+int adc5_gen3_read(struct adc5_device_data *adc, unsigned int sdam_index,
+ u16 offset, u8 *data, int len)
+{
+ return regmap_bulk_read(adc->regmap,
+ adc->base[sdam_index].base_addr + offset,
+ data, len);
+}
+EXPORT_SYMBOL_NS_GPL(adc5_gen3_read, "QCOM_SPMI_ADC5_GEN3");
+
+int adc5_gen3_write(struct adc5_device_data *adc, unsigned int sdam_index,
+ u16 offset, u8 *data, int len)
+{
+ return regmap_bulk_write(adc->regmap,
+ adc->base[sdam_index].base_addr + offset,
+ data, len);
+}
+EXPORT_SYMBOL_NS_GPL(adc5_gen3_write, "QCOM_SPMI_ADC5_GEN3");
+
+static int adc5_gen3_read_voltage_data(struct adc5_chip *adc, u16 *data)
+{
+ u8 rslt[2];
+ int ret;
+
+ ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
+ ADC5_GEN3_CH_DATA0(0), rslt, sizeof(rslt));
+ if (ret)
+ return ret;
+
+ *data = get_unaligned_le16(rslt);
+
+ if (*data == ADC5_USR_DATA_CHECK) {
+ dev_err(adc->dev, "Invalid data:%#x\n", *data);
+ return -EINVAL;
+ }
+
+ dev_dbg(adc->dev, "voltage raw code:%#x\n", *data);
+
+ return 0;
+}
+
+void adc5_gen3_update_dig_param(struct adc5_channel_common_prop *prop, u8 *data)
+{
+ /* Update calibration select and decimation ratio select */
+ *data &= ~(ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK | ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK);
+ *data |= FIELD_PREP(ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK, prop->cal_method);
+ *data |= FIELD_PREP(ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK, prop->decimation);
+}
+EXPORT_SYMBOL_NS_GPL(adc5_gen3_update_dig_param, "QCOM_SPMI_ADC5_GEN3");
+
+#define ADC5_GEN3_READ_CONFIG_REGS 7
+
+static int adc5_gen3_configure(struct adc5_chip *adc,
+ struct adc5_channel_common_prop *prop)
+{
+ u8 buf[ADC5_GEN3_READ_CONFIG_REGS];
+ u8 conv_req = 0;
+ int ret;
+
+ ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_SID,
+ buf, sizeof(buf));
+ if (ret)
+ return ret;
+
+ /* Write SID */
+ buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->sid);
+
+ /*
+ * Use channel 0 by default for immediate conversion and to indicate
+ * there is an actual conversion request
+ */
+ buf[1] = ADC5_GEN3_CHAN_CONV_REQ | 0;
+
+ buf[2] = ADC5_GEN3_TIME_IMMEDIATE;
+
+ /* Digital param selection */
+ adc5_gen3_update_dig_param(prop, &buf[3]);
+
+ /* Update fast average sample value */
+ buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK,
+ prop->avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN;
+
+ /* Select ADC channel */
+ buf[5] = prop->channel;
+
+ /* Select HW settle delay for channel */
+ buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK,
+ prop->hw_settle_time_us);
+
+ reinit_completion(&adc->complete);
+
+ ret = adc5_gen3_write(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_SID,
+ buf, sizeof(buf));
+ if (ret)
+ return ret;
+
+ conv_req = ADC5_GEN3_CONV_REQ_REQ;
+ return adc5_gen3_write(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
+ ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req));
+}
+
+/*
+ * Worst case delay from PBS in readying handshake bit can be up to 15ms, when
+ * PBS is busy running other simultaneous transactions, while in the best case,
+ * it is already ready at this point. Assigning polling delay and retry count
+ * accordingly.
+ */
+
+#define ADC5_GEN3_HS_DELAY_US 100
+#define ADC5_GEN3_HS_RETRY_COUNT 150
+
+int adc5_gen3_poll_wait_hs(struct adc5_device_data *adc,
+ unsigned int sdam_index)
+{
+ u8 conv_req = ADC5_GEN3_CONV_REQ_REQ;
+ int ret, count;
+ u8 status = 0;
+
+ for (count = 0; count < ADC5_GEN3_HS_RETRY_COUNT; count++) {
+ ret = adc5_gen3_read(adc, sdam_index, ADC5_GEN3_HS, &status, sizeof(status));
+ if (ret)
+ return ret;
+
+ if (status == ADC5_GEN3_HS_READY) {
+ ret = adc5_gen3_read(adc, sdam_index, ADC5_GEN3_CONV_REQ,
+ &conv_req, sizeof(conv_req));
+ if (ret)
+ return ret;
+
+ if (!conv_req)
+ return 0;
+ }
+
+ fsleep(ADC5_GEN3_HS_DELAY_US);
+ }
+
+ pr_err("Setting HS ready bit timed out, sdam_index:%d, status:%#x\n",
+ sdam_index, status);
+ return -ETIMEDOUT;
+}
+EXPORT_SYMBOL_NS_GPL(adc5_gen3_poll_wait_hs, "QCOM_SPMI_ADC5_GEN3");
+
+int adc5_gen3_status_clear(struct adc5_device_data *adc,
+ int sdam_index, u16 offset, u8 *val, int len)
+{
+ u8 value;
+ int ret;
+
+ ret = adc5_gen3_write(adc, sdam_index, offset, val, len);
+ if (ret)
+ return ret;
+
+ /* To indicate conversion request is only to clear a status */
+ value = 0;
+ ret = adc5_gen3_write(adc, sdam_index, ADC5_GEN3_PERPH_CH, &value,
+ sizeof(value));
+ if (ret)
+ return ret;
+
+ value = ADC5_GEN3_CONV_REQ_REQ;
+ return adc5_gen3_write(adc, sdam_index, ADC5_GEN3_CONV_REQ, &value,
+ sizeof(value));
+}
+EXPORT_SYMBOL_NS_GPL(adc5_gen3_status_clear, "QCOM_SPMI_ADC5_GEN3");
+
+/*
+ * Worst case delay from PBS for conversion time can be up to 500ms, when PBS
+ * has timed out twice, once for the initial attempt and once for a retry of
+ * the same transaction.
+ */
+
+#define ADC5_GEN3_CONV_TIMEOUT_MS 501
+
+static int adc5_gen3_do_conversion(struct adc5_chip *adc,
+ struct adc5_channel_common_prop *prop,
+ u16 *data_volt)
+{
+ unsigned long rc;
+ int ret;
+ u8 val;
+
+ guard(mutex)(&adc->lock);
+ ret = adc5_gen3_poll_wait_hs(&adc->dev_data, ADC5_GEN3_VADC_SDAM);
+ if (ret)
+ return ret;
+
+ ret = adc5_gen3_configure(adc, prop);
+ if (ret) {
+ dev_err(adc->dev, "ADC configure failed with %d\n", ret);
+ return ret;
+ }
+
+ /* No support for polling mode at present */
+ rc = wait_for_completion_timeout(&adc->complete,
+ msecs_to_jiffies(ADC5_GEN3_CONV_TIMEOUT_MS));
+ if (!rc) {
+ dev_err(adc->dev, "Reading ADC channel %s timed out\n",
+ prop->label);
+ return -ETIMEDOUT;
+ }
+
+ ret = adc5_gen3_read_voltage_data(adc, data_volt);
+ if (ret)
+ return ret;
+
+ val = BIT(0);
+ return adc5_gen3_status_clear(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
+ ADC5_GEN3_EOC_CLR, &val, 1);
+}
+
+static irqreturn_t adc5_gen3_isr(int irq, void *dev_id)
+{
+ struct adc5_chip *adc = dev_id;
+ struct device *dev = adc->dev;
+ struct auxiliary_device *adev;
+ u8 status, eoc_status, val;
+ u8 tm_status[2];
+ int ret;
+
+ ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
+ ADC5_GEN3_STATUS1, &status, sizeof(status));
+ if (ret) {
+ dev_err(dev, "adc read status1 failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
+ ADC5_GEN3_EOC_STS, &eoc_status, sizeof(eoc_status));
+ if (ret) {
+ dev_err(dev, "adc read eoc status failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ if (status & ADC5_GEN3_STATUS1_CONV_FAULT) {
+ dev_err_ratelimited(dev,
+ "Unexpected conversion fault, status:%#x, eoc_status:%#x\n",
+ status, eoc_status);
+ val = ADC5_GEN3_CONV_ERR_CLR_REQ;
+ adc5_gen3_status_clear(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
+ ADC5_GEN3_CONV_ERR_CLR, &val, 1);
+ return IRQ_HANDLED;
+ }
+
+ /* CHAN0 is the preconfigured channel for immediate conversion */
+ if (eoc_status & ADC5_GEN3_EOC_CHAN_0)
+ complete(&adc->complete);
+
+ ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
+ ADC5_GEN3_TM_HIGH_STS, tm_status, sizeof(tm_status));
+ if (ret) {
+ dev_err(dev, "adc read TM status failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x, high:%#x, low:%#x\n",
+ status, eoc_status, tm_status[0], tm_status[1]);
+
+ if (tm_status[0] || tm_status[1]) {
+ adev = adc->tm_aux;
+ if (!adev || !adev->dev.driver) {
+ dev_err(dev, "adc_tm auxiliary device not initialized\n");
+ return IRQ_HANDLED;
+ }
+
+ adc->handler(adev);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int adc5_gen3_fwnode_xlate(struct iio_dev *indio_dev,
+ const struct fwnode_reference_args *iiospec)
+{
+ struct adc5_chip *adc = iio_priv(indio_dev);
+ int i, v_channel;
+
+ for (i = 0; i < adc->nchannels; i++) {
+ v_channel = ADC5_GEN3_V_CHAN(adc->chan_props[i].common_props);
+ if (v_channel == iiospec->args[0])
+ return i;
+ }
+
+ return -ENOENT;
+}
+
+static int adc5_gen3_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct adc5_chip *adc = iio_priv(indio_dev);
+ struct adc5_channel_common_prop *prop;
+ u16 adc_code_volt;
+ int ret;
+
+ prop = &adc->chan_props[chan->address].common_props;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ ret = adc5_gen3_do_conversion(adc, prop, &adc_code_volt);
+ if (ret)
+ return ret;
+
+ ret = qcom_adc5_hw_scale(prop->scale_fn_type, prop->prescale,
+ adc->data, adc_code_volt, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adc5_gen3_read_label(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, char *label)
+{
+ struct adc5_chip *adc = iio_priv(indio_dev);
+ struct adc5_channel_prop *prop;
+
+ prop = &adc->chan_props[chan->address];
+ return sprintf(label, "%s\n", prop->common_props.label);
+}
+
+static const struct iio_info adc5_gen3_info = {
+ .read_raw = adc5_gen3_read_raw,
+ .read_label = adc5_gen3_read_label,
+ .fwnode_xlate = adc5_gen3_fwnode_xlate,
+};
+
+struct adc5_channels {
+ unsigned int prescale_index;
+ enum iio_chan_type type;
+ long info_mask;
+ enum vadc_scale_fn_type scale_fn_type;
+};
+
+/* In these definitions, _pre refers to an index into adc5_prescale_ratios. */
+#define ADC5_CHAN(_type, _mask, _pre, _scale) \
+ { \
+ .prescale_index = _pre, \
+ .type = _type, \
+ .info_mask = _mask, \
+ .scale_fn_type = _scale, \
+ }, \
+
+#define ADC5_CHAN_TEMP(_pre, _scale) \
+ ADC5_CHAN(IIO_TEMP, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale) \
+
+#define ADC5_CHAN_VOLT(_pre, _scale) \
+ ADC5_CHAN(IIO_VOLTAGE, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale) \
+
+#define ADC5_CHAN_CUR(_pre, _scale) \
+ ADC5_CHAN(IIO_CURRENT, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale) \
+
+static const struct adc5_channels adc5_gen3_chans_pmic[ADC5_MAX_CHANNEL] = {
+ [ADC5_GEN3_REF_GND] = ADC5_CHAN_VOLT(0, SCALE_HW_CALIB_DEFAULT)
+ [ADC5_GEN3_1P25VREF] = ADC5_CHAN_VOLT(0, SCALE_HW_CALIB_DEFAULT)
+ [ADC5_GEN3_VPH_PWR] = ADC5_CHAN_VOLT(1, SCALE_HW_CALIB_DEFAULT)
+ [ADC5_GEN3_VBAT_SNS_QBG] = ADC5_CHAN_VOLT(1, SCALE_HW_CALIB_DEFAULT)
+ [ADC5_GEN3_USB_SNS_V_16] = ADC5_CHAN_TEMP(8, SCALE_HW_CALIB_DEFAULT)
+ [ADC5_GEN3_VIN_DIV16_MUX] = ADC5_CHAN_TEMP(8, SCALE_HW_CALIB_DEFAULT)
+ [ADC5_GEN3_DIE_TEMP] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_PMIC_THERM_PM7)
+ [ADC5_GEN3_AMUX1_THM_100K_PU] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_THERM_100K_PU_PM7)
+ [ADC5_GEN3_AMUX2_THM_100K_PU] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_THERM_100K_PU_PM7)
+ [ADC5_GEN3_AMUX3_THM_100K_PU] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_THERM_100K_PU_PM7)
+ [ADC5_GEN3_AMUX4_THM_100K_PU] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_THERM_100K_PU_PM7)
+ [ADC5_GEN3_AMUX5_THM_100K_PU] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_THERM_100K_PU_PM7)
+ [ADC5_GEN3_AMUX6_THM_100K_PU] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_THERM_100K_PU_PM7)
+ [ADC5_GEN3_AMUX1_GPIO_100K_PU] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_THERM_100K_PU_PM7)
+ [ADC5_GEN3_AMUX2_GPIO_100K_PU] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_THERM_100K_PU_PM7)
+ [ADC5_GEN3_AMUX3_GPIO_100K_PU] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_THERM_100K_PU_PM7)
+ [ADC5_GEN3_AMUX4_GPIO_100K_PU] = ADC5_CHAN_TEMP(0,
+ SCALE_HW_CALIB_THERM_100K_PU_PM7)
+};
+
+static int adc5_gen3_get_fw_channel_data(struct adc5_chip *adc,
+ struct adc5_channel_prop *prop,
+ struct fwnode_handle *fwnode)
+{
+ const char *name = fwnode_get_name(fwnode);
+ const struct adc5_data *data = adc->data;
+ struct device *dev = adc->dev;
+ const char *channel_name;
+ u32 chan, value, sid;
+ u32 varr[2];
+ int ret;
+
+ ret = fwnode_property_read_u32(fwnode, "reg", &chan);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "invalid channel number %s\n",
+ name);
+
+ /*
+ * Value read from "reg" is virtual channel number
+ * virtual channel number = sid << 8 | channel number
+ */
+ sid = FIELD_GET(ADC5_GEN3_VIRTUAL_SID_MASK, chan);
+ chan = FIELD_GET(ADC5_GEN3_CHANNEL_MASK, chan);
+
+ if (chan > ADC5_MAX_CHANNEL)
+ return dev_err_probe(dev, -EINVAL,
+ "%s invalid channel number %d\n",
+ name, chan);
+
+ prop->common_props.channel = chan;
+ prop->common_props.sid = sid;
+
+ if (!adc->data->adc_chans[chan].info_mask)
+ return dev_err_probe(dev, -EINVAL, "Channel %#x not supported\n", chan);
+
+ channel_name = name;
+ fwnode_property_read_string(fwnode, "label", &channel_name);
+ prop->common_props.label = channel_name;
+
+ value = data->decimation[ADC5_DECIMATION_DEFAULT];
+ fwnode_property_read_u32(fwnode, "qcom,decimation", &value);
+ ret = qcom_adc5_decimation_from_dt(value, data->decimation);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%#x invalid decimation %d\n",
+ chan, value);
+ prop->common_props.decimation = ret;
+
+ prop->common_props.prescale = adc->data->adc_chans[chan].prescale_index;
+ ret = fwnode_property_read_u32_array(fwnode, "qcom,pre-scaling", varr, 2);
+ if (!ret) {
+ ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "%#x invalid pre-scaling <%d %d>\n",
+ chan, varr[0], varr[1]);
+ prop->common_props.prescale = ret;
+ }
+
+ value = data->hw_settle_1[VADC_DEF_HW_SETTLE_TIME];
+ fwnode_property_read_u32(fwnode, "qcom,hw-settle-time", &value);
+ ret = qcom_adc5_hw_settle_time_from_dt(value, data->hw_settle_1);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "%#x invalid hw-settle-time %d us\n",
+ chan, value);
+ prop->common_props.hw_settle_time_us = ret;
+
+ value = BIT(VADC_DEF_AVG_SAMPLES);
+ fwnode_property_read_u32(fwnode, "qcom,avg-samples", &value);
+ ret = qcom_adc5_avg_samples_from_dt(value);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%#x invalid avg-samples %d\n",
+ chan, value);
+ prop->common_props.avg_samples = ret;
+
+ if (fwnode_property_read_bool(fwnode, "qcom,ratiometric"))
+ prop->common_props.cal_method = ADC5_RATIOMETRIC_CAL;
+ else
+ prop->common_props.cal_method = ADC5_ABSOLUTE_CAL;
+
+ prop->adc_tm = fwnode_property_read_bool(fwnode, "qcom,adc-tm");
+ if (prop->adc_tm) {
+ adc->n_tm_channels++;
+ if (adc->n_tm_channels > (adc->dev_data.num_sdams * 8 - 1))
+ return dev_err_probe(dev, -EINVAL,
+ "Number of TM nodes %u greater than channels supported:%u\n",
+ adc->n_tm_channels,
+ adc->dev_data.num_sdams * 8 - 1);
+ }
+
+ return 0;
+}
+
+static const struct adc5_data adc5_gen3_data_pmic = {
+ .full_scale_code_volt = 0x70e4,
+ .adc_chans = adc5_gen3_chans_pmic,
+ .info = &adc5_gen3_info,
+ .decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
+ { 85, 340, 1360 },
+ .hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
+ { 15, 100, 200, 300,
+ 400, 500, 600, 700,
+ 1000, 2000, 4000, 8000,
+ 16000, 32000, 64000, 128000 },
+};
+
+static const struct of_device_id adc5_match_table[] = {
+ {
+ .compatible = "qcom,spmi-adc5-gen3",
+ .data = &adc5_gen3_data_pmic,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adc5_match_table);
+
+static int adc5_get_fw_data(struct adc5_chip *adc)
+{
+ const struct adc5_channels *adc_chan;
+ struct adc5_channel_prop *chan_props;
+ struct iio_chan_spec *iio_chan;
+ struct device *dev = adc->dev;
+ unsigned int index = 0;
+ int ret;
+
+ adc->nchannels = device_get_child_node_count(dev);
+ if (!adc->nchannels)
+ return dev_err_probe(dev, -EINVAL, "No ADC channels found\n");
+
+ adc->iio_chans = devm_kcalloc(dev, adc->nchannels,
+ sizeof(*adc->iio_chans), GFP_KERNEL);
+ if (!adc->iio_chans)
+ return -ENOMEM;
+
+ adc->chan_props = devm_kcalloc(dev, adc->nchannels,
+ sizeof(*adc->chan_props), GFP_KERNEL);
+ if (!adc->chan_props)
+ return -ENOMEM;
+
+ chan_props = adc->chan_props;
+ adc->n_tm_channels = 0;
+ iio_chan = adc->iio_chans;
+ adc->data = device_get_match_data(dev);
+
+ device_for_each_child_node_scoped(dev, child) {
+ ret = adc5_gen3_get_fw_channel_data(adc, chan_props, child);
+ if (ret)
+ return ret;
+
+ chan_props->chip = adc;
+ adc_chan = &adc->data->adc_chans[chan_props->common_props.channel];
+ chan_props->common_props.scale_fn_type = adc_chan->scale_fn_type;
+
+ iio_chan->channel = ADC5_GEN3_V_CHAN(chan_props->common_props);
+ iio_chan->info_mask_separate = adc_chan->info_mask;
+ iio_chan->type = adc_chan->type;
+ iio_chan->address = index;
+ iio_chan->indexed = 1;
+ iio_chan++;
+ chan_props++;
+ index++;
+ }
+
+ return 0;
+}
+
+static void adc5_gen3_uninit_aux(void *data)
+{
+ auxiliary_device_uninit(data);
+}
+
+static void adc5_gen3_delete_aux(void *data)
+{
+ auxiliary_device_delete(data);
+}
+
+static void adc5_gen3_aux_device_release(struct device *dev) {}
+
+static int adc5_gen3_add_aux_tm_device(struct adc5_chip *adc)
+{
+ struct tm5_aux_dev_wrapper *aux_device;
+ int i, ret, i_tm = 0;
+
+ aux_device = devm_kzalloc(adc->dev, sizeof(*aux_device), GFP_KERNEL);
+ if (!aux_device)
+ return -ENOMEM;
+
+ aux_device->aux_dev.name = "adc5_tm_gen3";
+ aux_device->aux_dev.dev.parent = adc->dev;
+ aux_device->aux_dev.dev.release = adc5_gen3_aux_device_release;
+
+ aux_device->tm_props = devm_kcalloc(adc->dev, adc->n_tm_channels,
+ sizeof(*aux_device->tm_props),
+ GFP_KERNEL);
+ if (!aux_device->tm_props)
+ return -ENOMEM;
+
+ aux_device->dev_data = &adc->dev_data;
+
+ for (i = 0; i < adc->nchannels; i++) {
+ if (!adc->chan_props[i].adc_tm)
+ continue;
+ aux_device->tm_props[i_tm] = adc->chan_props[i].common_props;
+ i_tm++;
+ }
+
+ device_set_of_node_from_dev(&aux_device->aux_dev.dev, adc->dev);
+
+ aux_device->n_tm_channels = adc->n_tm_channels;
+
+ ret = auxiliary_device_init(&aux_device->aux_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(adc->dev, adc5_gen3_uninit_aux,
+ &aux_device->aux_dev);
+ if (ret)
+ return ret;
+
+ ret = auxiliary_device_add(&aux_device->aux_dev);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(adc->dev, adc5_gen3_delete_aux,
+ &aux_device->aux_dev);
+ if (ret)
+ return ret;
+
+ adc->tm_aux = &aux_device->aux_dev;
+
+ return 0;
+}
+
+void adc5_gen3_mutex_lock(struct device *dev)
+ __acquires(&adc->lock)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
+ struct adc5_chip *adc = iio_priv(indio_dev);
+
+ mutex_lock(&adc->lock);
+}
+EXPORT_SYMBOL_NS_GPL(adc5_gen3_mutex_lock, "QCOM_SPMI_ADC5_GEN3");
+
+void adc5_gen3_mutex_unlock(struct device *dev)
+ __releases(&adc->lock)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
+ struct adc5_chip *adc = iio_priv(indio_dev);
+
+ mutex_unlock(&adc->lock);
+}
+EXPORT_SYMBOL_NS_GPL(adc5_gen3_mutex_unlock, "QCOM_SPMI_ADC5_GEN3");
+
+int adc5_gen3_get_scaled_reading(struct device *dev,
+ struct adc5_channel_common_prop *common_props,
+ int *val)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
+ struct adc5_chip *adc = iio_priv(indio_dev);
+ u16 adc_code_volt;
+ int ret;
+
+ ret = adc5_gen3_do_conversion(adc, common_props, &adc_code_volt);
+ if (ret)
+ return ret;
+
+ return qcom_adc5_hw_scale(common_props->scale_fn_type,
+ common_props->prescale,
+ adc->data, adc_code_volt, val);
+}
+EXPORT_SYMBOL_NS_GPL(adc5_gen3_get_scaled_reading, "QCOM_SPMI_ADC5_GEN3");
+
+int adc5_gen3_therm_code_to_temp(struct device *dev,
+ struct adc5_channel_common_prop *common_props,
+ u16 code, int *val)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
+ struct adc5_chip *adc = iio_priv(indio_dev);
+
+ return qcom_adc5_hw_scale(common_props->scale_fn_type,
+ common_props->prescale,
+ adc->data, code, val);
+}
+EXPORT_SYMBOL_NS_GPL(adc5_gen3_therm_code_to_temp, "QCOM_SPMI_ADC5_GEN3");
+
+void adc5_gen3_register_tm_event_notifier(struct device *dev,
+ void (*handler)(struct auxiliary_device *))
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
+ struct adc5_chip *adc = iio_priv(indio_dev);
+
+ adc->handler = handler;
+}
+EXPORT_SYMBOL_NS_GPL(adc5_gen3_register_tm_event_notifier, "QCOM_SPMI_ADC5_GEN3");
+
+static int adc5_gen3_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct iio_dev *indio_dev;
+ struct adc5_chip *adc;
+ struct regmap *regmap;
+ int ret, i;
+ u32 *reg;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ adc = iio_priv(indio_dev);
+ adc->dev_data.regmap = regmap;
+ adc->dev = dev;
+
+ ret = device_property_count_u32(dev, "reg");
+ if (ret < 0)
+ return ret;
+
+ adc->dev_data.num_sdams = ret;
+
+ reg = devm_kcalloc(dev, adc->dev_data.num_sdams, sizeof(u32),
+ GFP_KERNEL);
+ if (!reg)
+ return -ENOMEM;
+
+ ret = device_property_read_u32_array(dev, "reg", reg,
+ adc->dev_data.num_sdams);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read reg property\n");
+
+ adc->dev_data.base = devm_kcalloc(dev, adc->dev_data.num_sdams,
+ sizeof(*adc->dev_data.base),
+ GFP_KERNEL);
+ if (!adc->dev_data.base)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, indio_dev);
+ init_completion(&adc->complete);
+ ret = devm_mutex_init(dev, &adc->lock);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < adc->dev_data.num_sdams; i++) {
+ adc->dev_data.base[i].base_addr = reg[i];
+
+ ret = platform_get_irq(pdev, i);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Getting IRQ %d failed\n", i);
+
+ adc->dev_data.base[i].irq = ret;
+
+ adc->dev_data.base[i].irq_name = devm_kasprintf(dev, GFP_KERNEL,
+ "sdam%d", i);
+ if (!adc->dev_data.base[i].irq_name)
+ return -ENOMEM;
+ }
+
+ ret = devm_request_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq,
+ adc5_gen3_isr, 0,
+ adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name,
+ adc);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to request SDAM%d irq\n",
+ ADC5_GEN3_VADC_SDAM);
+
+ ret = adc5_get_fw_data(adc);
+ if (ret)
+ return ret;
+
+ if (adc->n_tm_channels > 0) {
+ ret = adc5_gen3_add_aux_tm_device(adc);
+ if (ret)
+ dev_err_probe(dev, ret,
+ "Failed to add auxiliary TM device\n");
+ }
+
+ indio_dev->name = "spmi-adc5-gen3";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &adc5_gen3_info;
+ indio_dev->channels = adc->iio_chans;
+ indio_dev->num_channels = adc->nchannels;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static struct platform_driver adc5_gen3_driver = {
+ .driver = {
+ .name = "qcom-spmi-adc5-gen3",
+ .of_match_table = adc5_match_table,
+ },
+ .probe = adc5_gen3_probe,
+};
+module_platform_driver(adc5_gen3_driver);
+
+MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 Gen3 ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3");
diff --git a/include/linux/iio/adc/qcom-adc5-gen3-common.h b/include/linux/iio/adc/qcom-adc5-gen3-common.h
new file mode 100644
index 000000000000..89c552a77341
--- /dev/null
+++ b/include/linux/iio/adc/qcom-adc5-gen3-common.h
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * Code used in the main and auxiliary Qualcomm PMIC voltage ADCs
+ * of type ADC5 Gen3.
+ */
+
+#ifndef QCOM_ADC5_GEN3_COMMON_H
+#define QCOM_ADC5_GEN3_COMMON_H
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/iio/adc/qcom-vadc-common.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define ADC5_GEN3_HS 0x45
+#define ADC5_GEN3_HS_BUSY BIT(7)
+#define ADC5_GEN3_HS_READY BIT(0)
+
+#define ADC5_GEN3_STATUS1 0x46
+#define ADC5_GEN3_STATUS1_CONV_FAULT BIT(7)
+#define ADC5_GEN3_STATUS1_THR_CROSS BIT(6)
+#define ADC5_GEN3_STATUS1_EOC BIT(0)
+
+#define ADC5_GEN3_TM_EN_STS 0x47
+#define ADC5_GEN3_TM_HIGH_STS 0x48
+#define ADC5_GEN3_TM_LOW_STS 0x49
+
+#define ADC5_GEN3_EOC_STS 0x4a
+#define ADC5_GEN3_EOC_CHAN_0 BIT(0)
+
+#define ADC5_GEN3_EOC_CLR 0x4b
+#define ADC5_GEN3_TM_HIGH_STS_CLR 0x4c
+#define ADC5_GEN3_TM_LOW_STS_CLR 0x4d
+#define ADC5_GEN3_CONV_ERR_CLR 0x4e
+#define ADC5_GEN3_CONV_ERR_CLR_REQ BIT(0)
+
+#define ADC5_GEN3_SID 0x4f
+#define ADC5_GEN3_SID_MASK GENMASK(3, 0)
+
+#define ADC5_GEN3_PERPH_CH 0x50
+#define ADC5_GEN3_CHAN_CONV_REQ BIT(7)
+
+#define ADC5_GEN3_TIMER_SEL 0x51
+#define ADC5_GEN3_TIME_IMMEDIATE 0x1
+
+#define ADC5_GEN3_DIG_PARAM 0x52
+#define ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK GENMASK(5, 4)
+#define ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK GENMASK(3, 2)
+
+#define ADC5_GEN3_FAST_AVG 0x53
+#define ADC5_GEN3_FAST_AVG_CTL_EN BIT(7)
+#define ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK GENMASK(2, 0)
+
+#define ADC5_GEN3_ADC_CH_SEL_CTL 0x54
+#define ADC5_GEN3_DELAY_CTL 0x55
+#define ADC5_GEN3_HW_SETTLE_DELAY_MASK GENMASK(3, 0)
+
+#define ADC5_GEN3_CH_EN 0x56
+#define ADC5_GEN3_HIGH_THR_INT_EN BIT(1)
+#define ADC5_GEN3_LOW_THR_INT_EN BIT(0)
+
+#define ADC5_GEN3_LOW_THR0 0x57
+#define ADC5_GEN3_LOW_THR1 0x58
+#define ADC5_GEN3_HIGH_THR0 0x59
+#define ADC5_GEN3_HIGH_THR1 0x5a
+
+#define ADC5_GEN3_CH_DATA0(channel) (0x5c + (channel) * 2)
+#define ADC5_GEN3_CH_DATA1(channel) (0x5d + (channel) * 2)
+
+#define ADC5_GEN3_CONV_REQ 0xe5
+#define ADC5_GEN3_CONV_REQ_REQ BIT(0)
+
+#define ADC5_GEN3_VIRTUAL_SID_MASK GENMASK(15, 8)
+#define ADC5_GEN3_CHANNEL_MASK GENMASK(7, 0)
+#define ADC5_GEN3_V_CHAN(x) \
+ (FIELD_PREP(ADC5_GEN3_VIRTUAL_SID_MASK, (x).sid) | (x).channel)
+
+/* ADC channels for PMIC5 Gen3 */
+#define ADC5_GEN3_REF_GND 0x00
+#define ADC5_GEN3_1P25VREF 0x01
+#define ADC5_GEN3_DIE_TEMP 0x03
+#define ADC5_GEN3_USB_SNS_V_16 0x11
+#define ADC5_GEN3_VIN_DIV16_MUX 0x12
+#define ADC5_GEN3_VPH_PWR 0x8e
+#define ADC5_GEN3_VBAT_SNS_QBG 0x8f
+/* 100k pull-up channels */
+#define ADC5_GEN3_AMUX1_THM_100K_PU 0x44
+#define ADC5_GEN3_AMUX2_THM_100K_PU 0x45
+#define ADC5_GEN3_AMUX3_THM_100K_PU 0x46
+#define ADC5_GEN3_AMUX4_THM_100K_PU 0x47
+#define ADC5_GEN3_AMUX5_THM_100K_PU 0x48
+#define ADC5_GEN3_AMUX6_THM_100K_PU 0x49
+#define ADC5_GEN3_AMUX1_GPIO_100K_PU 0x4a
+#define ADC5_GEN3_AMUX2_GPIO_100K_PU 0x4b
+#define ADC5_GEN3_AMUX3_GPIO_100K_PU 0x4c
+#define ADC5_GEN3_AMUX4_GPIO_100K_PU 0x4d
+
+#define ADC5_MAX_CHANNEL 0xc0
+
+enum adc5_cal_method {
+ ADC5_NO_CAL = 0,
+ ADC5_RATIOMETRIC_CAL,
+ ADC5_ABSOLUTE_CAL,
+};
+
+enum adc5_time_select {
+ MEAS_INT_DISABLE = 0,
+ MEAS_INT_IMMEDIATE,
+ MEAS_INT_50MS,
+ MEAS_INT_100MS,
+ MEAS_INT_1S,
+ MEAS_INT_NONE,
+};
+
+/**
+ * struct adc5_sdam_data - data per SDAM allocated for adc usage
+ * @base_addr: base address for the ADC SDAM peripheral.
+ * @irq_name: ADC IRQ name.
+ * @irq: ADC IRQ number.
+ */
+struct adc5_sdam_data {
+ u16 base_addr;
+ const char *irq_name;
+ int irq;
+};
+
+/**
+ * struct adc5_device_data - Top-level ADC device data
+ * @regmap: ADC peripheral register map field.
+ * @base: array of SDAM data.
+ * @num_sdams: number of ADC SDAM peripherals.
+ */
+struct adc5_device_data {
+ struct regmap *regmap;
+ struct adc5_sdam_data *base;
+ int num_sdams;
+};
+
+/**
+ * struct adc5_channel_common_prop - ADC channel properties (common to ADC and TM).
+ * @channel: channel number, refer to the channel list.
+ * @cal_method: calibration method.
+ * @decimation: sampling rate supported for the channel.
+ * @sid: ID of PMIC owning the channel.
+ * @label: Channel name used in device tree.
+ * @prescale: channel scaling performed on the input signal.
+ * @hw_settle_time_us: the time between AMUX being configured and the
+ * start of conversion in uS.
+ * @avg_samples: ability to provide single result from the ADC
+ * that is an average of multiple measurements.
+ * @scale_fn_type: Represents the scaling function to convert voltage
+ * physical units desired by the client for the channel.
+ */
+struct adc5_channel_common_prop {
+ unsigned int channel;
+ enum adc5_cal_method cal_method;
+ unsigned int decimation;
+ unsigned int sid;
+ const char *label;
+ unsigned int prescale;
+ unsigned int hw_settle_time_us;
+ unsigned int avg_samples;
+ enum vadc_scale_fn_type scale_fn_type;
+};
+
+/**
+ * struct tm5_aux_dev_wrapper - wrapper structure around TM auxiliary device
+ * @aux_dev: TM auxiliary device structure.
+ * @dev_data: Top-level ADC device data.
+ * @tm_props: Array of common ADC channel properties for TM channels.
+ * @n_tm_channels: number of TM channels.
+ */
+struct tm5_aux_dev_wrapper {
+ struct auxiliary_device aux_dev;
+ struct adc5_device_data *dev_data;
+ struct adc5_channel_common_prop *tm_props;
+ unsigned int n_tm_channels;
+};
+
+int adc5_gen3_read(struct adc5_device_data *adc, unsigned int sdam_index,
+ u16 offset, u8 *data, int len);
+
+int adc5_gen3_write(struct adc5_device_data *adc, unsigned int sdam_index,
+ u16 offset, u8 *data, int len);
+
+int adc5_gen3_poll_wait_hs(struct adc5_device_data *adc,
+ unsigned int sdam_index);
+
+void adc5_gen3_update_dig_param(struct adc5_channel_common_prop *prop,
+ u8 *data);
+
+int adc5_gen3_status_clear(struct adc5_device_data *adc,
+ int sdam_index, u16 offset, u8 *val, int len);
+
+void adc5_gen3_mutex_lock(struct device *dev);
+void adc5_gen3_mutex_unlock(struct device *dev);
+int adc5_gen3_get_scaled_reading(struct device *dev,
+ struct adc5_channel_common_prop *common_props,
+ int *val);
+int adc5_gen3_therm_code_to_temp(struct device *dev,
+ struct adc5_channel_common_prop *common_props,
+ u16 code, int *val);
+void adc5_gen3_register_tm_event_notifier(struct device *dev,
+ void (*handler)(struct auxiliary_device *));
+
+#endif /* QCOM_ADC5_GEN3_COMMON_H */
--
2.25.1
^ permalink raw reply related
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