// SPDX-License-Identifier: GPL-2.0 /* * RISC-V MPXY Based Clock Driver * * Copyright (C) 2025 Ventana Micro Systems Ltd. */ #include #include #include #include #include #include #include #include #include #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 "); MODULE_DESCRIPTION("Clock Driver based on RPMI message protocol"); MODULE_LICENSE("GPL");