diff options
author | Arnd Bergmann <arnd@arndb.de> | 2025-09-15 16:18:34 +0200 |
---|---|---|
committer | Arnd Bergmann <arnd@arndb.de> | 2025-09-15 16:18:39 +0200 |
commit | 7b1349bd47a2fcc1c838986d00fcf76bd7428dc2 (patch) | |
tree | cf081364d5a9fdd6ad7f85da6c3f80d09d4b12f7 | |
parent | 68454c6fc1494894e0972027b86f530705a973f3 (diff) | |
parent | 1da4cbefed4a2e69ebad81fc9b356cd9b807f380 (diff) |
Merge tag 'samsung-drivers-6.18' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux into soc/drivers
Samsung SoC drivers for v6.18
1. Google GS101:
Enable CPU Idle, which needs programming C2 idle hints
via ACPM firmware (Alive Clock and Power Manager). The patch
introducing this depends on 'local-timer-stop' Devicetree property,
which was merged in v6.17.
Fix handling error codes in ACPM firmware driver when talking to
PMIC.
2. Exynos2200: Add dedicated compatible for serial engines (USI).
* tag 'samsung-drivers-6.18' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux:
firmware: exynos-acpm: fix PMIC returned errno
dt-bindings: soc: samsung: usi: add samsung,exynos2200-usi compatible
soc: samsung: exynos-pmu: Enable CPU Idle for gs101
Link: https://lore.kernel.org/r/20250912135448.203678-2-krzysztof.kozlowski@linaro.org
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
-rw-r--r-- | Documentation/devicetree/bindings/soc/samsung/exynos-usi.yaml | 1 | ||||
-rw-r--r-- | drivers/firmware/samsung/exynos-acpm-pmic.c | 25 | ||||
-rw-r--r-- | drivers/soc/samsung/exynos-pmu.c | 276 |
3 files changed, 275 insertions, 27 deletions
diff --git a/Documentation/devicetree/bindings/soc/samsung/exynos-usi.yaml b/Documentation/devicetree/bindings/soc/samsung/exynos-usi.yaml index cb22637091e8..c694926e56ef 100644 --- a/Documentation/devicetree/bindings/soc/samsung/exynos-usi.yaml +++ b/Documentation/devicetree/bindings/soc/samsung/exynos-usi.yaml @@ -36,6 +36,7 @@ properties: - items: - enum: - google,gs101-usi + - samsung,exynos2200-usi - samsung,exynosautov9-usi - samsung,exynosautov920-usi - const: samsung,exynos850-usi diff --git a/drivers/firmware/samsung/exynos-acpm-pmic.c b/drivers/firmware/samsung/exynos-acpm-pmic.c index 39b33a356ebd..961d7599e422 100644 --- a/drivers/firmware/samsung/exynos-acpm-pmic.c +++ b/drivers/firmware/samsung/exynos-acpm-pmic.c @@ -4,7 +4,9 @@ * Copyright 2020 Google LLC. * Copyright 2024 Linaro Ltd. */ +#include <linux/array_size.h> #include <linux/bitfield.h> +#include <linux/errno.h> #include <linux/firmware/samsung/exynos-acpm-protocol.h> #include <linux/ktime.h> #include <linux/types.h> @@ -33,6 +35,19 @@ enum exynos_acpm_pmic_func { ACPM_PMIC_BULK_WRITE, }; +static const int acpm_pmic_linux_errmap[] = { + [0] = 0, /* ACPM_PMIC_SUCCESS */ + [1] = -EACCES, /* Read register can't be accessed or issues to access it. */ + [2] = -EACCES, /* Write register can't be accessed or issues to access it. */ +}; + +static int acpm_pmic_to_linux_err(int err) +{ + if (err >= 0 && err < ARRAY_SIZE(acpm_pmic_linux_errmap)) + return acpm_pmic_linux_errmap[err]; + return -EIO; +} + static inline u32 acpm_pmic_set_bulk(u32 data, unsigned int i) { return (data & ACPM_PMIC_BULK_MASK) << (ACPM_PMIC_BULK_SHIFT * i); @@ -79,7 +94,7 @@ int acpm_pmic_read_reg(const struct acpm_handle *handle, *buf = FIELD_GET(ACPM_PMIC_VALUE, xfer.rxd[1]); - return FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]); + return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1])); } static void acpm_pmic_init_bulk_read_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan, @@ -110,7 +125,7 @@ int acpm_pmic_bulk_read(const struct acpm_handle *handle, if (ret) return ret; - ret = FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]); + ret = acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1])); if (ret) return ret; @@ -150,7 +165,7 @@ int acpm_pmic_write_reg(const struct acpm_handle *handle, if (ret) return ret; - return FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]); + return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1])); } static void acpm_pmic_init_bulk_write_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan, @@ -190,7 +205,7 @@ int acpm_pmic_bulk_write(const struct acpm_handle *handle, if (ret) return ret; - return FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]); + return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1])); } static void acpm_pmic_init_update_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan, @@ -220,5 +235,5 @@ int acpm_pmic_update_reg(const struct acpm_handle *handle, if (ret) return ret; - return FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]); + return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1])); } diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c index a77288f49d24..22c50ca2aa79 100644 --- a/drivers/soc/samsung/exynos-pmu.c +++ b/drivers/soc/samsung/exynos-pmu.c @@ -7,7 +7,9 @@ #include <linux/array_size.h> #include <linux/arm-smccc.h> +#include <linux/bitmap.h> #include <linux/cpuhotplug.h> +#include <linux/cpu_pm.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/mfd/core.h> @@ -15,6 +17,7 @@ #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/delay.h> +#include <linux/reboot.h> #include <linux/regmap.h> #include <linux/soc/samsung/exynos-regs-pmu.h> @@ -35,6 +38,15 @@ struct exynos_pmu_context { const struct exynos_pmu_data *pmu_data; struct regmap *pmureg; struct regmap *pmuintrgen; + /* + * Serialization lock for CPU hot plug and cpuidle ACPM hint + * programming. Also protects in_cpuhp, sys_insuspend & sys_inreboot + * flags. + */ + raw_spinlock_t cpupm_lock; + unsigned long *in_cpuhp; + bool sys_insuspend; + bool sys_inreboot; }; void __iomem *pmu_base_addr; @@ -221,6 +233,15 @@ static const struct regmap_config regmap_smccfg = { .reg_read = tensor_sec_reg_read, .reg_write = tensor_sec_reg_write, .reg_update_bits = tensor_sec_update_bits, + .use_raw_spinlock = true, +}; + +static const struct regmap_config regmap_pmu_intr = { + .name = "pmu_intr_gen", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .use_raw_spinlock = true, }; static const struct exynos_pmu_data gs101_pmu_data = { @@ -330,13 +351,19 @@ struct regmap *exynos_get_pmu_regmap_by_phandle(struct device_node *np, EXPORT_SYMBOL_GPL(exynos_get_pmu_regmap_by_phandle); /* - * CPU_INFORM register hint values which are used by - * EL3 firmware (el3mon). + * CPU_INFORM register "hint" values are required to be programmed in addition to + * the standard PSCI calls to have functional CPU hotplug and CPU idle states. + * This is required to workaround limitations in the el3mon/ACPM firmware. */ #define CPU_INFORM_CLEAR 0 #define CPU_INFORM_C2 1 -static int gs101_cpuhp_pmu_online(unsigned int cpu) +/* + * __gs101_cpu_pmu_ prefix functions are common code shared by CPU PM notifiers + * (CPUIdle) and CPU hotplug callbacks. Functions should be called with IRQs + * disabled and cpupm_lock held. + */ +static int __gs101_cpu_pmu_online(unsigned int cpu) { unsigned int cpuhint = smp_processor_id(); u32 reg, mask; @@ -358,10 +385,48 @@ static int gs101_cpuhp_pmu_online(unsigned int cpu) return 0; } -static int gs101_cpuhp_pmu_offline(unsigned int cpu) +/* Called from CPU PM notifier (CPUIdle code path) with IRQs disabled */ +static int gs101_cpu_pmu_online(void) +{ + int cpu; + + raw_spin_lock(&pmu_context->cpupm_lock); + + if (pmu_context->sys_inreboot) { + raw_spin_unlock(&pmu_context->cpupm_lock); + return NOTIFY_OK; + } + + cpu = smp_processor_id(); + __gs101_cpu_pmu_online(cpu); + raw_spin_unlock(&pmu_context->cpupm_lock); + + return NOTIFY_OK; +} + +/* Called from CPU hot plug callback with IRQs enabled */ +static int gs101_cpuhp_pmu_online(unsigned int cpu) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&pmu_context->cpupm_lock, flags); + + __gs101_cpu_pmu_online(cpu); + /* + * Mark this CPU as having finished the hotplug. + * This means this CPU can now enter C2 idle state. + */ + clear_bit(cpu, pmu_context->in_cpuhp); + raw_spin_unlock_irqrestore(&pmu_context->cpupm_lock, flags); + + return 0; +} + +/* Common function shared by both CPU hot plug and CPUIdle */ +static int __gs101_cpu_pmu_offline(unsigned int cpu) { - u32 reg, mask; unsigned int cpuhint = smp_processor_id(); + u32 reg, mask; /* set cpu inform hint */ regmap_write(pmu_context->pmureg, GS101_CPU_INFORM(cpuhint), @@ -379,6 +444,165 @@ static int gs101_cpuhp_pmu_offline(unsigned int cpu) regmap_read(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_UPEND, ®); regmap_write(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_CLEAR, reg & mask); + + return 0; +} + +/* Called from CPU PM notifier (CPUIdle code path) with IRQs disabled */ +static int gs101_cpu_pmu_offline(void) +{ + int cpu; + + raw_spin_lock(&pmu_context->cpupm_lock); + cpu = smp_processor_id(); + + if (test_bit(cpu, pmu_context->in_cpuhp)) { + raw_spin_unlock(&pmu_context->cpupm_lock); + return NOTIFY_BAD; + } + + /* Ignore CPU_PM_ENTER event in reboot or suspend sequence. */ + if (pmu_context->sys_insuspend || pmu_context->sys_inreboot) { + raw_spin_unlock(&pmu_context->cpupm_lock); + return NOTIFY_OK; + } + + __gs101_cpu_pmu_offline(cpu); + raw_spin_unlock(&pmu_context->cpupm_lock); + + return NOTIFY_OK; +} + +/* Called from CPU hot plug callback with IRQs enabled */ +static int gs101_cpuhp_pmu_offline(unsigned int cpu) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&pmu_context->cpupm_lock, flags); + /* + * Mark this CPU as entering hotplug. So as not to confuse + * ACPM the CPU entering hotplug should not enter C2 idle state. + */ + set_bit(cpu, pmu_context->in_cpuhp); + __gs101_cpu_pmu_offline(cpu); + + raw_spin_unlock_irqrestore(&pmu_context->cpupm_lock, flags); + + return 0; +} + +static int gs101_cpu_pm_notify_callback(struct notifier_block *self, + unsigned long action, void *v) +{ + switch (action) { + case CPU_PM_ENTER: + return gs101_cpu_pmu_offline(); + + case CPU_PM_EXIT: + return gs101_cpu_pmu_online(); + } + + return NOTIFY_OK; +} + +static struct notifier_block gs101_cpu_pm_notifier = { + .notifier_call = gs101_cpu_pm_notify_callback, + /* + * We want to be called first, as the ACPM hint and handshake is what + * puts the CPU into C2. + */ + .priority = INT_MAX +}; + +static int exynos_cpupm_reboot_notifier(struct notifier_block *nb, + unsigned long event, void *v) +{ + unsigned long flags; + + switch (event) { + case SYS_POWER_OFF: + case SYS_RESTART: + raw_spin_lock_irqsave(&pmu_context->cpupm_lock, flags); + pmu_context->sys_inreboot = true; + raw_spin_unlock_irqrestore(&pmu_context->cpupm_lock, flags); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block exynos_cpupm_reboot_nb = { + .priority = INT_MAX, + .notifier_call = exynos_cpupm_reboot_notifier, +}; + +static int setup_cpuhp_and_cpuidle(struct device *dev) +{ + struct device_node *intr_gen_node; + struct resource intrgen_res; + void __iomem *virt_addr; + int ret, cpu; + + intr_gen_node = of_parse_phandle(dev->of_node, + "google,pmu-intr-gen-syscon", 0); + if (!intr_gen_node) { + /* + * To maintain support for older DTs that didn't specify syscon + * phandle just issue a warning rather than fail to probe. + */ + dev_warn(dev, "pmu-intr-gen syscon unavailable\n"); + return 0; + } + + /* + * To avoid lockdep issues (CPU PM notifiers use raw spinlocks) create + * a mmio regmap for pmu-intr-gen that uses raw spinlocks instead of + * syscon provided regmap. + */ + ret = of_address_to_resource(intr_gen_node, 0, &intrgen_res); + of_node_put(intr_gen_node); + + virt_addr = devm_ioremap(dev, intrgen_res.start, + resource_size(&intrgen_res)); + if (!virt_addr) + return -ENOMEM; + + pmu_context->pmuintrgen = devm_regmap_init_mmio(dev, virt_addr, + ®map_pmu_intr); + if (IS_ERR(pmu_context->pmuintrgen)) { + dev_err(dev, "failed to initialize pmu-intr-gen regmap\n"); + return PTR_ERR(pmu_context->pmuintrgen); + } + + /* register custom mmio regmap with syscon */ + ret = of_syscon_register_regmap(intr_gen_node, + pmu_context->pmuintrgen); + if (ret) + return ret; + + pmu_context->in_cpuhp = devm_bitmap_zalloc(dev, num_possible_cpus(), + GFP_KERNEL); + if (!pmu_context->in_cpuhp) + return -ENOMEM; + + raw_spin_lock_init(&pmu_context->cpupm_lock); + pmu_context->sys_inreboot = false; + pmu_context->sys_insuspend = false; + + /* set PMU to power on */ + for_each_online_cpu(cpu) + gs101_cpuhp_pmu_online(cpu); + + /* register CPU hotplug callbacks */ + cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "soc/exynos-pmu:prepare", + gs101_cpuhp_pmu_online, NULL); + + cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/exynos-pmu:online", + NULL, gs101_cpuhp_pmu_offline); + + /* register CPU PM notifiers for cpuidle */ + cpu_pm_register_notifier(&gs101_cpu_pm_notifier); + register_reboot_notifier(&exynos_cpupm_reboot_nb); return 0; } @@ -435,23 +659,9 @@ static int exynos_pmu_probe(struct platform_device *pdev) pmu_context->dev = dev; if (pmu_context->pmu_data && pmu_context->pmu_data->pmu_cpuhp) { - pmu_context->pmuintrgen = syscon_regmap_lookup_by_phandle(dev->of_node, - "google,pmu-intr-gen-syscon"); - if (IS_ERR(pmu_context->pmuintrgen)) { - /* - * To maintain support for older DTs that didn't specify syscon phandle - * just issue a warning rather than fail to probe. - */ - dev_warn(&pdev->dev, "pmu-intr-gen syscon unavailable\n"); - } else { - cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, - "soc/exynos-pmu:prepare", - gs101_cpuhp_pmu_online, NULL); - - cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, - "soc/exynos-pmu:online", - NULL, gs101_cpuhp_pmu_offline); - } + ret = setup_cpuhp_and_cpuidle(dev); + if (ret) + return ret; } if (pmu_context->pmu_data && pmu_context->pmu_data->pmu_init) @@ -471,10 +681,32 @@ static int exynos_pmu_probe(struct platform_device *pdev) return 0; } +static int exynos_cpupm_suspend_noirq(struct device *dev) +{ + raw_spin_lock(&pmu_context->cpupm_lock); + pmu_context->sys_insuspend = true; + raw_spin_unlock(&pmu_context->cpupm_lock); + return 0; +} + +static int exynos_cpupm_resume_noirq(struct device *dev) +{ + raw_spin_lock(&pmu_context->cpupm_lock); + pmu_context->sys_insuspend = false; + raw_spin_unlock(&pmu_context->cpupm_lock); + return 0; +} + +static const struct dev_pm_ops cpupm_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(exynos_cpupm_suspend_noirq, + exynos_cpupm_resume_noirq) +}; + static struct platform_driver exynos_pmu_driver = { .driver = { .name = "exynos-pmu", .of_match_table = exynos_pmu_of_device_ids, + .pm = pm_sleep_ptr(&cpupm_pm_ops), }, .probe = exynos_pmu_probe, }; |