Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

clk: samsung: add Exynos ACPM clock driver

Add the Exynos ACPM clock driver. It provides support for clocks that
are controlled by firmware that implements the ACPM interface.

Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Reviewed-by: Peter Griffin <peter.griffin@linaro.org>
Tested-by: Peter Griffin <peter.griffin@linaro.org> # on gs101-oriole
Link: https://patch.msgid.link/20251010-acpm-clk-v6-4-321ee8826fd4@linaro.org
Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>

authored by

Tudor Ambarus and committed by
Krzysztof Kozlowski
40498a74 bad0d126

+196
+10
drivers/clk/samsung/Kconfig
··· 95 95 status of the certains clocks from SoC, but it could also be tied to 96 96 other devices as an input clock. 97 97 98 + config EXYNOS_ACPM_CLK 99 + tristate "Clock driver controlled via ACPM interface" 100 + depends on EXYNOS_ACPM_PROTOCOL || (COMPILE_TEST && !EXYNOS_ACPM_PROTOCOL) 101 + help 102 + This driver provides support for clocks that are controlled by 103 + firmware that implements the ACPM interface. 104 + 105 + This driver uses the ACPM interface to interact with the firmware 106 + providing all the clock controlls. 107 + 98 108 config TESLA_FSD_COMMON_CLK 99 109 bool "Tesla FSD clock controller support" if COMPILE_TEST 100 110 depends on COMMON_CLK_SAMSUNG
+1
drivers/clk/samsung/Makefile
··· 28 28 obj-$(CONFIG_EXYNOS_ARM64_COMMON_CLK) += clk-exynosautov9.o 29 29 obj-$(CONFIG_EXYNOS_ARM64_COMMON_CLK) += clk-exynosautov920.o 30 30 obj-$(CONFIG_EXYNOS_ARM64_COMMON_CLK) += clk-gs101.o 31 + obj-$(CONFIG_EXYNOS_ACPM_CLK) += clk-acpm.o 31 32 obj-$(CONFIG_S3C64XX_COMMON_CLK) += clk-s3c64xx.o 32 33 obj-$(CONFIG_S5PV210_COMMON_CLK) += clk-s5pv210.o clk-s5pv210-audss.o 33 34 obj-$(CONFIG_TESLA_FSD_COMMON_CLK) += clk-fsd.o
+185
drivers/clk/samsung/clk-acpm.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Samsung Exynos ACPM protocol based clock driver. 4 + * 5 + * Copyright 2025 Linaro Ltd. 6 + */ 7 + 8 + #include <linux/array_size.h> 9 + #include <linux/clk-provider.h> 10 + #include <linux/container_of.h> 11 + #include <linux/device/devres.h> 12 + #include <linux/device.h> 13 + #include <linux/err.h> 14 + #include <linux/firmware/samsung/exynos-acpm-protocol.h> 15 + #include <linux/module.h> 16 + #include <linux/platform_device.h> 17 + #include <linux/types.h> 18 + 19 + struct acpm_clk { 20 + u32 id; 21 + struct clk_hw hw; 22 + unsigned int mbox_chan_id; 23 + const struct acpm_handle *handle; 24 + }; 25 + 26 + struct acpm_clk_variant { 27 + const char *name; 28 + }; 29 + 30 + struct acpm_clk_driver_data { 31 + const struct acpm_clk_variant *clks; 32 + unsigned int nr_clks; 33 + unsigned int mbox_chan_id; 34 + }; 35 + 36 + #define to_acpm_clk(clk) container_of(clk, struct acpm_clk, hw) 37 + 38 + #define ACPM_CLK(cname) \ 39 + { \ 40 + .name = cname, \ 41 + } 42 + 43 + static const struct acpm_clk_variant gs101_acpm_clks[] = { 44 + ACPM_CLK("mif"), 45 + ACPM_CLK("int"), 46 + ACPM_CLK("cpucl0"), 47 + ACPM_CLK("cpucl1"), 48 + ACPM_CLK("cpucl2"), 49 + ACPM_CLK("g3d"), 50 + ACPM_CLK("g3dl2"), 51 + ACPM_CLK("tpu"), 52 + ACPM_CLK("intcam"), 53 + ACPM_CLK("tnr"), 54 + ACPM_CLK("cam"), 55 + ACPM_CLK("mfc"), 56 + ACPM_CLK("disp"), 57 + ACPM_CLK("bo"), 58 + }; 59 + 60 + static const struct acpm_clk_driver_data acpm_clk_gs101 = { 61 + .clks = gs101_acpm_clks, 62 + .nr_clks = ARRAY_SIZE(gs101_acpm_clks), 63 + .mbox_chan_id = 0, 64 + }; 65 + 66 + static unsigned long acpm_clk_recalc_rate(struct clk_hw *hw, 67 + unsigned long parent_rate) 68 + { 69 + struct acpm_clk *clk = to_acpm_clk(hw); 70 + 71 + return clk->handle->ops.dvfs_ops.get_rate(clk->handle, 72 + clk->mbox_chan_id, clk->id); 73 + } 74 + 75 + static int acpm_clk_determine_rate(struct clk_hw *hw, 76 + struct clk_rate_request *req) 77 + { 78 + /* 79 + * We can't figure out what rate it will be, so just return the 80 + * rate back to the caller. acpm_clk_recalc_rate() will be called 81 + * after the rate is set and we'll know what rate the clock is 82 + * running at then. 83 + */ 84 + return 0; 85 + } 86 + 87 + static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate, 88 + unsigned long parent_rate) 89 + { 90 + struct acpm_clk *clk = to_acpm_clk(hw); 91 + 92 + return clk->handle->ops.dvfs_ops.set_rate(clk->handle, 93 + clk->mbox_chan_id, clk->id, rate); 94 + } 95 + 96 + static const struct clk_ops acpm_clk_ops = { 97 + .recalc_rate = acpm_clk_recalc_rate, 98 + .determine_rate = acpm_clk_determine_rate, 99 + .set_rate = acpm_clk_set_rate, 100 + }; 101 + 102 + static int acpm_clk_register(struct device *dev, struct acpm_clk *aclk, 103 + const char *name) 104 + { 105 + struct clk_init_data init = {}; 106 + 107 + init.name = name; 108 + init.ops = &acpm_clk_ops; 109 + aclk->hw.init = &init; 110 + 111 + return devm_clk_hw_register(dev, &aclk->hw); 112 + } 113 + 114 + static int acpm_clk_probe(struct platform_device *pdev) 115 + { 116 + const struct acpm_handle *acpm_handle; 117 + struct clk_hw_onecell_data *clk_data; 118 + struct clk_hw **hws; 119 + struct device *dev = &pdev->dev; 120 + struct acpm_clk *aclks; 121 + unsigned int mbox_chan_id; 122 + int i, err, count; 123 + 124 + acpm_handle = devm_acpm_get_by_node(dev, dev->parent->of_node); 125 + if (IS_ERR(acpm_handle)) 126 + return dev_err_probe(dev, PTR_ERR(acpm_handle), 127 + "Failed to get acpm handle\n"); 128 + 129 + count = acpm_clk_gs101.nr_clks; 130 + mbox_chan_id = acpm_clk_gs101.mbox_chan_id; 131 + 132 + clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, count), 133 + GFP_KERNEL); 134 + if (!clk_data) 135 + return -ENOMEM; 136 + 137 + clk_data->num = count; 138 + hws = clk_data->hws; 139 + 140 + aclks = devm_kcalloc(dev, count, sizeof(*aclks), GFP_KERNEL); 141 + if (!aclks) 142 + return -ENOMEM; 143 + 144 + for (i = 0; i < count; i++) { 145 + struct acpm_clk *aclk = &aclks[i]; 146 + 147 + /* 148 + * The code assumes the clock IDs start from zero, 149 + * are sequential and do not have gaps. 150 + */ 151 + aclk->id = i; 152 + aclk->handle = acpm_handle; 153 + aclk->mbox_chan_id = mbox_chan_id; 154 + 155 + hws[i] = &aclk->hw; 156 + 157 + err = acpm_clk_register(dev, aclk, 158 + acpm_clk_gs101.clks[i].name); 159 + if (err) 160 + return dev_err_probe(dev, err, 161 + "Failed to register clock\n"); 162 + } 163 + 164 + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, 165 + clk_data); 166 + } 167 + 168 + static const struct platform_device_id acpm_clk_id[] = { 169 + { "gs101-acpm-clk" }, 170 + {} 171 + }; 172 + MODULE_DEVICE_TABLE(platform, acpm_clk_id); 173 + 174 + static struct platform_driver acpm_clk_driver = { 175 + .driver = { 176 + .name = "acpm-clocks", 177 + }, 178 + .probe = acpm_clk_probe, 179 + .id_table = acpm_clk_id, 180 + }; 181 + module_platform_driver(acpm_clk_driver); 182 + 183 + MODULE_AUTHOR("Tudor Ambarus <tudor.ambarus@linaro.org>"); 184 + MODULE_DESCRIPTION("Samsung Exynos ACPM clock driver"); 185 + MODULE_LICENSE("GPL");