Import the Linux v5.13 state of the SCMI clock protocol driver. Signed-off-by: Ahmad Fatoum <a.fatoum@xxxxxxxxxxxxxx> --- drivers/clk/Kconfig | 11 + drivers/clk/Makefile | 1 + drivers/clk/clk-scmi.c | 192 +++++++++++++++ drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/clock.c | 374 +++++++++++++++++++++++++++++ drivers/firmware/arm_scmi/common.h | 1 + drivers/firmware/arm_scmi/driver.c | 1 + 7 files changed, 581 insertions(+), 1 deletion(-) create mode 100644 drivers/clk/clk-scmi.c create mode 100644 drivers/firmware/arm_scmi/clock.c diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index e01ffe10f249..5b5acf4e0656 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -20,10 +20,21 @@ config CLK_SOCFPGA select COMMON_CLK_OF_PROVIDER default y if ARCH_SOCFPGA && OFDEVICE + config COMMON_CLK_STM32F bool "STM32F4 and STM32F7 clock driver" if COMPILE_TEST depends on COMMON_CLK && ARCH_STM32 help Support for stm32f4 and stm32f7 SoC families clocks +config COMMON_CLK_SCMI + tristate "Clock driver controlled via SCMI interface" + depends on ARM_SCMI_PROTOCOL || COMPILE_TEST + help + This driver provides support for clocks that are controlled + by firmware that implements the SCMI interface. + + This driver uses SCMI Message Protocol to interact with the + firmware providing all the clock controls. + source "drivers/clk/sifive/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index c60b38ff6002..ee503c1edb5f 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_CLK_SIFIVE) += sifive/ obj-$(CONFIG_SOC_STARFIVE) += starfive/ obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o obj-$(CONFIG_MACH_RPI_COMMON) += clk-rpi.o +obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c new file mode 100644 index 000000000000..9170dba393c4 --- /dev/null +++ b/drivers/clk/clk-scmi.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Power Interface (SCMI) Protocol based clock driver + * + * Copyright (C) 2018-2021 ARM Ltd. + */ + +#include <common.h> +#include <linux/clk.h> +#include <driver.h> +#include <linux/err.h> +#include <of.h> +#include <linux/scmi_protocol.h> +#include <linux/overflow.h> +#include <linux/math64.h> + +static const struct scmi_clk_proto_ops *scmi_proto_clk_ops; + +struct scmi_clk { + u32 id; + struct clk_hw hw; + const struct scmi_clock_info *info; + const struct scmi_protocol_handle *ph; +}; + +#define to_scmi_clk(clk) container_of(clk, struct scmi_clk, hw) + +static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + int ret; + u64 rate; + struct scmi_clk *clk = to_scmi_clk(hw); + + ret = scmi_proto_clk_ops->rate_get(clk->ph, clk->id, &rate); + if (ret) + return 0; + return rate; +} + +static long scmi_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + u64 fmin, fmax, ftmp; + struct scmi_clk *clk = to_scmi_clk(hw); + + /* + * We can't figure out what rate it will be, so just return the + * rate back to the caller. scmi_clk_recalc_rate() will be called + * after the rate is set and we'll know what rate the clock is + * running at then. + */ + if (clk->info->rate_discrete) + return rate; + + fmin = clk->info->range.min_rate; + fmax = clk->info->range.max_rate; + if (rate <= fmin) + return fmin; + else if (rate >= fmax) + return fmax; + + ftmp = rate - fmin; + ftmp += clk->info->range.step_size - 1; /* to round up */ + do_div(ftmp, clk->info->range.step_size); + + return ftmp * clk->info->range.step_size + fmin; +} + +static int scmi_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct scmi_clk *clk = to_scmi_clk(hw); + + return scmi_proto_clk_ops->rate_set(clk->ph, clk->id, rate); +} + +static int scmi_clk_enable(struct clk_hw *hw) +{ + struct scmi_clk *clk = to_scmi_clk(hw); + + return scmi_proto_clk_ops->enable(clk->ph, clk->id); +} + +static void scmi_clk_disable(struct clk_hw *hw) +{ + struct scmi_clk *clk = to_scmi_clk(hw); + + scmi_proto_clk_ops->disable(clk->ph, clk->id); +} + +static const struct clk_ops scmi_clk_ops = { + .recalc_rate = scmi_clk_recalc_rate, + .round_rate = scmi_clk_round_rate, + .set_rate = scmi_clk_set_rate, + /* + * Unlike Linux, we can provide enable/disable callback as everything + * runs in atomic context. + */ + .enable = scmi_clk_enable, + .disable = scmi_clk_disable, +}; + +static int scmi_clk_ops_init(struct device_d *dev, struct scmi_clk *sclk) +{ + struct clk_init_data init = { + .flags = CLK_GET_RATE_NOCACHE, + .num_parents = 0, + .ops = &scmi_clk_ops, + .name = sclk->info->name, + }; + + sclk->hw.init = &init; + return clk_hw_register(dev, &sclk->hw); +} + +static int scmi_clocks_probe(struct scmi_device *sdev) +{ + int idx, count, err; + struct clk **clks; + struct clk_onecell_data *clk_data; + struct device_d *dev = &sdev->dev; + struct device_node *np = dev->device_node; + const struct scmi_handle *handle = sdev->handle; + struct scmi_protocol_handle *ph; + + if (!handle) + return -ENODEV; + + scmi_proto_clk_ops = + handle->protocol_get(sdev, SCMI_PROTOCOL_CLOCK, &ph); + if (IS_ERR(scmi_proto_clk_ops)) + return PTR_ERR(scmi_proto_clk_ops); + + count = scmi_proto_clk_ops->count_get(ph); + if (count < 0) { + dev_err(dev, "%pOFn: invalid clock output count\n", np); + return -EINVAL; + } + + clk_data = kzalloc(sizeof (*clk_data), GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clk_num = count; + clks = clk_data->clks = calloc(clk_data->clk_num, sizeof(struct clk *)); + + for (idx = 0; idx < count; idx++) { + struct scmi_clk *sclk; + + sclk = kzalloc(sizeof(*sclk), GFP_KERNEL); + if (!sclk) + return -ENOMEM; + + sclk->info = scmi_proto_clk_ops->info_get(ph, idx); + if (!sclk->info) { + dev_dbg(dev, "invalid clock info for idx %d\n", idx); + continue; + } + + sclk->id = idx; + sclk->ph = ph; + + err = scmi_clk_ops_init(dev, sclk); + if (err) { + dev_err(dev, "failed to register clock %d\n", idx); + kfree(sclk); + clks[idx] = NULL; + } else { + dev_dbg(dev, "Registered clock:%s\n", sclk->info->name); + clks[idx] = &sclk->hw.clk; + } + } + + return of_clk_add_provider(dev->device_node, of_clk_src_onecell_get, clk_data); +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_CLOCK, "clocks" }, + { }, +}; + +static struct scmi_driver scmi_clocks_driver = { + .name = "scmi-clocks", + .probe = scmi_clocks_probe, + .id_table = scmi_id_table, +}; +core_scmi_driver(scmi_clocks_driver); + +MODULE_AUTHOR("Sudeep Holla <sudeep.holla@xxxxxxx>"); +MODULE_DESCRIPTION("ARM SCMI clock driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 510c0372a096..968a7eb7771e 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -3,7 +3,7 @@ scmi-bus-y = bus.o scmi-driver-y = driver.o scmi-transport-y = shmem.o scmi-transport-$(CONFIG_ARM_SMCCC) += smc.o -scmi-protocols-y = base.o reset.o +scmi-protocols-y = base.o reset.o clock.o scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \ $(scmi-transport-y) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c new file mode 100644 index 000000000000..8f9017206c1c --- /dev/null +++ b/drivers/firmware/arm_scmi/clock.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Clock Protocol + * + * Copyright (C) 2018-2021 ARM Ltd. + */ + +#include <common.h> +#include <qsort.h> + +#include "common.h" + +enum scmi_clock_protocol_cmd { + CLOCK_ATTRIBUTES = 0x3, + CLOCK_DESCRIBE_RATES = 0x4, + CLOCK_RATE_SET = 0x5, + CLOCK_RATE_GET = 0x6, + CLOCK_CONFIG_SET = 0x7, +}; + +struct scmi_msg_resp_clock_protocol_attributes { + __le16 num_clocks; + u8 max_async_req; + u8 reserved; +}; + +struct scmi_msg_resp_clock_attributes { + __le32 attributes; +#define CLOCK_ENABLE BIT(0) + u8 name[SCMI_MAX_STR_SIZE]; +}; + +struct scmi_clock_set_config { + __le32 id; + __le32 attributes; +}; + +struct scmi_msg_clock_describe_rates { + __le32 id; + __le32 rate_index; +}; + +struct scmi_msg_resp_clock_describe_rates { + __le32 num_rates_flags; +#define NUM_RETURNED(x) ((x) & 0xfff) +#define RATE_DISCRETE(x) !((x) & BIT(12)) +#define NUM_REMAINING(x) ((x) >> 16) + struct { + __le32 value_low; + __le32 value_high; + } rate[0]; +#define RATE_TO_U64(X) \ +({ \ + typeof(X) x = (X); \ + le32_to_cpu((x).value_low) | (u64)le32_to_cpu((x).value_high) << 32; \ +}) +}; + +struct scmi_clock_set_rate { + __le32 flags; +#define CLOCK_SET_ASYNC BIT(0) +#define CLOCK_SET_IGNORE_RESP BIT(1) +#define CLOCK_SET_ROUND_UP BIT(2) +#define CLOCK_SET_ROUND_AUTO BIT(3) + __le32 id; + __le32 value_low; + __le32 value_high; +}; + +struct clock_info { + u32 version; + int num_clocks; + int max_async_req; + unsigned cur_async_req; + struct scmi_clock_info *clk; +}; + +static int +scmi_clock_protocol_attributes_get(const struct scmi_protocol_handle *ph, + struct clock_info *ci) +{ + int ret; + struct scmi_xfer *t; + struct scmi_msg_resp_clock_protocol_attributes *attr; + + ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, + 0, sizeof(*attr), &t); + if (ret) + return ret; + + attr = t->rx.buf; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + ci->num_clocks = le16_to_cpu(attr->num_clocks); + ci->max_async_req = attr->max_async_req; + } + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_clock_attributes_get(const struct scmi_protocol_handle *ph, + u32 clk_id, struct scmi_clock_info *clk) +{ + int ret; + struct scmi_xfer *t; + struct scmi_msg_resp_clock_attributes *attr; + + ret = ph->xops->xfer_get_init(ph, CLOCK_ATTRIBUTES, + sizeof(clk_id), sizeof(*attr), &t); + if (ret) + return ret; + + put_unaligned_le32(clk_id, t->tx.buf); + attr = t->rx.buf; + + ret = ph->xops->do_xfer(ph, t); + if (!ret) + strlcpy(clk->name, attr->name, SCMI_MAX_STR_SIZE); + else + clk->name[0] = '\0'; + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int rate_cmp_func(const void *_r1, const void *_r2) +{ + const u64 *r1 = _r1, *r2 = _r2; + + if (*r1 < *r2) + return -1; + else if (*r1 == *r2) + return 0; + else + return 1; +} + +static int +scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, + struct scmi_clock_info *clk) +{ + u64 *rate = NULL; + int ret, cnt; + bool rate_discrete = false; + u32 tot_rate_cnt = 0, rates_flag; + u16 num_returned, num_remaining; + struct scmi_xfer *t; + struct scmi_msg_clock_describe_rates *clk_desc; + struct scmi_msg_resp_clock_describe_rates *rlist; + + ret = ph->xops->xfer_get_init(ph, CLOCK_DESCRIBE_RATES, + sizeof(*clk_desc), 0, &t); + if (ret) + return ret; + + clk_desc = t->tx.buf; + rlist = t->rx.buf; + + do { + clk_desc->id = cpu_to_le32(clk_id); + /* Set the number of rates to be skipped/already read */ + clk_desc->rate_index = cpu_to_le32(tot_rate_cnt); + + ret = ph->xops->do_xfer(ph, t); + if (ret) + goto err; + + rates_flag = le32_to_cpu(rlist->num_rates_flags); + num_remaining = NUM_REMAINING(rates_flag); + rate_discrete = RATE_DISCRETE(rates_flag); + num_returned = NUM_RETURNED(rates_flag); + + if (tot_rate_cnt + num_returned > SCMI_MAX_NUM_RATES) { + dev_err(ph->dev, "No. of rates > MAX_NUM_RATES"); + break; + } + + if (!rate_discrete) { + clk->range.min_rate = RATE_TO_U64(rlist->rate[0]); + clk->range.max_rate = RATE_TO_U64(rlist->rate[1]); + clk->range.step_size = RATE_TO_U64(rlist->rate[2]); + dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n", + clk->range.min_rate, clk->range.max_rate, + clk->range.step_size); + break; + } + + rate = &clk->list.rates[tot_rate_cnt]; + for (cnt = 0; cnt < num_returned; cnt++, rate++) { + *rate = RATE_TO_U64(rlist->rate[cnt]); + dev_dbg(ph->dev, "Rate %llu Hz\n", *rate); + } + + tot_rate_cnt += num_returned; + + ph->xops->reset_rx_to_maxsz(ph, t); + /* + * check for both returned and remaining to avoid infinite + * loop due to buggy firmware + */ + } while (num_returned && num_remaining); + + if (rate_discrete && rate) { + clk->list.num_rates = tot_rate_cnt; + qsort(rate, tot_rate_cnt, sizeof(*rate), rate_cmp_func); + } + + clk->rate_discrete = rate_discrete; + +err: + ph->xops->xfer_put(ph, t); + return ret; +} + +static int +scmi_clock_rate_get(const struct scmi_protocol_handle *ph, + u32 clk_id, u64 *value) +{ + int ret; + struct scmi_xfer *t; + + ret = ph->xops->xfer_get_init(ph, CLOCK_RATE_GET, + sizeof(__le32), sizeof(u64), &t); + if (ret) + return ret; + + put_unaligned_le32(clk_id, t->tx.buf); + + ret = ph->xops->do_xfer(ph, t); + if (!ret) + *value = get_unaligned_le64(t->rx.buf); + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_clock_rate_set(const struct scmi_protocol_handle *ph, + u32 clk_id, u64 rate) +{ + int ret; + u32 flags = 0; + struct scmi_xfer *t; + struct scmi_clock_set_rate *cfg; + struct clock_info *ci = ph->get_priv(ph); + + ret = ph->xops->xfer_get_init(ph, CLOCK_RATE_SET, sizeof(*cfg), 0, &t); + if (ret) + return ret; + + if (ci->max_async_req && + ci->cur_async_req++ < ci->max_async_req) + flags |= CLOCK_SET_ASYNC; + + cfg = t->tx.buf; + cfg->flags = cpu_to_le32(flags); + cfg->id = cpu_to_le32(clk_id); + cfg->value_low = cpu_to_le32(rate & 0xffffffff); + cfg->value_high = cpu_to_le32(rate >> 32); + + if (flags & CLOCK_SET_ASYNC) + ret = ph->xops->do_xfer_with_response(ph, t); + else + ret = ph->xops->do_xfer(ph, t); + + if (ci->max_async_req) + ci->cur_async_req--; + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int +scmi_clock_config_set(const struct scmi_protocol_handle *ph, u32 clk_id, + u32 config) +{ + int ret; + struct scmi_xfer *t; + struct scmi_clock_set_config *cfg; + + ret = ph->xops->xfer_get_init(ph, CLOCK_CONFIG_SET, + sizeof(*cfg), 0, &t); + if (ret) + return ret; + + cfg = t->tx.buf; + cfg->id = cpu_to_le32(clk_id); + cfg->attributes = cpu_to_le32(config); + + ret = ph->xops->do_xfer(ph, t); + + ph->xops->xfer_put(ph, t); + return ret; +} + +static int scmi_clock_enable(const struct scmi_protocol_handle *ph, u32 clk_id) +{ + return scmi_clock_config_set(ph, clk_id, CLOCK_ENABLE); +} + +static int scmi_clock_disable(const struct scmi_protocol_handle *ph, u32 clk_id) +{ + return scmi_clock_config_set(ph, clk_id, 0); +} + +static int scmi_clock_count_get(const struct scmi_protocol_handle *ph) +{ + struct clock_info *ci = ph->get_priv(ph); + + return ci->num_clocks; +} + +static const struct scmi_clock_info * +scmi_clock_info_get(const struct scmi_protocol_handle *ph, u32 clk_id) +{ + struct clock_info *ci = ph->get_priv(ph); + struct scmi_clock_info *clk = ci->clk + clk_id; + + if (!clk->name[0]) + return NULL; + + return clk; +} + +static const struct scmi_clk_proto_ops clk_proto_ops = { + .count_get = scmi_clock_count_get, + .info_get = scmi_clock_info_get, + .rate_get = scmi_clock_rate_get, + .rate_set = scmi_clock_rate_set, + .enable = scmi_clock_enable, + .disable = scmi_clock_disable, +}; + +static int scmi_clock_protocol_init(const struct scmi_protocol_handle *ph) +{ + u32 version; + int clkid, ret; + struct clock_info *cinfo; + + ph->xops->version_get(ph, &version); + + dev_dbg(ph->dev, "Clock Version %d.%d\n", + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); + + cinfo = kzalloc(sizeof(*cinfo), GFP_KERNEL); + if (!cinfo) + return -ENOMEM; + + scmi_clock_protocol_attributes_get(ph, cinfo); + + cinfo->clk = kcalloc(cinfo->num_clocks, sizeof(*cinfo->clk), GFP_KERNEL); + if (!cinfo->clk) + return -ENOMEM; + + for (clkid = 0; clkid < cinfo->num_clocks; clkid++) { + struct scmi_clock_info *clk = cinfo->clk + clkid; + + ret = scmi_clock_attributes_get(ph, clkid, clk); + if (!ret) + scmi_clock_describe_rates_get(ph, clkid, clk); + } + + cinfo->version = version; + return ph->set_priv(ph, cinfo); +} + +static const struct scmi_protocol scmi_clock = { + .id = SCMI_PROTOCOL_CLOCK, + .instance_init = &scmi_clock_protocol_init, + .ops = &clk_proto_ops, +}; + +DEFINE_SCMI_PROTOCOL_REGISTER(clock, scmi_clock) diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index f1f6cdae5af2..7ac9fd1bc961 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -233,6 +233,7 @@ void __exit scmi_bus_exit(void); int __init scmi_##func##_register(void); DECLARE_SCMI_REGISTER(base); DECLARE_SCMI_REGISTER(reset); +DECLARE_SCMI_REGISTER(clock); #define DEFINE_SCMI_PROTOCOL_REGISTER(name, proto) \ static const struct scmi_protocol *__this_proto = &(proto); \ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index d862e4de8afb..ca71e1a279ac 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -1260,6 +1260,7 @@ static int __init scmi_bus_driver_init(void) scmi_base_register(); scmi_reset_register(); + scmi_clock_register(); return 0; } -- 2.30.2 _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox