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 > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html