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).
 -----BEGIN PGP SIGNATURE-----
 
 iQJEBAABCgAuFiEE3dJiKD0RGyM7briowTdm5oaLg9cFAmjEJbkQHGtyemtAa2Vy
 bmVsLm9yZwAKCRDBN2bmhouD1wotD/9sMtDTS2graV2Rda60ltXBYbTQmZGFCPbu
 0QLoXl7spLvKGAbS/Dp7nRsf3oyZ1Hnc0uqeWKNglJNwESelgPX0gdc4/3CMTg2c
 CJ8Jw7IuxAuoy09W+EJsY+/tdEnmQNO//YVltn6Ycmo3T2CixYAbAUgmQQ7f1zaA
 RjK/3AYUJMcVs5H/ZBOHMWUZyUCTYlxxppBpcXfqJDoyY0E57JtPX5n2jdHSiwKt
 qyNOUWWVz3gG+tWXc1vQXoqnQTUQ7QJPMgaMb3kz4khrlz6QxedRT5uy0b7c6cJs
 yNLBL6pwXNtHcpmESLmRpI4dBeJPeJhEyWS9UfiXH4AduynUQkKvGQxwjC2OUSPB
 xhF8G1aZ59VM+cmhPcR5Vp/RtKpls3J07EeVBB0oEbABEIoXuN48m9fxaxplUdhG
 /WgwaONFBmBQEOcIUpSlASGysHOoRcVRg21+gGvOqH5gh9AwuqhIPZzw7kONhNaU
 WT1mSZaf4XtdqKnE5OorQteutgU+w6GEoS+0u20SKvcnVrGQi4vb2VAzzV/JSHG1
 KHqTbNGGR+TDgrz0caNTOK1vZ4Qtv4JR/ZO0GH6BVqeqWsLI6sP/N/tgIbQ4OcJ/
 z48ejpR7/c/kd8Nl+5U8bg7I3LsnQwpJwvzGH8Ot5KXgjoRYGt8pBzQyhgYNbFb1
 UdAUJmi4BQ==
 =pOwr
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmjIID8ACgkQmmx57+YA
 GNn9sA/+JLhonck3esYyA1F3/rdrllTDwCCLU357LqVBTuIcmXC/ePKQU0MMkBcg
 KA8S9tPyeEiegl2dhHSajrdavcBoUcXs3HVDVfLafcBP5ZItQuVaNj0gnXKf+npa
 cE+udnqihXBSSUeNJFPS8eO8qh77amUJxGF570zPoA62M8Z3lq1ZevI0LeA69eLO
 yujIEU7tSLjkcQlPr/Yj9+MjR8X672zp8sCDHrspGNtnyXklty5cC8P9CJqnCWBv
 hpbzRT+R0GeugiswdFqS5Kt0umXsdilXZRU4Azc3m+OVgzTDFZovjj38sZ0QMW1K
 oktwHKt7IZ3/T9v+sQLHemiYVoGOIaveBHEN7CJ1acGMPeSZHm68Aggs2JMsofqy
 /u2dCGWyMBgZB0K5ZQ532Q7mj+tW1SVvX33hJd3/MpskyRBJOecJdo8NHgor3g/q
 yWhfj5nNFUoIMoxl+NuRe6gbmwwSH9DUnAI7a6LAHoBkfth9pLm26hYfMypoYBIE
 R3MM2ywDdMRNUIAd0JseBmLLQ2Ka/jU+UdIH3+wd9FY5f4+ghNmRIsnxaWVy4aI/
 /nSMDR9jb+8lNgmZzqTJmDPBmK18AZdCc+nnrifyfCls7uwux4w/F4zJbReYkU/W
 YKVxbUWO/C0dmRC1vOOcfRsmduMjWex/5wj+kStNE20j8OVeLdY=
 =4h/m
 -----END PGP SIGNATURE-----

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>
pull/1354/merge
Arnd Bergmann 2025-09-15 16:18:34 +02:00
commit 7b1349bd47
3 changed files with 275 additions and 27 deletions

View File

@ -36,6 +36,7 @@ properties:
- items:
- enum:
- google,gs101-usi
- samsung,exynos2200-usi
- samsung,exynosautov9-usi
- samsung,exynosautov920-usi
- const: samsung,exynos850-usi

View File

@ -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]));
}

View File

@ -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, &reg);
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,
&regmap_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,
};