Re: [RFC PATCH 5/8] firmware: arm_scmi: add initial support for clock protocol

From: Roy Franz
Date: Wed Jun 07 2017 - 15:20:41 EST


On Wed, Jun 7, 2017 at 9:10 AM, Sudeep Holla <sudeep.holla@xxxxxxx> wrote:
> The clock protocol is intended for management of clocks. It is used to
> enable or disable clocks, and to set and get the clock rates. This
> protocol provides commands to describe the protocol version, discover
> various implementation specific attributes, describe a clock, enable
> and disable a clock and get/set the rate of the clock synchronously or
> asynchronously.
>
> This patch adds initial support for the clock protocol.
>
> Signed-off-by: Sudeep Holla <sudeep.holla@xxxxxxx>
> ---
> drivers/firmware/arm_scmi/Makefile | 2 +-
> drivers/firmware/arm_scmi/clock.c | 340 +++++++++++++++++++++++++++++++++++++
> drivers/firmware/arm_scmi/common.h | 1 +
> include/linux/scmi_protocol.h | 18 ++
> 4 files changed, 360 insertions(+), 1 deletion(-)
> create mode 100644 drivers/firmware/arm_scmi/clock.c
>
> diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
> index 159de726ee45..6836b1f38f7f 100644
> --- a/drivers/firmware/arm_scmi/Makefile
> +++ b/drivers/firmware/arm_scmi/Makefile
> @@ -1,2 +1,2 @@
> obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
> -arm_scmi-y = base.o driver.o perf.o
> +arm_scmi-y = base.o clock.o driver.o perf.o
> diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c
> new file mode 100644
> index 000000000000..e02827a48ab7
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/clock.c
> @@ -0,0 +1,340 @@
> +/*
> + * System Control and Management Interface (SCMI) Clock Protocol
> + *
> + * Copyright (C) 2017 ARM Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program. If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#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;
> +} __packed;
> +
> +struct scmi_msg_resp_clock_attributes {
> + __le32 attributes;
> +#define CLOCK_ENABLE BIT(0)
> + u8 name[SCMI_MAX_STR_SIZE];
> +} __packed;
> +
> +struct scmi_clock_set_config {
> + __le32 id;
> + __le32 attributes;
> +} __packed;
> +
> +struct scmi_msg_clock_describe_rates {
> + __le32 id;
> + __le32 rate_index;
> +} __packed;
> +
> +struct scmi_msg_resp_clock_describe_rates {
> + __le16 num_returned;
> +#define NUM_RETURNED_MASK (0xfff)
> +#define RATE_DISCRETE(x) ((x) & BIT(12))
> + __le16 num_remaining;
> + 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; \
> +})
> +} __packed;
> +
> +struct scmi_clock_set_rate {
> + __le32 flags;
> +#define CLOCK_SET_ASYSC BIT(0)
> +#define CLOCK_SET_DELAYED BIT(1)
> +#define CLOCK_ROUND_UP BIT(2)
> +#define CLOCK_ROUND_AUTO BIT(3)
> + __le32 id;
> + __le32 value_low;
> + __le32 value_high;
> +} __packed;
> +
> +struct clock_info {
> + u32 attributes;
> + char name[SCMI_MAX_STR_SIZE];
> + union {
> + int num_rates;
> + u64 rates[MAX_NUM_RATES];
> + struct {
> + u64 min_rate;
> + u64 max_rate;
> + u64 step_size;
> + } range;
> + };
> +};
> +
> +struct scmi_clock_info {
> + int num_clocks;
> + int max_async_req;
> + struct clock_info *clk;
> +};
> +
> +static struct scmi_clock_info clocks;
> +
> +static int scmi_clock_protocol_attributes_get(struct scmi_handle *handle,
> + struct scmi_clock_info *clocks)
> +{
> + int ret;
> + struct scmi_xfer *t;
> + struct scmi_msg_resp_clock_protocol_attributes *attr;
> +
> + ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
> + SCMI_PROTOCOL_CLOCK, 0, sizeof(*attr), &t);
> + if (ret)
> + return ret;
> +
> + attr = (struct scmi_msg_resp_clock_protocol_attributes *)t->rx.buf;
> +
> + ret = scmi_do_xfer(handle, t);
> + if (!ret) {
> + clocks->num_clocks = le16_to_cpu(attr->num_clocks);
> + clocks->max_async_req = attr->max_async_req;
> + }
> +
> + scmi_put_one_xfer(handle, t);
> + return ret;
> +}
> +
> +static int scmi_clock_attributes_get(struct scmi_handle *handle, u32 clk_id,
> + struct clock_info *clk)
> +{
> + int ret;
> + struct scmi_xfer *t;
> + struct scmi_msg_resp_clock_attributes *attr;
> +
> + ret = scmi_one_xfer_init(handle, CLOCK_ATTRIBUTES, SCMI_PROTOCOL_CLOCK,
> + sizeof(clk_id), sizeof(*attr), &t);
> + if (ret)
> + return ret;
> +
> + *(__le32 *)t->tx.buf = cpu_to_le32(clk_id);
> + attr = (struct scmi_msg_resp_clock_attributes *)t->rx.buf;
> +
> + ret = scmi_do_xfer(handle, t);
> + if (!ret) {
> + clk->attributes = le32_to_cpu(attr->attributes);
> + memcpy(clk->name, attr->name, SCMI_MAX_STR_SIZE);
> + }
> +
> + scmi_put_one_xfer(handle, t);
> + return ret;
> +}
> +
> +static int scmi_clock_describe_rates_get(struct scmi_handle *handle, u32 clk_id,
> + struct clock_info *clk)
> +{
> + u64 *rate;
> + int ret, cnt;
> + bool rate_discrete;
> + u32 tot_rate_cnt = 0;
> + 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 = scmi_one_xfer_init(handle, CLOCK_DESCRIBE_RATES,
> + SCMI_PROTOCOL_CLOCK, sizeof(*clk_desc), 0, &t);
> + if (ret)
> + return ret;
> +
> + clk_desc = (struct scmi_msg_clock_describe_rates *)t->tx.buf;
> + rlist = (struct scmi_msg_resp_clock_describe_rates *)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 = scmi_do_xfer(handle, t);
> + if (ret)
> + break;
> +
> + num_returned = le16_to_cpu(rlist->num_returned);
> + num_remaining = le16_to_cpu(rlist->num_remaining);
> + rate_discrete = RATE_DISCRETE(num_remaining);
> + num_remaining &= NUM_RETURNED_MASK;
> +
> + if (tot_rate_cnt + num_returned > MAX_NUM_RATES) {
> + dev_err(handle->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(handle->dev, "Min %llu Max %llu Step %llu Hz\n",
> + clk->range.min_rate, clk->range.max_rate,
> + clk->range.step_size);
> + break;
> + }
> +
> + rate = &clk->rates[tot_rate_cnt];
> + for (cnt = 0; cnt < num_returned; cnt++, rate++) {
> + *rate = RATE_TO_U64(rlist->rate[cnt]);
> + dev_dbg(handle->dev, "Rate %llu Hz\n", *rate);
> + }
> +
> + tot_rate_cnt += num_returned;
> + /*
> + * check for both returned and remaining to avoid infinite
> + * loop due to buggy firmware
> + */
> + } while (num_returned && num_remaining);
> +
> + clk->num_rates = tot_rate_cnt;
> +
> + scmi_put_one_xfer(handle, t);
> + return ret;
> +}
> +
> +static int
> +scmi_clock_rate_get(struct scmi_handle *handle, u32 clk_id, u64 *value)
> +{
> + int ret;
> + struct scmi_xfer *t;
> +
> + ret = scmi_one_xfer_init(handle, CLOCK_RATE_GET, SCMI_PROTOCOL_CLOCK,
> + sizeof(__le32), sizeof(u64), &t);
> + if (ret)
> + return ret;
> +
> + *(__le32 *)t->tx.buf = cpu_to_le32(clk_id);
> +
> + ret = scmi_do_xfer(handle, t);
> + if (!ret) {
> + __le32 *pval = (__le32 *)t->rx.buf;
> +
> + *value = le32_to_cpu(*pval);
> + *value |= le32_to_cpu(*(pval + 1));
missing shift for upper bits
> + }
> +
> + scmi_put_one_xfer(handle, t);
> + return ret;
> +}
> +
> +static int scmi_clock_rate_set(struct scmi_handle *handle, u32 clk_id,
> + u32 config, u64 rate)
> +{
> + int ret;
> + struct scmi_xfer *t;
> + struct scmi_clock_set_rate *cfg;
> +
> + ret = scmi_one_xfer_init(handle, CLOCK_RATE_SET, SCMI_PROTOCOL_CLOCK,
> + sizeof(*cfg), 0, &t);
> + if (ret)
> + return ret;
> +
> + cfg = (struct scmi_clock_set_rate *)t->tx.buf;
> + cfg->flags = cpu_to_le32(config);
> + cfg->id = cpu_to_le32(clk_id);
> + cfg->value_low = cpu_to_le32(rate & 0xffffffff);
> + cfg->value_high = cpu_to_le32(rate >> 32);
> +
> + ret = scmi_do_xfer(handle, t);
> +
> + scmi_put_one_xfer(handle, t);
> + return ret;
> +}
> +
> +static int
> +scmi_clock_config_set(struct scmi_handle *handle, u32 clk_id, u32 config)
> +{
> + int ret;
> + struct scmi_xfer *t;
> + struct scmi_clock_set_config *cfg;
> +
> + ret = scmi_one_xfer_init(handle, CLOCK_CONFIG_SET, SCMI_PROTOCOL_CLOCK,
> + sizeof(*cfg), 0, &t);
> + if (ret)
> + return ret;
> +
> + cfg = (struct scmi_clock_set_config *)t->tx.buf;
> + cfg->id = cpu_to_le32(clk_id);
> + cfg->attributes = cpu_to_le32(config);
> +
> + ret = scmi_do_xfer(handle, t);
> +
> + scmi_put_one_xfer(handle, t);
> + return ret;
> +}
> +
> +static int scmi_clock_enable(struct scmi_handle *handle, u32 clk_id)
> +{
> + return scmi_clock_config_set(handle, clk_id, CLOCK_ENABLE);
> +}
> +
> +static int scmi_clock_disable(struct scmi_handle *handle, u32 clk_id)
> +{
> + return scmi_clock_config_set(handle, clk_id, 0);
> +}
> +
> +static struct scmi_clk_ops clk_ops = {
> + .rate_get = scmi_clock_rate_get,
> + .rate_set = scmi_clock_rate_set,
> + .enable = scmi_clock_enable,
> + .disable = scmi_clock_disable,
> +};
> +
> +int scmi_clock_protocol_init(struct scmi_handle *handle)
> +{
> + int clk_id;
> + u32 version;
> +
> + if (!scmi_is_protocol_implemented(handle, SCMI_PROTOCOL_CLOCK)) {
> + dev_err(handle->dev, "SCMI Clock protocol not implemented\n");
> + return -EPROTONOSUPPORT;
> + }
> +
> + scmi_version_get(handle, SCMI_PROTOCOL_CLOCK, &version);
> +
> + dev_dbg(handle->dev, "Clock Version %d.%d\n",
> + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
> +
> + scmi_clock_protocol_attributes_get(handle, &clocks);
> +
> + clocks.clk = devm_kcalloc(handle->dev, clocks.num_clocks,
> + sizeof(struct clock_info), GFP_KERNEL);
> + if (!clocks.clk)
> + return -ENOMEM;
> +
> + dev_info(handle->dev, "Num Clock %d Max Async Req %d\n",
> + clocks.num_clocks, clocks.max_async_req);
> +
> + for (clk_id = 0; clk_id < clocks.num_clocks; clk_id++) {
> + struct clock_info *clk = clocks.clk + clk_id;
> +
> + scmi_clock_attributes_get(handle, clk_id, clk);
> + scmi_clock_describe_rates_get(handle, clk_id, clk);
> + }
> +
> + handle->clk_ops = &clk_ops;
> +
> + return 0;
> +}
> diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
> index e153f05720e4..32873a315c76 100644
> --- a/drivers/firmware/arm_scmi/common.h
> +++ b/drivers/firmware/arm_scmi/common.h
> @@ -31,6 +31,7 @@
> #define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK)
> #define MAX_PROTOCOLS_IMP 16
> #define MAX_OPPS 16
> +#define MAX_NUM_RATES 16
>
> enum scmi_std_protocol {
> SCMI_PROTOCOL_BASE = 0x10,
> diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> index 0f25a3defb4c..75ba10441f54 100644
> --- a/include/linux/scmi_protocol.h
> +++ b/include/linux/scmi_protocol.h
> @@ -46,6 +46,22 @@ struct scmi_revision_info {
> struct scmi_handle;
>
> /**
> + * struct scmi_clk_ops - represents the various operations provided
> + * by SCMI Clock Protocol
> + *
> + * @rate_get: request the current clock rate of a clock
> + * @rate_set: set the clock rate of a clock
> + * @enable: enables the specified clock
> + * @disable: disables the specified clock
> + */
> +struct scmi_clk_ops {
> + int (*rate_get)(struct scmi_handle *, u32, u64*);
> + int (*rate_set)(struct scmi_handle *, u32, u32, u64);
> + int (*enable)(struct scmi_handle *, u32);
> + int (*disable)(struct scmi_handle *, u32);
> +};
> +
> +/**
> * struct scmi_perf_ops - represents the various operations provided
> * by SCMI Performance Protocol
> *
> @@ -73,11 +89,13 @@ struct scmi_perf_ops {
> * @dev: pointer to the SCMI device
> * @version: pointer to the structure containing SCMI version information
> * @perf_ops: pointer to set of performance protocol operations
> + * @clk_ops: pointer to set of clock protocol operations
> */
> struct scmi_handle {
> struct device *dev;
> struct scmi_revision_info *version;
> struct scmi_perf_ops *perf_ops;
> + struct scmi_clk_ops *clk_ops;
> };
>
> struct scmi_opp {
> --
> 2.7.4
>