| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * RISC-V MPXY Based Clock Driver |
| * |
| * Copyright (C) 2025 Ventana Micro Systems Ltd. |
| */ |
| |
| #include <linux/clk-provider.h> |
| #include <linux/err.h> |
| #include <linux/mailbox_client.h> |
| #include <linux/mailbox/riscv-rpmi-message.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/types.h> |
| #include <linux/slab.h> |
| #include <linux/wordpart.h> |
| |
| #define RPMI_CLK_DISCRETE_MAX_NUM_RATES 16 |
| #define RPMI_CLK_NAME_LEN 16 |
| |
| #define to_rpmi_clk(clk) container_of(clk, struct rpmi_clk, hw) |
| |
| enum rpmi_clk_config { |
| RPMI_CLK_DISABLE = 0, |
| RPMI_CLK_ENABLE = 1, |
| RPMI_CLK_CONFIG_MAX_IDX |
| }; |
| |
| #define RPMI_CLK_TYPE_MASK GENMASK(1, 0) |
| enum rpmi_clk_type { |
| RPMI_CLK_DISCRETE = 0, |
| RPMI_CLK_LINEAR = 1, |
| RPMI_CLK_TYPE_MAX_IDX |
| }; |
| |
| struct rpmi_clk_context { |
| struct device *dev; |
| struct mbox_chan *chan; |
| struct mbox_client client; |
| u32 max_msg_data_size; |
| }; |
| |
| /* |
| * rpmi_clk_rates represents the rates format |
| * as specified by the RPMI specification. |
| * No other data format (e.g., struct linear_range) |
| * is required to avoid to and from conversion. |
| */ |
| union rpmi_clk_rates { |
| u64 discrete[RPMI_CLK_DISCRETE_MAX_NUM_RATES]; |
| struct { |
| u64 min; |
| u64 max; |
| u64 step; |
| } linear; |
| }; |
| |
| struct rpmi_clk { |
| struct rpmi_clk_context *context; |
| u32 id; |
| u32 num_rates; |
| u32 transition_latency; |
| enum rpmi_clk_type type; |
| union rpmi_clk_rates *rates; |
| char name[RPMI_CLK_NAME_LEN]; |
| struct clk_hw hw; |
| }; |
| |
| struct rpmi_clk_rate_discrete { |
| __le32 lo; |
| __le32 hi; |
| }; |
| |
| struct rpmi_clk_rate_linear { |
| __le32 min_lo; |
| __le32 min_hi; |
| __le32 max_lo; |
| __le32 max_hi; |
| __le32 step_lo; |
| __le32 step_hi; |
| }; |
| |
| struct rpmi_get_num_clocks_rx { |
| __le32 status; |
| __le32 num_clocks; |
| }; |
| |
| struct rpmi_get_attrs_tx { |
| __le32 clkid; |
| }; |
| |
| struct rpmi_get_attrs_rx { |
| __le32 status; |
| __le32 flags; |
| __le32 num_rates; |
| __le32 transition_latency; |
| char name[RPMI_CLK_NAME_LEN]; |
| }; |
| |
| struct rpmi_get_supp_rates_tx { |
| __le32 clkid; |
| __le32 clk_rate_idx; |
| }; |
| |
| struct rpmi_get_supp_rates_rx { |
| __le32 status; |
| __le32 flags; |
| __le32 remaining; |
| __le32 returned; |
| __le32 rates[]; |
| }; |
| |
| struct rpmi_get_rate_tx { |
| __le32 clkid; |
| }; |
| |
| struct rpmi_get_rate_rx { |
| __le32 status; |
| __le32 lo; |
| __le32 hi; |
| }; |
| |
| struct rpmi_set_rate_tx { |
| __le32 clkid; |
| __le32 flags; |
| __le32 lo; |
| __le32 hi; |
| }; |
| |
| struct rpmi_set_rate_rx { |
| __le32 status; |
| }; |
| |
| struct rpmi_set_config_tx { |
| __le32 clkid; |
| __le32 config; |
| }; |
| |
| struct rpmi_set_config_rx { |
| __le32 status; |
| }; |
| |
| static inline u64 rpmi_clkrate_u64(u32 __hi, u32 __lo) |
| { |
| return (((u64)(__hi) << 32) | (u32)(__lo)); |
| } |
| |
| static u32 rpmi_clk_get_num_clocks(struct rpmi_clk_context *context) |
| { |
| struct rpmi_get_num_clocks_rx rx, *resp; |
| struct rpmi_mbox_message msg; |
| int ret; |
| |
| rpmi_mbox_init_send_with_response(&msg, RPMI_CLK_SRV_GET_NUM_CLOCKS, |
| NULL, 0, &rx, sizeof(rx)); |
| |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return 0; |
| |
| resp = rpmi_mbox_get_msg_response(&msg); |
| if (!resp || resp->status) |
| return 0; |
| |
| return le32_to_cpu(resp->num_clocks); |
| } |
| |
| static int rpmi_clk_get_attrs(u32 clkid, struct rpmi_clk *rpmi_clk) |
| { |
| struct rpmi_clk_context *context = rpmi_clk->context; |
| struct rpmi_mbox_message msg; |
| struct rpmi_get_attrs_tx tx; |
| struct rpmi_get_attrs_rx rx, *resp; |
| u8 format; |
| int ret; |
| |
| tx.clkid = cpu_to_le32(clkid); |
| rpmi_mbox_init_send_with_response(&msg, RPMI_CLK_SRV_GET_ATTRIBUTES, |
| &tx, sizeof(tx), &rx, sizeof(rx)); |
| |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return ret; |
| |
| resp = rpmi_mbox_get_msg_response(&msg); |
| if (!resp) |
| return -EINVAL; |
| if (resp->status) |
| return rpmi_to_linux_error(le32_to_cpu(resp->status)); |
| |
| rpmi_clk->id = clkid; |
| rpmi_clk->num_rates = le32_to_cpu(resp->num_rates); |
| rpmi_clk->transition_latency = le32_to_cpu(resp->transition_latency); |
| strscpy(rpmi_clk->name, resp->name, RPMI_CLK_NAME_LEN); |
| |
| format = le32_to_cpu(resp->flags) & RPMI_CLK_TYPE_MASK; |
| if (format >= RPMI_CLK_TYPE_MAX_IDX) |
| return -EINVAL; |
| |
| rpmi_clk->type = format; |
| |
| return 0; |
| } |
| |
| static int rpmi_clk_get_supported_rates(u32 clkid, struct rpmi_clk *rpmi_clk) |
| { |
| struct rpmi_clk_context *context = rpmi_clk->context; |
| struct rpmi_clk_rate_discrete *rate_discrete; |
| struct rpmi_clk_rate_linear *rate_linear; |
| struct rpmi_get_supp_rates_tx tx; |
| struct rpmi_get_supp_rates_rx *resp; |
| struct rpmi_mbox_message msg; |
| size_t clk_rate_idx; |
| int ret, rateidx, j; |
| |
| tx.clkid = cpu_to_le32(clkid); |
| tx.clk_rate_idx = 0; |
| |
| /* |
| * Make sure we allocate rx buffer sufficient to be accommodate all |
| * the rates sent in one RPMI message. |
| */ |
| struct rpmi_get_supp_rates_rx *rx __free(kfree) = |
| kzalloc(context->max_msg_data_size, GFP_KERNEL); |
| if (!rx) |
| return -ENOMEM; |
| |
| rpmi_mbox_init_send_with_response(&msg, RPMI_CLK_SRV_GET_SUPPORTED_RATES, |
| &tx, sizeof(tx), rx, context->max_msg_data_size); |
| |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return ret; |
| |
| resp = rpmi_mbox_get_msg_response(&msg); |
| if (!resp) |
| return -EINVAL; |
| if (resp->status) |
| return rpmi_to_linux_error(le32_to_cpu(resp->status)); |
| if (!le32_to_cpu(resp->returned)) |
| return -EINVAL; |
| |
| if (rpmi_clk->type == RPMI_CLK_DISCRETE) { |
| rate_discrete = (struct rpmi_clk_rate_discrete *)resp->rates; |
| |
| for (rateidx = 0; rateidx < le32_to_cpu(resp->returned); rateidx++) { |
| rpmi_clk->rates->discrete[rateidx] = |
| rpmi_clkrate_u64(le32_to_cpu(rate_discrete[rateidx].hi), |
| le32_to_cpu(rate_discrete[rateidx].lo)); |
| } |
| |
| /* |
| * Keep sending the request message until all |
| * the rates are received. |
| */ |
| clk_rate_idx = 0; |
| while (le32_to_cpu(resp->remaining)) { |
| clk_rate_idx += le32_to_cpu(resp->returned); |
| tx.clk_rate_idx = cpu_to_le32(clk_rate_idx); |
| |
| rpmi_mbox_init_send_with_response(&msg, |
| RPMI_CLK_SRV_GET_SUPPORTED_RATES, |
| &tx, sizeof(tx), |
| rx, context->max_msg_data_size); |
| |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return ret; |
| |
| resp = rpmi_mbox_get_msg_response(&msg); |
| if (!resp) |
| return -EINVAL; |
| if (resp->status) |
| return rpmi_to_linux_error(le32_to_cpu(resp->status)); |
| if (!le32_to_cpu(resp->returned)) |
| return -EINVAL; |
| |
| for (j = 0; j < le32_to_cpu(resp->returned); j++) { |
| if (rateidx >= clk_rate_idx + le32_to_cpu(resp->returned)) |
| break; |
| rpmi_clk->rates->discrete[rateidx++] = |
| rpmi_clkrate_u64(le32_to_cpu(rate_discrete[j].hi), |
| le32_to_cpu(rate_discrete[j].lo)); |
| } |
| } |
| } else if (rpmi_clk->type == RPMI_CLK_LINEAR) { |
| rate_linear = (struct rpmi_clk_rate_linear *)resp->rates; |
| |
| rpmi_clk->rates->linear.min = rpmi_clkrate_u64(le32_to_cpu(rate_linear->min_hi), |
| le32_to_cpu(rate_linear->min_lo)); |
| rpmi_clk->rates->linear.max = rpmi_clkrate_u64(le32_to_cpu(rate_linear->max_hi), |
| le32_to_cpu(rate_linear->max_lo)); |
| rpmi_clk->rates->linear.step = rpmi_clkrate_u64(le32_to_cpu(rate_linear->step_hi), |
| le32_to_cpu(rate_linear->step_lo)); |
| } |
| |
| return 0; |
| } |
| |
| static unsigned long rpmi_clk_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| struct rpmi_clk *rpmi_clk = to_rpmi_clk(hw); |
| struct rpmi_clk_context *context = rpmi_clk->context; |
| struct rpmi_mbox_message msg; |
| struct rpmi_get_rate_tx tx; |
| struct rpmi_get_rate_rx rx, *resp; |
| int ret; |
| |
| tx.clkid = cpu_to_le32(rpmi_clk->id); |
| |
| rpmi_mbox_init_send_with_response(&msg, RPMI_CLK_SRV_GET_RATE, |
| &tx, sizeof(tx), &rx, sizeof(rx)); |
| |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return ret; |
| |
| resp = rpmi_mbox_get_msg_response(&msg); |
| if (!resp) |
| return -EINVAL; |
| if (resp->status) |
| return rpmi_to_linux_error(le32_to_cpu(resp->status)); |
| |
| return rpmi_clkrate_u64(le32_to_cpu(resp->hi), le32_to_cpu(resp->lo)); |
| } |
| |
| static int rpmi_clk_determine_rate(struct clk_hw *hw, |
| struct clk_rate_request *req) |
| { |
| struct rpmi_clk *rpmi_clk = to_rpmi_clk(hw); |
| u64 fmin, fmax, ftmp; |
| |
| /* |
| * Keep the requested rate if the clock format |
| * is of discrete type. Let the platform which |
| * is actually controlling the clock handle that. |
| */ |
| if (rpmi_clk->type == RPMI_CLK_DISCRETE) |
| return 0; |
| |
| fmin = rpmi_clk->rates->linear.min; |
| fmax = rpmi_clk->rates->linear.max; |
| |
| if (req->rate <= fmin) { |
| req->rate = fmin; |
| return 0; |
| } else if (req->rate >= fmax) { |
| req->rate = fmax; |
| return 0; |
| } |
| |
| ftmp = req->rate - fmin; |
| ftmp += rpmi_clk->rates->linear.step - 1; |
| do_div(ftmp, rpmi_clk->rates->linear.step); |
| |
| req->rate = ftmp * rpmi_clk->rates->linear.step + fmin; |
| |
| return 0; |
| } |
| |
| static int rpmi_clk_set_rate(struct clk_hw *hw, unsigned long rate, |
| unsigned long parent_rate) |
| { |
| struct rpmi_clk *rpmi_clk = to_rpmi_clk(hw); |
| struct rpmi_clk_context *context = rpmi_clk->context; |
| struct rpmi_mbox_message msg; |
| struct rpmi_set_rate_tx tx; |
| struct rpmi_set_rate_rx rx, *resp; |
| int ret; |
| |
| tx.clkid = cpu_to_le32(rpmi_clk->id); |
| tx.lo = cpu_to_le32(lower_32_bits(rate)); |
| tx.hi = cpu_to_le32(upper_32_bits(rate)); |
| |
| rpmi_mbox_init_send_with_response(&msg, RPMI_CLK_SRV_SET_RATE, |
| &tx, sizeof(tx), &rx, sizeof(rx)); |
| |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return ret; |
| |
| resp = rpmi_mbox_get_msg_response(&msg); |
| if (!resp) |
| return -EINVAL; |
| if (resp->status) |
| return rpmi_to_linux_error(le32_to_cpu(resp->status)); |
| |
| return 0; |
| } |
| |
| static int rpmi_clk_enable(struct clk_hw *hw) |
| { |
| struct rpmi_clk *rpmi_clk = to_rpmi_clk(hw); |
| struct rpmi_clk_context *context = rpmi_clk->context; |
| struct rpmi_mbox_message msg; |
| struct rpmi_set_config_tx tx; |
| struct rpmi_set_config_rx rx, *resp; |
| int ret; |
| |
| tx.config = cpu_to_le32(RPMI_CLK_ENABLE); |
| tx.clkid = cpu_to_le32(rpmi_clk->id); |
| |
| rpmi_mbox_init_send_with_response(&msg, RPMI_CLK_SRV_SET_CONFIG, |
| &tx, sizeof(tx), &rx, sizeof(rx)); |
| |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return ret; |
| |
| resp = rpmi_mbox_get_msg_response(&msg); |
| if (!resp) |
| return -EINVAL; |
| if (resp->status) |
| return rpmi_to_linux_error(le32_to_cpu(resp->status)); |
| |
| return 0; |
| } |
| |
| static void rpmi_clk_disable(struct clk_hw *hw) |
| { |
| struct rpmi_clk *rpmi_clk = to_rpmi_clk(hw); |
| struct rpmi_clk_context *context = rpmi_clk->context; |
| struct rpmi_mbox_message msg; |
| struct rpmi_set_config_tx tx; |
| struct rpmi_set_config_rx rx; |
| |
| tx.config = cpu_to_le32(RPMI_CLK_DISABLE); |
| tx.clkid = cpu_to_le32(rpmi_clk->id); |
| |
| rpmi_mbox_init_send_with_response(&msg, RPMI_CLK_SRV_SET_CONFIG, |
| &tx, sizeof(tx), &rx, sizeof(rx)); |
| |
| rpmi_mbox_send_message(context->chan, &msg); |
| } |
| |
| static const struct clk_ops rpmi_clk_ops = { |
| .recalc_rate = rpmi_clk_recalc_rate, |
| .determine_rate = rpmi_clk_determine_rate, |
| .set_rate = rpmi_clk_set_rate, |
| .prepare = rpmi_clk_enable, |
| .unprepare = rpmi_clk_disable, |
| }; |
| |
| static struct clk_hw *rpmi_clk_enumerate(struct rpmi_clk_context *context, u32 clkid) |
| { |
| struct device *dev = context->dev; |
| unsigned long min_rate, max_rate; |
| union rpmi_clk_rates *rates; |
| struct rpmi_clk *rpmi_clk; |
| struct clk_init_data init = {}; |
| struct clk_hw *clk_hw; |
| int ret; |
| |
| rates = devm_kzalloc(dev, sizeof(*rates), GFP_KERNEL); |
| if (!rates) |
| return ERR_PTR(-ENOMEM); |
| |
| rpmi_clk = devm_kzalloc(dev, sizeof(*rpmi_clk), GFP_KERNEL); |
| if (!rpmi_clk) |
| return ERR_PTR(-ENOMEM); |
| |
| rpmi_clk->context = context; |
| rpmi_clk->rates = rates; |
| |
| ret = rpmi_clk_get_attrs(clkid, rpmi_clk); |
| if (ret) |
| return dev_err_ptr_probe(dev, ret, |
| "Failed to get clk-%u attributes\n", |
| clkid); |
| |
| ret = rpmi_clk_get_supported_rates(clkid, rpmi_clk); |
| if (ret) |
| return dev_err_ptr_probe(dev, ret, |
| "Get supported rates failed for clk-%u\n", |
| clkid); |
| |
| init.flags = CLK_GET_RATE_NOCACHE; |
| init.num_parents = 0; |
| init.ops = &rpmi_clk_ops; |
| init.name = rpmi_clk->name; |
| clk_hw = &rpmi_clk->hw; |
| clk_hw->init = &init; |
| |
| ret = devm_clk_hw_register(dev, clk_hw); |
| if (ret) |
| return dev_err_ptr_probe(dev, ret, |
| "Unable to register clk-%u\n", |
| clkid); |
| |
| if (rpmi_clk->type == RPMI_CLK_DISCRETE) { |
| min_rate = rpmi_clk->rates->discrete[0]; |
| max_rate = rpmi_clk->rates->discrete[rpmi_clk->num_rates - 1]; |
| } else { |
| min_rate = rpmi_clk->rates->linear.min; |
| max_rate = rpmi_clk->rates->linear.max; |
| } |
| |
| clk_hw_set_rate_range(clk_hw, min_rate, max_rate); |
| |
| return clk_hw; |
| } |
| |
| static void rpmi_clk_mbox_chan_release(void *data) |
| { |
| struct mbox_chan *chan = data; |
| |
| mbox_free_channel(chan); |
| } |
| |
| static int rpmi_clk_probe(struct platform_device *pdev) |
| { |
| int ret; |
| unsigned int num_clocks, i; |
| struct clk_hw_onecell_data *clk_data; |
| struct rpmi_clk_context *context; |
| struct rpmi_mbox_message msg; |
| struct clk_hw *hw_ptr; |
| struct device *dev = &pdev->dev; |
| |
| context = devm_kzalloc(dev, sizeof(*context), GFP_KERNEL); |
| if (!context) |
| return -ENOMEM; |
| context->dev = dev; |
| platform_set_drvdata(pdev, context); |
| |
| context->client.dev = context->dev; |
| context->client.rx_callback = NULL; |
| context->client.tx_block = false; |
| context->client.knows_txdone = true; |
| context->client.tx_tout = 0; |
| |
| context->chan = mbox_request_channel(&context->client, 0); |
| if (IS_ERR(context->chan)) |
| return PTR_ERR(context->chan); |
| |
| ret = devm_add_action_or_reset(dev, rpmi_clk_mbox_chan_release, context->chan); |
| if (ret) |
| return dev_err_probe(dev, ret, "Failed to add rpmi mbox channel cleanup\n"); |
| |
| rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SPEC_VERSION); |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return dev_err_probe(dev, ret, "Failed to get spec version\n"); |
| if (msg.attr.value < RPMI_MKVER(1, 0)) { |
| return dev_err_probe(dev, -EINVAL, |
| "msg protocol version mismatch, expected 0x%x, found 0x%x\n", |
| RPMI_MKVER(1, 0), msg.attr.value); |
| } |
| |
| rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SERVICEGROUP_ID); |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return dev_err_probe(dev, ret, "Failed to get service group ID\n"); |
| if (msg.attr.value != RPMI_SRVGRP_CLOCK) { |
| return dev_err_probe(dev, -EINVAL, |
| "service group match failed, expected 0x%x, found 0x%x\n", |
| RPMI_SRVGRP_CLOCK, msg.attr.value); |
| } |
| |
| rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SERVICEGROUP_VERSION); |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return dev_err_probe(dev, ret, "Failed to get service group version\n"); |
| if (msg.attr.value < RPMI_MKVER(1, 0)) { |
| return dev_err_probe(dev, -EINVAL, |
| "service group version failed, expected 0x%x, found 0x%x\n", |
| RPMI_MKVER(1, 0), msg.attr.value); |
| } |
| |
| rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE); |
| ret = rpmi_mbox_send_message(context->chan, &msg); |
| if (ret) |
| return dev_err_probe(dev, ret, "Failed to get max message data size\n"); |
| |
| context->max_msg_data_size = msg.attr.value; |
| num_clocks = rpmi_clk_get_num_clocks(context); |
| if (!num_clocks) |
| return dev_err_probe(dev, -ENODEV, "No clocks found\n"); |
| |
| clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, num_clocks), |
| GFP_KERNEL); |
| if (!clk_data) |
| return dev_err_probe(dev, -ENOMEM, "No memory for clock data\n"); |
| clk_data->num = num_clocks; |
| |
| for (i = 0; i < clk_data->num; i++) { |
| hw_ptr = rpmi_clk_enumerate(context, i); |
| if (IS_ERR(hw_ptr)) { |
| return dev_err_probe(dev, PTR_ERR(hw_ptr), |
| "Failed to register clk-%d\n", i); |
| } |
| clk_data->hws[i] = hw_ptr; |
| } |
| |
| ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data); |
| if (ret) |
| return dev_err_probe(dev, ret, "Failed to register clock HW provider\n"); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id rpmi_clk_of_match[] = { |
| { .compatible = "riscv,rpmi-clock" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, rpmi_clk_of_match); |
| |
| static struct platform_driver rpmi_clk_driver = { |
| .driver = { |
| .name = "riscv-rpmi-clock", |
| .of_match_table = rpmi_clk_of_match, |
| }, |
| .probe = rpmi_clk_probe, |
| }; |
| module_platform_driver(rpmi_clk_driver); |
| |
| MODULE_AUTHOR("Rahul Pathak <rpathak@ventanamicro.com>"); |
| MODULE_DESCRIPTION("Clock Driver based on RPMI message protocol"); |
| MODULE_LICENSE("GPL"); |