* [PATCH 2/4] clk: meson: s4-pll: add the CPU clock
2026-10-25 19:22 [PATCH 0/4] arm64: amlogic: S4 CPU frequency scaling christian.koever-draxl
2026-10-25 19:22 ` [PATCH 1/4] dt-bindings: clock: amlogic,s4-pll-clkc: add the CPU clock christian.koever-draxl
@ 2026-10-25 19:22 ` christian.koever-draxl
2026-10-25 19:22 ` [PATCH 3/4] firmware: meson: sm: add the CPU speed bin call christian.koever-draxl
2026-10-25 19:22 ` [PATCH 4/4] cpufreq: add an Amlogic speed-bin driver christian.koever-draxl
3 siblings, 0 replies; 10+ messages in thread
From: christian.koever-draxl @ 2026-10-25 19:22 UTC (permalink / raw)
To: neil.armstrong, jbrunet, sboyd, bmasney+clk, khilman,
martin.blumenstingl, rafael, viresh.kumar, robh, krzk+dt,
conor+dt
Cc: linux-kernel, linux-amlogic, linux-clk, linux-arm-kernel,
linux-pm, devicetree, Christian Stefan Kövér-Draxl
From: Christian Stefan Kövér-Draxl <ckoever@outlook.de>
On S4 the CPU clock control register is not reachable from the normal
world: every access goes through the BL31 firmware, which exposes two
SMC services, one for sys_pll (0x82000098) and one for the CPU clock
tree (0x82000099). They take pre-shifted mask/value pairs and behave
like a secure regmap_update_bits(). The sys_pll status register is
still readable from the ANACTRL block.
The tree behind it is the G12A one: two "dynamic" premux/postmux/divider
chains fed from xtal, fclk_div2 and fclk_div3, and a final mux picking
the dynamic tree or sys_pll. As the firmware calls do not map onto
independent muxes and dividers, and the park/reprogram/restore order is
what keeps the cores alive while sys_pll changes, model the whole tree
as a single clock:
- rates up to 1 GHz come from the dynamic tree,
- higher rates come from sys_pll, with the CPU parked on the 1 GHz
dynamic clock while the PLL is reprogrammed,
- the switch back to sys_pll waits for the new post divider and lock,
and stays parked with an error if the PLL does not lock.
The firmware boots the cores from sys_pll at about 1.5 GHz, while a PWM
CPU supply can still be at the bottom of its range until cpufreq takes
over, so the clock parks on 1 GHz at registration.
The settings mirror the tables of the vendor firmware and kernel.
Tested on an S905W2 board with cpufreq cycling through all operating
points from 100 MHz to at least 1.7 GHz.
Signed-off-by: Christian Stefan Kövér-Draxl <ckoever@outlook.de>
---
drivers/clk/meson/s4-pll.c | 320 +++++++++++++++++++++++++++++++++++++
1 file changed, 320 insertions(+)
diff --git a/drivers/clk/meson/s4-pll.c b/drivers/clk/meson/s4-pll.c
index 56ce6f566e53..951c7060b544 100644
--- a/drivers/clk/meson/s4-pll.c
+++ b/drivers/clk/meson/s4-pll.c
@@ -6,7 +6,10 @@
* Author: Yu Tu <yu.tu@amlogic.com>
*/
+#include <linux/arm-smccc.h>
+#include <linux/bitfield.h>
#include <linux/clk-provider.h>
+#include <linux/delay.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
@@ -16,6 +19,7 @@
#include "meson-clkc-utils.h"
#include <dt-bindings/clock/amlogic,s4-pll-clkc.h>
+#define ANACTRL_SYSPLL_CTRL0 0x000
#define ANACTRL_FIXPLL_CTRL0 0x040
#define ANACTRL_FIXPLL_CTRL1 0x044
#define ANACTRL_FIXPLL_CTRL3 0x04c
@@ -758,6 +762,321 @@ static struct clk_regmap s4_mpll3 = {
},
};
+/*
+ * CPU clock.
+ *
+ * On S4 the CPU clock control register is only accessible from the secure
+ * world: it is not mapped in the CLKCTRL block visible to Linux, and every
+ * write has to go through BL31. The firmware exposes two SMC services, one
+ * for sys_pll and one for the CPU clock tree, which take pre-shifted
+ * mask/value pairs and behave like a secure regmap_update_bits().
+ *
+ * The hardware tree itself is the well known G12A one (a "dynamic" mux
+ * chain fed by xtal/fclk_div2/fclk_div3 plus a final mux selecting either
+ * that chain or sys_pll). Because the individual muxes cannot be read or
+ * written independently without firmware help, and because the ordering of
+ * the park/reprogram/restore sequence is what keeps the core alive, the
+ * whole tree is modelled here as a single clock with its own ops instead of
+ * a set of clk_regmap muxes and dividers.
+ */
+#define SECURE_PLL_CLK 0x82000098
+#define SECURE_CPU_CLK 0x82000099
+
+/* SECURE_PLL_CLK sub-commands */
+#define SECURE_PLL_SET_MN 0
+#define SECURE_PLL_PRE_CHANGE 1
+#define SECURE_PLL_SET_OD 2
+
+/* SECURE_CPU_CLK sub-commands */
+#define SECURE_CPU_SET_MUX 6
+#define SECURE_CPU_GET_CTRL 7
+#define SECURE_CPU_SET_DYN 8
+
+#define SYS_PLL_LOCK BIT(31)
+#define SYS_PLL_M GENMASK(7, 0)
+#define SYS_PLL_N GENMASK(14, 10)
+#define SYS_PLL_OD GENMASK(18, 16)
+
+/* Fields of the secure CPU clock control register */
+#define CPU_CLK_DYN0_PREMUX GENMASK(1, 0)
+#define CPU_CLK_DYN0_POSTMUX BIT(2)
+#define CPU_CLK_DYN0_DIV GENMASK(9, 4)
+#define CPU_CLK_DYN_MUX BIT(10)
+#define CPU_CLK_FINAL_MUX BIT(11)
+#define CPU_CLK_DYN1_PREMUX GENMASK(17, 16)
+#define CPU_CLK_DYN1_POSTMUX BIT(18)
+#define CPU_CLK_DYN1_DIV GENMASK(25, 20)
+
+static unsigned long s4_secure_clk_smc(u32 fid, u32 cmd, u32 a2, u32 a3, u32 a4)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(fid, cmd, a2, a3, a4, 0, 0, 0, &res);
+
+ return res.a0;
+}
+
+/* Settings of the dynamic tree, as used by the vendor firmware */
+struct s4_cpu_dyn_setting {
+ unsigned long rate;
+ u32 premux;
+ u32 postmux;
+ u32 div;
+};
+
+static const struct s4_cpu_dyn_setting s4_cpu_dyn_table[] = {
+ { 24000000, 0, 0, 0 },
+ { 100000000, 1, 1, 9 },
+ { 250000000, 1, 1, 3 },
+ { 333333333, 2, 1, 1 },
+ { 500000000, 1, 1, 1 },
+ { 666666666, 2, 0, 0 },
+ { 1000000000, 1, 0, 0 },
+};
+
+/* The clock the CPU is parked on while sys_pll is being reprogrammed */
+#define S4_CPU_PARK_IDX 6
+
+/*
+ * sys_pll settings: rate = xtal * m / 2^od with n = 1. Only multipliers
+ * keeping the DCO between 3 and 6 GHz are usable, which is why the lower
+ * rates use a bigger OD instead of a smaller multiplier.
+ */
+struct s4_sys_pll_setting {
+ unsigned long rate;
+ u32 m;
+ u32 od;
+};
+
+static const struct s4_sys_pll_setting s4_sys_pll_table[] = {
+ { 1200000000, 200, 2 },
+ { 1404000000, 234, 2 },
+ { 1500000000, 125, 1 },
+ { 1608000000, 134, 1 },
+ { 1704000000, 142, 1 },
+ { 1800000000, 150, 1 },
+};
+
+static unsigned long s4_cpu_clk_dyn_parent_rate(u32 premux)
+{
+ switch (premux) {
+ case 1:
+ return clk_hw_get_rate(&s4_fclk_div2.hw);
+ case 2:
+ return clk_hw_get_rate(&s4_fclk_div3.hw);
+ default:
+ return 0;
+ }
+}
+
+static unsigned long s4_cpu_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ unsigned long parent;
+ u32 ctrl, val, m, n, od;
+ u32 premux, postmux, div;
+
+ ctrl = s4_secure_clk_smc(SECURE_CPU_CLK, SECURE_CPU_GET_CTRL, 0, 0, 0);
+
+ if (ctrl & CPU_CLK_FINAL_MUX) {
+ regmap_read(clk->map, ANACTRL_SYSPLL_CTRL0, &val);
+
+ m = FIELD_GET(SYS_PLL_M, val);
+ n = FIELD_GET(SYS_PLL_N, val);
+ od = FIELD_GET(SYS_PLL_OD, val);
+ if (!n)
+ return 0;
+
+ return (unsigned long)div_u64((u64)prate * m, n) >> od;
+ }
+
+ if (ctrl & CPU_CLK_DYN_MUX) {
+ premux = FIELD_GET(CPU_CLK_DYN1_PREMUX, ctrl);
+ postmux = FIELD_GET(CPU_CLK_DYN1_POSTMUX, ctrl);
+ div = FIELD_GET(CPU_CLK_DYN1_DIV, ctrl);
+ } else {
+ premux = FIELD_GET(CPU_CLK_DYN0_PREMUX, ctrl);
+ postmux = FIELD_GET(CPU_CLK_DYN0_POSTMUX, ctrl);
+ div = FIELD_GET(CPU_CLK_DYN0_DIV, ctrl);
+ }
+
+ parent = premux ? s4_cpu_clk_dyn_parent_rate(premux) : prate;
+
+ return postmux ? parent / (div + 1) : parent;
+}
+
+/*
+ * Pick the supported setting closest to @rate. Nearest rather than floor
+ * matching is deliberate: cpufreq carries frequencies in kHz, so a rate such
+ * as 666666666 Hz comes back as 666666000 Hz and would otherwise drop
+ * to the next lower operating point.
+ */
+static unsigned long s4_cpu_clk_pick(unsigned long rate,
+ const struct s4_cpu_dyn_setting **dyn,
+ const struct s4_sys_pll_setting **pll)
+{
+ unsigned long best = 0, delta = ULONG_MAX;
+ int i;
+
+ *dyn = NULL;
+ *pll = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(s4_cpu_dyn_table); i++) {
+ unsigned long d = abs_diff(s4_cpu_dyn_table[i].rate, rate);
+
+ if (d < delta) {
+ delta = d;
+ best = s4_cpu_dyn_table[i].rate;
+ *dyn = &s4_cpu_dyn_table[i];
+ *pll = NULL;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(s4_sys_pll_table); i++) {
+ unsigned long d = abs_diff(s4_sys_pll_table[i].rate, rate);
+
+ if (d < delta) {
+ delta = d;
+ best = s4_sys_pll_table[i].rate;
+ *dyn = NULL;
+ *pll = &s4_sys_pll_table[i];
+ }
+ }
+
+ return best;
+}
+
+static int s4_cpu_clk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ const struct s4_cpu_dyn_setting *dyn;
+ const struct s4_sys_pll_setting *pll;
+
+ req->rate = s4_cpu_clk_pick(req->rate, &dyn, &pll);
+
+ return 0;
+}
+
+static void s4_cpu_clk_set_dyn(const struct s4_cpu_dyn_setting *dyn)
+{
+ s4_secure_clk_smc(SECURE_CPU_CLK, SECURE_CPU_SET_DYN,
+ dyn->premux, dyn->postmux, dyn->div);
+ s4_secure_clk_smc(SECURE_CPU_CLK, SECURE_CPU_SET_MUX,
+ CPU_CLK_FINAL_MUX, 0, 0);
+}
+
+static int s4_cpu_clk_init(struct clk_hw *hw)
+{
+ int ret;
+
+ ret = clk_regmap_init(hw);
+ if (ret)
+ return ret;
+
+ /*
+ * The firmware hands the cores over running from sys_pll at ~1.5 GHz,
+ * but the CPU supply only comes under Linux control later and a PWM
+ * regulator adopts whatever duty cycle the bootloader left behind,
+ * which can be the minimum of the rail. Running 1.5 GHz at that
+ * voltage is out of spec and hangs some boards every few boots, so
+ * park on the 1 GHz dynamic clock until cpufreq starts managing
+ * frequency and voltage together.
+ */
+ s4_cpu_clk_set_dyn(&s4_cpu_dyn_table[S4_CPU_PARK_IDX]);
+
+ return 0;
+}
+
+static int s4_cpu_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ const struct s4_cpu_dyn_setting *dyn;
+ const struct s4_sys_pll_setting *pll;
+ unsigned int val;
+ int ret;
+
+ s4_cpu_clk_pick(rate, &dyn, &pll);
+
+ if (dyn) {
+ s4_cpu_clk_set_dyn(dyn);
+ return 0;
+ }
+
+ if (!pll)
+ return -EINVAL;
+
+ /* Park the CPU on the dynamic tree before touching sys_pll */
+ s4_cpu_clk_set_dyn(&s4_cpu_dyn_table[S4_CPU_PARK_IDX]);
+
+ s4_secure_clk_smc(SECURE_PLL_CLK, SECURE_PLL_PRE_CHANGE, 0, 0, 0);
+ s4_secure_clk_smc(SECURE_PLL_CLK, SECURE_PLL_SET_MN, pll->m, 1, 0);
+ s4_secure_clk_smc(SECURE_PLL_CLK, SECURE_PLL_SET_OD, SYS_PLL_OD,
+ FIELD_PREP(SYS_PLL_OD, pll->od), 0);
+
+ /*
+ * Wait for the PLL to relock before running the cores off it again.
+ * Switching the final mux onto a PLL that has not locked yet kills
+ * the CPU, so on timeout stay parked on the dynamic tree instead:
+ * the cores keep running and the caller gets an error.
+ *
+ * The lock bit alone is not enough here. SET_MN releases reset and
+ * BL31 waits for lock internally, so by the time SET_OD runs the bit
+ * is already set and stays set while the divider changes underneath a
+ * live PLL - between the two writes the output is the undivided DCO.
+ * Confirm the divider has actually taken and let the output settle,
+ * otherwise the mux can land while the cores are still being clocked
+ * from the DCO.
+ */
+ ret = regmap_read_poll_timeout(clk->map, ANACTRL_SYSPLL_CTRL0, val,
+ FIELD_GET(SYS_PLL_OD, val) == pll->od,
+ 10, 10000);
+ if (ret) {
+ pr_err("cpu_clk: sys_pll OD did not take, staying on %lu Hz\n",
+ s4_cpu_dyn_table[S4_CPU_PARK_IDX].rate);
+ return ret;
+ }
+
+ fsleep(100);
+
+ ret = regmap_read_poll_timeout(clk->map, ANACTRL_SYSPLL_CTRL0, val,
+ val & SYS_PLL_LOCK, 10, 10000);
+ if (ret) {
+ pr_err("cpu_clk: sys_pll did not lock, staying on %lu Hz\n",
+ s4_cpu_dyn_table[S4_CPU_PARK_IDX].rate);
+ return ret;
+ }
+
+ s4_secure_clk_smc(SECURE_CPU_CLK, SECURE_CPU_SET_MUX,
+ CPU_CLK_FINAL_MUX, CPU_CLK_FINAL_MUX, 0);
+
+ return 0;
+}
+
+static const struct clk_ops s4_cpu_clk_ops = {
+ .init = s4_cpu_clk_init,
+ .recalc_rate = s4_cpu_clk_recalc_rate,
+ .determine_rate = s4_cpu_clk_determine_rate,
+ .set_rate = s4_cpu_clk_set_rate,
+};
+
+static struct clk_regmap s4_cpu_clk = {
+ .hw.init = &(struct clk_init_data){
+ .name = "cpu_clk",
+ .ops = &s4_cpu_clk_ops,
+ .parent_data = (const struct clk_parent_data []) {
+ { .fw_name = "xtal", }
+ },
+ .num_parents = 1,
+ /*
+ * The cores run on this clock, it must never be gated and its
+ * rate is changed behind our back by the firmware on resume.
+ */
+ .flags = CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE,
+ },
+};
+
/* Array of all clocks provided by this provider */
static struct clk_hw *s4_pll_hw_clks[] = {
[CLKID_FIXED_PLL_DCO] = &s4_fixed_pll_dco.hw,
@@ -792,6 +1111,7 @@ static struct clk_hw *s4_pll_hw_clks[] = {
[CLKID_MPLL2] = &s4_mpll2.hw,
[CLKID_MPLL3_DIV] = &s4_mpll3_div.hw,
[CLKID_MPLL3] = &s4_mpll3.hw,
+ [CLKID_CPU_CLK] = &s4_cpu_clk.hw,
};
static const struct reg_sequence s4_pll_init_regs[] = {
--
2.56.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 4/4] cpufreq: add an Amlogic speed-bin driver
2026-10-25 19:22 [PATCH 0/4] arm64: amlogic: S4 CPU frequency scaling christian.koever-draxl
` (2 preceding siblings ...)
2026-10-25 19:22 ` [PATCH 3/4] firmware: meson: sm: add the CPU speed bin call christian.koever-draxl
@ 2026-10-25 19:22 ` christian.koever-draxl
2026-10-26 5:30 ` Viresh Kumar
3 siblings, 1 reply; 10+ messages in thread
From: christian.koever-draxl @ 2026-10-25 19:22 UTC (permalink / raw)
To: neil.armstrong, jbrunet, sboyd, bmasney+clk, khilman,
martin.blumenstingl, rafael, viresh.kumar, robh, krzk+dt,
conor+dt
Cc: linux-kernel, linux-amlogic, linux-clk, linux-arm-kernel,
linux-pm, devicetree, Christian Stefan Kövér-Draxl
From: Christian Stefan Kövér-Draxl <ckoever@outlook.de>
Every S4 speed bin runs the same CPU frequencies but needs its own
voltage curve, and the bin can only be read through the secure monitor.
Read the bin from the firmware, select the matching
"opp-microvolt-speed<bin>" property for all CPUs, then register
cpufreq-dt. Bins outside the known range fall back to bin 0, which
has the highest voltages. Like sun50i-cpufreq-nvmem, the driver binds
on the SoC compatible and needs no node of its own, and S4 is added to
the cpufreq-dt-platdev blocklist so cpufreq-dt is only created after
the bin has been applied.
Signed-off-by: Christian Stefan Kövér-Draxl <ckoever@outlook.de>
---
MAINTAINERS | 1 +
drivers/cpufreq/Kconfig.arm | 14 ++
drivers/cpufreq/Makefile | 1 +
drivers/cpufreq/amlogic-cpufreq.c | 184 +++++++++++++++++++++++++++
drivers/cpufreq/cpufreq-dt-platdev.c | 2 +
5 files changed, 202 insertions(+)
create mode 100644 drivers/cpufreq/amlogic-cpufreq.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 0e9fe9c2a39b..c048aed9f7d9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2597,6 +2597,7 @@ F: Documentation/devicetree/bindings/phy/amlogic*
F: arch/arm/boot/dts/amlogic/
F: arch/arm/mach-meson/
F: arch/arm64/boot/dts/amlogic/
+F: drivers/cpufreq/amlogic-cpufreq.c
F: drivers/pmdomain/amlogic/
F: drivers/mmc/host/meson*
F: drivers/phy/amlogic/
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index a441668f9e0c..c0e60e6801c1 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -24,6 +24,20 @@ config ARM_AIROHA_SOC_CPUFREQ
help
This adds the CPUFreq driver for Airoha EN7581 SoCs.
+config ARM_AMLOGIC_CPUFREQ
+ tristate "Amlogic CPUFreq speed-bin support"
+ depends on ARCH_MESON || COMPILE_TEST
+ depends on CPUFREQ_DT
+ depends on MESON_SM
+ help
+ This adds the CPUFreq driver for Amlogic SoCs whose CPU speed bin
+ is only readable through the secure monitor, such as the S4 family.
+ It reads the bin from the firmware, selects the matching voltage
+ curve from the CPU operating points table and then registers
+ cpufreq-dt.
+
+ If unsure, say N.
+
config ARM_APPLE_SOC_CPUFREQ
tristate "Apple Silicon SoC CPUFreq support"
depends on ARCH_APPLE || (COMPILE_TEST && 64BIT)
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 6c7a39b7f8d2..781b92f9d43f 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_X86_AMD_FREQ_SENSITIVITY) += amd_freq_sensitivity.o
##################################################################################
# ARM SoC drivers
obj-$(CONFIG_ARM_AIROHA_SOC_CPUFREQ) += airoha-cpufreq.o
+obj-$(CONFIG_ARM_AMLOGIC_CPUFREQ) += amlogic-cpufreq.o
obj-$(CONFIG_ARM_APPLE_SOC_CPUFREQ) += apple-soc-cpufreq.o
obj-$(CONFIG_ARM_ARMADA_37XX_CPUFREQ) += armada-37xx-cpufreq.o
obj-$(CONFIG_ARM_ARMADA_8K_CPUFREQ) += armada-8k-cpufreq.o
diff --git a/drivers/cpufreq/amlogic-cpufreq.c b/drivers/cpufreq/amlogic-cpufreq.c
new file mode 100644
index 000000000000..60f399d14846
--- /dev/null
+++ b/drivers/cpufreq/amlogic-cpufreq.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Amlogic CPUFreq speed-bin driver
+ *
+ * Amlogic SoCs are binned at manufacturing time and every bin has its own
+ * voltage curve for one and the same set of frequencies. The bin is only
+ * readable through the secure monitor. This driver asks the firmware which
+ * bin the chip is, points the OPP core at the matching
+ * "opp-microvolt-speed<bin>" property, and then hands over to cpufreq-dt.
+ *
+ * Copyright (c) 2026 Christian Stefan Kövér-Draxl <ckoever@outlook.de>
+ */
+
+#include <linux/cpu.h>
+#include <linux/firmware/meson/meson_sm.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+
+/* The firmware reports one of four bins, 0 being the one needing most volts */
+#define AMLOGIC_CPUFREQ_MAX_BIN 3
+
+static struct platform_device *amlogic_cpufreq_pdev;
+
+struct amlogic_cpufreq {
+ struct platform_device *cpufreq_dt;
+ unsigned int nr_configured;
+ int opp_tokens[];
+};
+
+static int amlogic_cpufreq_get_speed_bin(struct device *dev)
+{
+ struct meson_sm_firmware *fw;
+ s32 bin;
+ int ret;
+
+ struct device_node *sm_np __free(device_node) =
+ of_find_compatible_node(NULL, NULL, "amlogic,meson-gxbb-sm");
+ if (!sm_np)
+ return dev_err_probe(dev, -ENODEV, "no secure monitor\n");
+
+ fw = meson_sm_get(sm_np);
+ if (!fw)
+ return -EPROBE_DEFER;
+
+ /* The only argument is the cluster, these SoCs have a single one */
+ ret = meson_sm_call(fw, SM_GET_CPU_SPEED_BIN, &bin, 0, 0, 0, 0, 0);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to read the speed bin\n");
+
+ /*
+ * Treat anything outside the known range as bin 0, whose voltages are
+ * the highest and therefore safe on any chip.
+ */
+ if (bin < 0 || bin > AMLOGIC_CPUFREQ_MAX_BIN) {
+ dev_warn(dev, "unknown speed bin %d, assuming 0\n", bin);
+ bin = 0;
+ }
+
+ return bin;
+}
+
+static void amlogic_cpufreq_clear_config(struct amlogic_cpufreq *priv)
+{
+ unsigned int cpu;
+
+ for (cpu = 0; cpu < priv->nr_configured; cpu++)
+ dev_pm_opp_clear_config(priv->opp_tokens[cpu]);
+}
+
+static int amlogic_cpufreq_probe(struct platform_device *pdev)
+{
+ struct dev_pm_opp_config config = {};
+ struct device *dev = &pdev->dev;
+ struct amlogic_cpufreq *priv;
+ char name[16];
+ unsigned int cpu;
+ int bin, ret;
+
+ bin = amlogic_cpufreq_get_speed_bin(dev);
+ if (bin < 0)
+ return bin;
+
+ priv = devm_kzalloc(dev, struct_size(priv, opp_tokens, nr_cpu_ids),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ snprintf(name, sizeof(name), "speed%d", bin);
+ config.prop_name = name;
+
+ for_each_present_cpu(cpu) {
+ struct device *cpu_dev = get_cpu_device(cpu);
+
+ if (!cpu_dev) {
+ ret = -ENODEV;
+ goto clear_config;
+ }
+
+ ret = dev_pm_opp_set_config(cpu_dev, &config);
+ if (ret < 0)
+ goto clear_config;
+
+ priv->opp_tokens[cpu] = ret;
+ priv->nr_configured = cpu + 1;
+ }
+
+ priv->cpufreq_dt = platform_device_register_simple("cpufreq-dt", -1,
+ NULL, 0);
+ if (IS_ERR(priv->cpufreq_dt)) {
+ ret = PTR_ERR(priv->cpufreq_dt);
+ goto clear_config;
+ }
+
+ platform_set_drvdata(pdev, priv);
+ dev_info(dev, "using the voltages of speed bin %d\n", bin);
+
+ return 0;
+
+clear_config:
+ amlogic_cpufreq_clear_config(priv);
+
+ return ret;
+}
+
+static void amlogic_cpufreq_remove(struct platform_device *pdev)
+{
+ struct amlogic_cpufreq *priv = platform_get_drvdata(pdev);
+
+ platform_device_unregister(priv->cpufreq_dt);
+ amlogic_cpufreq_clear_config(priv);
+}
+
+static struct platform_driver amlogic_cpufreq_driver = {
+ .probe = amlogic_cpufreq_probe,
+ .remove = amlogic_cpufreq_remove,
+ .driver = {
+ .name = "amlogic-cpufreq",
+ },
+};
+
+static const struct of_device_id amlogic_cpufreq_match_list[] = {
+ { .compatible = "amlogic,s4" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, amlogic_cpufreq_match_list);
+
+/*
+ * The secure monitor may not have probed yet, so all the real work happens
+ * in probe, which can be deferred. Init only registers the driver and its
+ * device, on the SoCs the driver knows about.
+ */
+static int __init amlogic_cpufreq_init(void)
+{
+ int ret;
+
+ if (!of_machine_device_match(amlogic_cpufreq_match_list))
+ return -ENODEV;
+
+ ret = platform_driver_register(&amlogic_cpufreq_driver);
+ if (ret)
+ return ret;
+
+ amlogic_cpufreq_pdev = platform_device_register_simple("amlogic-cpufreq",
+ -1, NULL, 0);
+ ret = PTR_ERR_OR_ZERO(amlogic_cpufreq_pdev);
+ if (ret)
+ platform_driver_unregister(&amlogic_cpufreq_driver);
+
+ return ret;
+}
+module_init(amlogic_cpufreq_init);
+
+static void __exit amlogic_cpufreq_exit(void)
+{
+ platform_device_unregister(amlogic_cpufreq_pdev);
+ platform_driver_unregister(&amlogic_cpufreq_driver);
+}
+module_exit(amlogic_cpufreq_exit);
+
+MODULE_DESCRIPTION("Amlogic CPUFreq speed-bin driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c
index bce4c87573a1..f452ddc46b2c 100644
--- a/drivers/cpufreq/cpufreq-dt-platdev.c
+++ b/drivers/cpufreq/cpufreq-dt-platdev.c
@@ -115,6 +115,8 @@ static const struct of_device_id blocklist[] __initconst = {
{ .compatible = "allwinner,sun50i-h618", },
{ .compatible = "allwinner,sun50i-h700", },
+ { .compatible = "amlogic,s4", },
+
{ .compatible = "apple,arm-platform", },
{ .compatible = "arm,vexpress", },
--
2.56.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread