Samsung SoC firmware/clock drivers for v6.19

Extend Samsung ACPM (Alive Clock and Power Manager) firmware drivers:
 1. Add support for passing DVFS (Dynamic Voltage and Frequency) messages
    to configure the clocks in ACPM device.
 
 2. Add Exynos ACPM clock driver, which exposes to Linux several clocks
    handled by the ACPM (firmware).
 -----BEGIN PGP SIGNATURE-----
 
 iQJEBAABCgAuFiEE3dJiKD0RGyM7briowTdm5oaLg9cFAmkR1kMQHGtyemtAa2Vy
 bmVsLm9yZwAKCRDBN2bmhouD1/8REACWm6l/mfn6ji8q6TziaQ/MmSjCEn0xvGH9
 ZmtuBQtFT/KpN2liOckMVX9AUqf3ZjmxaqimbFzO7zBMb/VG/9/8Phofn++jq5dt
 h4MQ1XVZRqNlxPP3SA3BMcYj6lEJTK8VvgsM4KqCuZ3yKF/RTlnpOSLRTYzwvIAV
 se7DGMp6fBd1Z+igBOfWzjVyv0RyzlDeX7EP6S1AXYcexhuCyfG4oKI0P97A8u2i
 B+hKeVrSY2Yuefsi6nJaE7SDjjRLW5kxpE/htzDCzBNzju7KihpbYb4qlzy24L+6
 mQ0cjNcysQHmkb45a4QYY6BiX/JlBa+lrHAQ9626afxUZAfJsZG7sdvUBOiu4icp
 IIGio1S+rLKEHhModoYvpe9oHNHXpAJr80wZAiGIpryZXjZku21wtb6l3oMO89rz
 UkUqhbbqBPOVzI8ucirRFnzOSsKsEcs+NfBGBcvCWyZziKqsuShri3BL15S4fkMW
 AnnObhWcssNAgTy0lWma+fOrocu/DyGI6TsNMy/TDu/TgH0FwncK/3e8GFdfwqTP
 XUQBN/kTNmJi8GE3iG5bGP1SZRkcHw9VuF5BEjvhtSojcR550nU9Dl5xna3uVLQF
 eVI4qbedRzlHj2jGOaB4QDd/1NpBh4ItKELJGD2MDQJXq7VyTeByIStoHxgtX1WU
 uyZ59V5UYA==
 =4F4S
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQJIBAABCAAyFiEE9L57QeeUxqYDyoaDrQKIl8bklSUFAmkWpCYUHHN3Ym95ZEBj
 aHJvbWl1bS5vcmcACgkQrQKIl8bklSUF0xAAzy74TBjuu5ZkLk0ir52rQdyY/D76
 fRm49AyvE6DMDWnN5QKK37ospDJn1D7KMqfXA4FWoY9ljEjeQ2nthUPB9zwxYfuQ
 P9DxJNeC6/28bhu6vENkfbK28sPwxGPznDvuKvy3msi4V8K/CXTL/qaXZHPCStrA
 Bye/i0/h+u3vonQI+ti6D3F7ZV4Tic80OReDLAMyg3RteLEYRAJDmh1QFWjW8LVb
 rGhA2GBnnM3wo/zZEL3phXtHuVy3WS1vjY8v15p7G5etNNLHA2V1C5c8Kl+svwrV
 DD+HmWRmMqlZUS5uUL1sYEOJcxVau1KFQyOUCqpMz5meTJ6EqAfar7z2v0DvDGhQ
 f6Kj6XbesMA0Iy5Bm8ulLeQRoj91rteM+Z9bILZnKSl9PN4dvZ4qqPASGux7+sgQ
 dRGpenaijVE/W9PHaOf0fq5B/04gyg4eUDmIWCWnvdqlrwtj7StBFLLGjkwXBCcy
 gw+q+Ws0ddyMF93GqTbQDlCi7QvLAAjDseDu0ApExX3nRMwPz9y/ZRhro6Yfbo0s
 6w+kyYyaaalThodhC4j4N7m0XAiEhCtBMiZc6QQ4/dlalX6VdRNKXa6gW5jjnHLd
 5tkLopxPeNTBz2NW+cstWIcY+TcHOwt+QUu+EtTzcW84SPWSRc/5Y9+exKyGAYOQ
 GitnlNSG24+cvsw=
 =susi
 -----END PGP SIGNATURE-----

Merge tag 'samsung-drivers-firmware-clk-6.19' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux into clk-samsung

Pull Samsung clk driver updates from Krzysztof Kozlowski:

Extend Samsung ACPM (Alive Clock and Power Manager) firmware drivers:

 - Add support for passing DVFS (Dynamic Voltage and Frequency) messages
   to configure the clocks in ACPM device.
 - Add Exynos ACPM clock driver, which exposes to Linux several clocks
   handled by the ACPM (firmware)

* tag 'samsung-drivers-firmware-clk-6.19' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux:
  firmware: exynos-acpm: add empty method to allow compile test
  MAINTAINERS: add ACPM clock bindings and driver
  clk: samsung: add Exynos ACPM clock driver
  firmware: exynos-acpm: register ACPM clocks pdev
  firmware: exynos-acpm: add DVFS protocol
  dt-bindings: firmware: google,gs101-acpm-ipc: add ACPM clocks
This commit is contained in:
Stephen Boyd 2025-11-13 19:37:27 -08:00
commit 44244194e1
11 changed files with 384 additions and 2 deletions

View File

@ -24,6 +24,15 @@ properties:
compatible:
const: google,gs101-acpm-ipc
"#clock-cells":
const: 1
description:
Clocks that are variable and index based. These clocks don't provide
an entire range of values between the limits but only discrete points
within the range. The firmware also manages the voltage scaling
appropriately with the clock scaling. The argument is the ID of the
clock contained by the firmware messages.
mboxes:
maxItems: 1
@ -45,6 +54,7 @@ properties:
required:
- compatible
- "#clock-cells"
- mboxes
- shmem
@ -56,6 +66,7 @@ examples:
power-management {
compatible = "google,gs101-acpm-ipc";
#clock-cells = <1>;
mboxes = <&ap2apm_mailbox>;
shmem = <&apm_sram>;

View File

@ -10601,7 +10601,7 @@ F: Documentation/devicetree/bindings/soc/google/google,gs101-pmu-intr-gen.yaml
F: arch/arm64/boot/dts/exynos/google/
F: drivers/clk/samsung/clk-gs101.c
F: drivers/phy/samsung/phy-gs101-ufs.c
F: include/dt-bindings/clock/google,gs101.h
F: include/dt-bindings/clock/google,gs101*
K: [gG]oogle.?[tT]ensor
GPD FAN DRIVER
@ -22761,6 +22761,7 @@ L: linux-kernel@vger.kernel.org
L: linux-samsung-soc@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml
F: drivers/clk/samsung/clk-acpm.c
F: drivers/firmware/samsung/exynos-acpm*
F: include/linux/firmware/samsung/exynos-acpm-protocol.h

View File

@ -95,6 +95,16 @@ config EXYNOS_CLKOUT
status of the certains clocks from SoC, but it could also be tied to
other devices as an input clock.
config EXYNOS_ACPM_CLK
tristate "Clock driver controlled via ACPM interface"
depends on EXYNOS_ACPM_PROTOCOL || (COMPILE_TEST && !EXYNOS_ACPM_PROTOCOL)
help
This driver provides support for clocks that are controlled by
firmware that implements the ACPM interface.
This driver uses the ACPM interface to interact with the firmware
providing all the clock controlls.
config TESLA_FSD_COMMON_CLK
bool "Tesla FSD clock controller support" if COMPILE_TEST
depends on COMMON_CLK_SAMSUNG

View File

@ -28,6 +28,7 @@ obj-$(CONFIG_EXYNOS_ARM64_COMMON_CLK) += clk-exynos990.o
obj-$(CONFIG_EXYNOS_ARM64_COMMON_CLK) += clk-exynosautov9.o
obj-$(CONFIG_EXYNOS_ARM64_COMMON_CLK) += clk-exynosautov920.o
obj-$(CONFIG_EXYNOS_ARM64_COMMON_CLK) += clk-gs101.o
obj-$(CONFIG_EXYNOS_ACPM_CLK) += clk-acpm.o
obj-$(CONFIG_S3C64XX_COMMON_CLK) += clk-s3c64xx.o
obj-$(CONFIG_S5PV210_COMMON_CLK) += clk-s5pv210.o clk-s5pv210-audss.o
obj-$(CONFIG_TESLA_FSD_COMMON_CLK) += clk-fsd.o

View File

@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Samsung Exynos ACPM protocol based clock driver.
*
* Copyright 2025 Linaro Ltd.
*/
#include <linux/array_size.h>
#include <linux/clk-provider.h>
#include <linux/container_of.h>
#include <linux/device/devres.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/firmware/samsung/exynos-acpm-protocol.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
struct acpm_clk {
u32 id;
struct clk_hw hw;
unsigned int mbox_chan_id;
const struct acpm_handle *handle;
};
struct acpm_clk_variant {
const char *name;
};
struct acpm_clk_driver_data {
const struct acpm_clk_variant *clks;
unsigned int nr_clks;
unsigned int mbox_chan_id;
};
#define to_acpm_clk(clk) container_of(clk, struct acpm_clk, hw)
#define ACPM_CLK(cname) \
{ \
.name = cname, \
}
static const struct acpm_clk_variant gs101_acpm_clks[] = {
ACPM_CLK("mif"),
ACPM_CLK("int"),
ACPM_CLK("cpucl0"),
ACPM_CLK("cpucl1"),
ACPM_CLK("cpucl2"),
ACPM_CLK("g3d"),
ACPM_CLK("g3dl2"),
ACPM_CLK("tpu"),
ACPM_CLK("intcam"),
ACPM_CLK("tnr"),
ACPM_CLK("cam"),
ACPM_CLK("mfc"),
ACPM_CLK("disp"),
ACPM_CLK("bo"),
};
static const struct acpm_clk_driver_data acpm_clk_gs101 = {
.clks = gs101_acpm_clks,
.nr_clks = ARRAY_SIZE(gs101_acpm_clks),
.mbox_chan_id = 0,
};
static unsigned long acpm_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct acpm_clk *clk = to_acpm_clk(hw);
return clk->handle->ops.dvfs_ops.get_rate(clk->handle,
clk->mbox_chan_id, clk->id);
}
static int acpm_clk_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
/*
* We can't figure out what rate it will be, so just return the
* rate back to the caller. acpm_clk_recalc_rate() will be called
* after the rate is set and we'll know what rate the clock is
* running at then.
*/
return 0;
}
static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct acpm_clk *clk = to_acpm_clk(hw);
return clk->handle->ops.dvfs_ops.set_rate(clk->handle,
clk->mbox_chan_id, clk->id, rate);
}
static const struct clk_ops acpm_clk_ops = {
.recalc_rate = acpm_clk_recalc_rate,
.determine_rate = acpm_clk_determine_rate,
.set_rate = acpm_clk_set_rate,
};
static int acpm_clk_register(struct device *dev, struct acpm_clk *aclk,
const char *name)
{
struct clk_init_data init = {};
init.name = name;
init.ops = &acpm_clk_ops;
aclk->hw.init = &init;
return devm_clk_hw_register(dev, &aclk->hw);
}
static int acpm_clk_probe(struct platform_device *pdev)
{
const struct acpm_handle *acpm_handle;
struct clk_hw_onecell_data *clk_data;
struct clk_hw **hws;
struct device *dev = &pdev->dev;
struct acpm_clk *aclks;
unsigned int mbox_chan_id;
int i, err, count;
acpm_handle = devm_acpm_get_by_node(dev, dev->parent->of_node);
if (IS_ERR(acpm_handle))
return dev_err_probe(dev, PTR_ERR(acpm_handle),
"Failed to get acpm handle\n");
count = acpm_clk_gs101.nr_clks;
mbox_chan_id = acpm_clk_gs101.mbox_chan_id;
clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, count),
GFP_KERNEL);
if (!clk_data)
return -ENOMEM;
clk_data->num = count;
hws = clk_data->hws;
aclks = devm_kcalloc(dev, count, sizeof(*aclks), GFP_KERNEL);
if (!aclks)
return -ENOMEM;
for (i = 0; i < count; i++) {
struct acpm_clk *aclk = &aclks[i];
/*
* The code assumes the clock IDs start from zero,
* are sequential and do not have gaps.
*/
aclk->id = i;
aclk->handle = acpm_handle;
aclk->mbox_chan_id = mbox_chan_id;
hws[i] = &aclk->hw;
err = acpm_clk_register(dev, aclk,
acpm_clk_gs101.clks[i].name);
if (err)
return dev_err_probe(dev, err,
"Failed to register clock\n");
}
return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
clk_data);
}
static const struct platform_device_id acpm_clk_id[] = {
{ "gs101-acpm-clk" },
{}
};
MODULE_DEVICE_TABLE(platform, acpm_clk_id);
static struct platform_driver acpm_clk_driver = {
.driver = {
.name = "acpm-clocks",
},
.probe = acpm_clk_probe,
.id_table = acpm_clk_id,
};
module_platform_driver(acpm_clk_driver);
MODULE_AUTHOR("Tudor Ambarus <tudor.ambarus@linaro.org>");
MODULE_DESCRIPTION("Samsung Exynos ACPM clock driver");
MODULE_LICENSE("GPL");

View File

@ -1,4 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
acpm-protocol-objs := exynos-acpm.o exynos-acpm-pmic.o
acpm-protocol-objs := exynos-acpm.o
acpm-protocol-objs += exynos-acpm-pmic.o
acpm-protocol-objs += exynos-acpm-dvfs.o
obj-$(CONFIG_EXYNOS_ACPM_PROTOCOL) += acpm-protocol.o

View File

@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2020 Samsung Electronics Co., Ltd.
* Copyright 2020 Google LLC.
* Copyright 2025 Linaro Ltd.
*/
#include <linux/bitfield.h>
#include <linux/firmware/samsung/exynos-acpm-protocol.h>
#include <linux/ktime.h>
#include <linux/types.h>
#include <linux/units.h>
#include "exynos-acpm.h"
#include "exynos-acpm-dvfs.h"
#define ACPM_DVFS_ID GENMASK(11, 0)
#define ACPM_DVFS_REQ_TYPE GENMASK(15, 0)
#define ACPM_DVFS_FREQ_REQ 0
#define ACPM_DVFS_FREQ_GET 1
static void acpm_dvfs_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
unsigned int acpm_chan_id, bool response)
{
xfer->acpm_chan_id = acpm_chan_id;
xfer->txd = cmd;
xfer->txlen = cmdlen;
if (response) {
xfer->rxd = cmd;
xfer->rxlen = cmdlen;
}
}
static void acpm_dvfs_init_set_rate_cmd(u32 cmd[4], unsigned int clk_id,
unsigned long rate)
{
cmd[0] = FIELD_PREP(ACPM_DVFS_ID, clk_id);
cmd[1] = rate / HZ_PER_KHZ;
cmd[2] = FIELD_PREP(ACPM_DVFS_REQ_TYPE, ACPM_DVFS_FREQ_REQ);
cmd[3] = ktime_to_ms(ktime_get());
}
int acpm_dvfs_set_rate(const struct acpm_handle *handle,
unsigned int acpm_chan_id, unsigned int clk_id,
unsigned long rate)
{
struct acpm_xfer xfer = {0};
u32 cmd[4];
acpm_dvfs_init_set_rate_cmd(cmd, clk_id, rate);
acpm_dvfs_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id, false);
return acpm_do_xfer(handle, &xfer);
}
static void acpm_dvfs_init_get_rate_cmd(u32 cmd[4], unsigned int clk_id)
{
cmd[0] = FIELD_PREP(ACPM_DVFS_ID, clk_id);
cmd[2] = FIELD_PREP(ACPM_DVFS_REQ_TYPE, ACPM_DVFS_FREQ_GET);
cmd[3] = ktime_to_ms(ktime_get());
}
unsigned long acpm_dvfs_get_rate(const struct acpm_handle *handle,
unsigned int acpm_chan_id, unsigned int clk_id)
{
struct acpm_xfer xfer;
unsigned int cmd[4] = {0};
int ret;
acpm_dvfs_init_get_rate_cmd(cmd, clk_id);
acpm_dvfs_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
return 0;
return xfer.rxd[1] * HZ_PER_KHZ;
}

View File

@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2020 Samsung Electronics Co., Ltd.
* Copyright 2020 Google LLC.
* Copyright 2025 Linaro Ltd.
*/
#ifndef __EXYNOS_ACPM_DVFS_H__
#define __EXYNOS_ACPM_DVFS_H__
#include <linux/types.h>
struct acpm_handle;
int acpm_dvfs_set_rate(const struct acpm_handle *handle,
unsigned int acpm_chan_id, unsigned int id,
unsigned long rate);
unsigned long acpm_dvfs_get_rate(const struct acpm_handle *handle,
unsigned int acpm_chan_id,
unsigned int clk_id);
#endif /* __EXYNOS_ACPM_DVFS_H__ */

View File

@ -29,6 +29,7 @@
#include <linux/types.h>
#include "exynos-acpm.h"
#include "exynos-acpm-dvfs.h"
#include "exynos-acpm-pmic.h"
#define ACPM_PROTOCOL_SEQNUM GENMASK(21, 16)
@ -176,9 +177,11 @@ struct acpm_info {
/**
* struct acpm_match_data - of_device_id data.
* @initdata_base: offset in SRAM where the channels configuration resides.
* @acpm_clk_dev_name: base name for the ACPM clocks device that we're registering.
*/
struct acpm_match_data {
loff_t initdata_base;
const char *acpm_clk_dev_name;
};
#define client_to_acpm_chan(c) container_of(c, struct acpm_chan, cl)
@ -590,8 +593,12 @@ static int acpm_channels_init(struct acpm_info *acpm)
*/
static void acpm_setup_ops(struct acpm_info *acpm)
{
struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs_ops;
struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic_ops;
dvfs_ops->set_rate = acpm_dvfs_set_rate;
dvfs_ops->get_rate = acpm_dvfs_get_rate;
pmic_ops->read_reg = acpm_pmic_read_reg;
pmic_ops->bulk_read = acpm_pmic_bulk_read;
pmic_ops->write_reg = acpm_pmic_write_reg;
@ -599,9 +606,15 @@ static void acpm_setup_ops(struct acpm_info *acpm)
pmic_ops->update_reg = acpm_pmic_update_reg;
}
static void acpm_clk_pdev_unregister(void *data)
{
platform_device_unregister(data);
}
static int acpm_probe(struct platform_device *pdev)
{
const struct acpm_match_data *match_data;
struct platform_device *acpm_clk_pdev;
struct device *dev = &pdev->dev;
struct device_node *shmem;
struct acpm_info *acpm;
@ -642,6 +655,18 @@ static int acpm_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, acpm);
acpm_clk_pdev = platform_device_register_data(dev,
match_data->acpm_clk_dev_name,
PLATFORM_DEVID_NONE, NULL, 0);
if (IS_ERR(acpm_clk_pdev))
return dev_err_probe(dev, PTR_ERR(acpm_clk_pdev),
"Failed to register ACPM clocks device.\n");
ret = devm_add_action_or_reset(dev, acpm_clk_pdev_unregister,
acpm_clk_pdev);
if (ret)
return dev_err_probe(dev, ret, "Failed to add devm action.\n");
return devm_of_platform_populate(dev);
}
@ -741,6 +766,7 @@ EXPORT_SYMBOL_GPL(devm_acpm_get_by_node);
static const struct acpm_match_data acpm_gs101 = {
.initdata_base = ACPM_GS101_INITDATA_BASE,
.acpm_clk_dev_name = "gs101-acpm-clk",
};
static const struct of_device_id acpm_match[] = {

View File

@ -0,0 +1,26 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
/*
* Copyright 2025 Linaro Ltd.
*
* Device Tree binding constants for Google gs101 ACPM clock controller.
*/
#ifndef _DT_BINDINGS_CLOCK_GOOGLE_GS101_ACPM_H
#define _DT_BINDINGS_CLOCK_GOOGLE_GS101_ACPM_H
#define GS101_CLK_ACPM_DVFS_MIF 0
#define GS101_CLK_ACPM_DVFS_INT 1
#define GS101_CLK_ACPM_DVFS_CPUCL0 2
#define GS101_CLK_ACPM_DVFS_CPUCL1 3
#define GS101_CLK_ACPM_DVFS_CPUCL2 4
#define GS101_CLK_ACPM_DVFS_G3D 5
#define GS101_CLK_ACPM_DVFS_G3DL2 6
#define GS101_CLK_ACPM_DVFS_TPU 7
#define GS101_CLK_ACPM_DVFS_INTCAM 8
#define GS101_CLK_ACPM_DVFS_TNR 9
#define GS101_CLK_ACPM_DVFS_CAM 10
#define GS101_CLK_ACPM_DVFS_MFC 11
#define GS101_CLK_ACPM_DVFS_DISP 12
#define GS101_CLK_ACPM_DVFS_BO 13
#endif /* _DT_BINDINGS_CLOCK_GOOGLE_GS101_ACPM_H */

View File

@ -13,6 +13,15 @@
struct acpm_handle;
struct device_node;
struct acpm_dvfs_ops {
int (*set_rate)(const struct acpm_handle *handle,
unsigned int acpm_chan_id, unsigned int clk_id,
unsigned long rate);
unsigned long (*get_rate)(const struct acpm_handle *handle,
unsigned int acpm_chan_id,
unsigned int clk_id);
};
struct acpm_pmic_ops {
int (*read_reg)(const struct acpm_handle *handle,
unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
@ -32,6 +41,7 @@ struct acpm_pmic_ops {
};
struct acpm_ops {
struct acpm_dvfs_ops dvfs_ops;
struct acpm_pmic_ops pmic_ops;
};
@ -45,7 +55,16 @@ struct acpm_handle {
struct device;
#if IS_ENABLED(CONFIG_EXYNOS_ACPM_PROTOCOL)
const struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
struct device_node *np);
#else
static inline const struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
struct device_node *np)
{
return NULL;
}
#endif
#endif /* __EXYNOS_ACPM_PROTOCOL_H */