linux/drivers/clk/tegra/clk-bpmp.c
Maxime Ripard 4552a852f2 clk: tegra: bpmp: Add a determine_rate hook
The Tegra BPMP mux clock implements a mux with a set_parent hook, but
doesn't provide a determine_rate implementation.

This is a bit odd, since set_parent() is there to, as its name implies,
change the parent of a clock. However, the most likely candidates to
trigger that parent change are either the assigned-clock-parents device
tree property or a call to clk_set_rate(), with determine_rate()
figuring out which parent is the best suited for a given rate.

The other trigger would be a call to clk_set_parent(), but it's far less
used, and it doesn't look like there's any obvious user for that clock.

However, the upstream device trees seem to use assigned-clock-parents on
that clock to force the parent at boot time, so it's likely that the
author intent was to force the parent through the device tree and
prevent any reparenting but through an explicit call to
clk_set_parent().

This case would be equivalent to setting the determine_rate
implementation to clk_hw_determine_rate_no_reparent(). Indeed, if no
determine_rate implementation is provided, clk_round_rate() (through
clk_core_round_rate_nolock()) will call itself on the parent if
CLK_SET_RATE_PARENT is set, and will not change the clock rate
otherwise.

Cc: Jonathan Hunter <jonathanh@nvidia.com>
Cc: Peter De Schrijver <pdeschrijver@nvidia.com>
Cc: Prashant Gaikwad <pgaikwad@nvidia.com>
Cc: Thierry Reding <thierry.reding@gmail.com>
Cc: linux-tegra@vger.kernel.org
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Link: https://lore.kernel.org/r/20221018-clk-range-checks-fixes-v4-34-971d5077e7d2@cerno.tech
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2023-06-08 18:39:30 -07:00

706 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2016-2022 NVIDIA Corporation
*/
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/seq_buf.h>
#include <linux/slab.h>
#include <soc/tegra/bpmp.h>
#include <soc/tegra/bpmp-abi.h>
#define TEGRA_BPMP_DUMP_CLOCK_INFO 0
#define TEGRA_BPMP_CLK_HAS_MUX BIT(0)
#define TEGRA_BPMP_CLK_HAS_SET_RATE BIT(1)
#define TEGRA_BPMP_CLK_IS_ROOT BIT(2)
struct tegra_bpmp_clk_info {
unsigned int id;
char name[MRQ_CLK_NAME_MAXLEN];
unsigned int parents[MRQ_CLK_MAX_PARENTS];
unsigned int num_parents;
unsigned long flags;
};
struct tegra_bpmp_clk {
struct clk_hw hw;
struct tegra_bpmp *bpmp;
unsigned int id;
unsigned int num_parents;
unsigned int *parents;
};
static inline struct tegra_bpmp_clk *to_tegra_bpmp_clk(struct clk_hw *hw)
{
return container_of(hw, struct tegra_bpmp_clk, hw);
}
struct tegra_bpmp_clk_message {
unsigned int cmd;
unsigned int id;
struct {
const void *data;
size_t size;
} tx;
struct {
void *data;
size_t size;
int ret;
} rx;
};
static int tegra_bpmp_clk_transfer(struct tegra_bpmp *bpmp,
const struct tegra_bpmp_clk_message *clk)
{
struct mrq_clk_request request;
struct tegra_bpmp_message msg;
void *req = &request;
int err;
memset(&request, 0, sizeof(request));
request.cmd_and_id = (clk->cmd << 24) | clk->id;
/*
* The mrq_clk_request structure has an anonymous union at offset 4
* that contains all possible sub-command structures. Copy the data
* to that union. Ideally we'd be able to refer to it by name, but
* doing so would require changing the ABI header and increase the
* maintenance burden.
*/
memcpy(req + 4, clk->tx.data, clk->tx.size);
memset(&msg, 0, sizeof(msg));
msg.mrq = MRQ_CLK;
msg.tx.data = &request;
msg.tx.size = sizeof(request);
msg.rx.data = clk->rx.data;
msg.rx.size = clk->rx.size;
err = tegra_bpmp_transfer(bpmp, &msg);
if (err < 0)
return err;
else if (msg.rx.ret < 0)
return -EINVAL;
return 0;
}
static int tegra_bpmp_clk_prepare(struct clk_hw *hw)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct tegra_bpmp_clk_message msg;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_ENABLE;
msg.id = clk->id;
return tegra_bpmp_clk_transfer(clk->bpmp, &msg);
}
static void tegra_bpmp_clk_unprepare(struct clk_hw *hw)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct tegra_bpmp_clk_message msg;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_DISABLE;
msg.id = clk->id;
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0)
dev_err(clk->bpmp->dev, "failed to disable clock %s: %d\n",
clk_hw_get_name(hw), err);
}
static int tegra_bpmp_clk_is_prepared(struct clk_hw *hw)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_is_enabled_response response;
struct tegra_bpmp_clk_message msg;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_IS_ENABLED;
msg.id = clk->id;
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0)
return err;
return response.state;
}
static unsigned long tegra_bpmp_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_get_rate_response response;
struct cmd_clk_get_rate_request request;
struct tegra_bpmp_clk_message msg;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_GET_RATE;
msg.id = clk->id;
msg.tx.data = &request;
msg.tx.size = sizeof(request);
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0)
return err;
return response.rate;
}
static int tegra_bpmp_clk_determine_rate(struct clk_hw *hw,
struct clk_rate_request *rate_req)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_round_rate_response response;
struct cmd_clk_round_rate_request request;
struct tegra_bpmp_clk_message msg;
unsigned long rate;
int err;
rate = min(max(rate_req->rate, rate_req->min_rate), rate_req->max_rate);
memset(&request, 0, sizeof(request));
request.rate = min_t(u64, rate, S64_MAX);
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_ROUND_RATE;
msg.id = clk->id;
msg.tx.data = &request;
msg.tx.size = sizeof(request);
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0)
return err;
rate_req->rate = (unsigned long)response.rate;
return 0;
}
static int tegra_bpmp_clk_set_parent(struct clk_hw *hw, u8 index)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_set_parent_response response;
struct cmd_clk_set_parent_request request;
struct tegra_bpmp_clk_message msg;
int err;
memset(&request, 0, sizeof(request));
request.parent_id = clk->parents[index];
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_SET_PARENT;
msg.id = clk->id;
msg.tx.data = &request;
msg.tx.size = sizeof(request);
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0)
return err;
/* XXX check parent ID in response */
return 0;
}
static u8 tegra_bpmp_clk_get_parent(struct clk_hw *hw)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_get_parent_response response;
struct tegra_bpmp_clk_message msg;
unsigned int i;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_GET_PARENT;
msg.id = clk->id;
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0) {
dev_err(clk->bpmp->dev, "failed to get parent for %s: %d\n",
clk_hw_get_name(hw), err);
return U8_MAX;
}
for (i = 0; i < clk->num_parents; i++)
if (clk->parents[i] == response.parent_id)
return i;
return U8_MAX;
}
static int tegra_bpmp_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_set_rate_response response;
struct cmd_clk_set_rate_request request;
struct tegra_bpmp_clk_message msg;
memset(&request, 0, sizeof(request));
request.rate = min_t(u64, rate, S64_MAX);
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_SET_RATE;
msg.id = clk->id;
msg.tx.data = &request;
msg.tx.size = sizeof(request);
msg.rx.data = &response;
msg.rx.size = sizeof(response);
return tegra_bpmp_clk_transfer(clk->bpmp, &msg);
}
static const struct clk_ops tegra_bpmp_clk_gate_ops = {
.prepare = tegra_bpmp_clk_prepare,
.unprepare = tegra_bpmp_clk_unprepare,
.is_prepared = tegra_bpmp_clk_is_prepared,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
};
static const struct clk_ops tegra_bpmp_clk_mux_ops = {
.prepare = tegra_bpmp_clk_prepare,
.unprepare = tegra_bpmp_clk_unprepare,
.is_prepared = tegra_bpmp_clk_is_prepared,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
.determine_rate = clk_hw_determine_rate_no_reparent,
.set_parent = tegra_bpmp_clk_set_parent,
.get_parent = tegra_bpmp_clk_get_parent,
};
static const struct clk_ops tegra_bpmp_clk_rate_ops = {
.prepare = tegra_bpmp_clk_prepare,
.unprepare = tegra_bpmp_clk_unprepare,
.is_prepared = tegra_bpmp_clk_is_prepared,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
.determine_rate = tegra_bpmp_clk_determine_rate,
.set_rate = tegra_bpmp_clk_set_rate,
};
static const struct clk_ops tegra_bpmp_clk_mux_rate_ops = {
.prepare = tegra_bpmp_clk_prepare,
.unprepare = tegra_bpmp_clk_unprepare,
.is_prepared = tegra_bpmp_clk_is_prepared,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
.determine_rate = tegra_bpmp_clk_determine_rate,
.set_parent = tegra_bpmp_clk_set_parent,
.get_parent = tegra_bpmp_clk_get_parent,
.set_rate = tegra_bpmp_clk_set_rate,
};
static const struct clk_ops tegra_bpmp_clk_mux_read_only_ops = {
.get_parent = tegra_bpmp_clk_get_parent,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
};
static const struct clk_ops tegra_bpmp_clk_read_only_ops = {
.recalc_rate = tegra_bpmp_clk_recalc_rate,
};
static const struct clk_ops tegra_bpmp_clk_gate_mux_read_only_ops = {
.prepare = tegra_bpmp_clk_prepare,
.unprepare = tegra_bpmp_clk_unprepare,
.is_prepared = tegra_bpmp_clk_is_prepared,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
.get_parent = tegra_bpmp_clk_get_parent,
};
static int tegra_bpmp_clk_get_max_id(struct tegra_bpmp *bpmp)
{
struct cmd_clk_get_max_clk_id_response response;
struct tegra_bpmp_clk_message msg;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_GET_MAX_CLK_ID;
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(bpmp, &msg);
if (err < 0)
return err;
if (response.max_id > INT_MAX)
return -E2BIG;
return response.max_id;
}
static int tegra_bpmp_clk_get_info(struct tegra_bpmp *bpmp, unsigned int id,
struct tegra_bpmp_clk_info *info)
{
struct cmd_clk_get_all_info_response response;
struct tegra_bpmp_clk_message msg;
unsigned int i;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_GET_ALL_INFO;
msg.id = id;
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(bpmp, &msg);
if (err < 0)
return err;
strscpy(info->name, response.name, MRQ_CLK_NAME_MAXLEN);
info->num_parents = response.num_parents;
for (i = 0; i < info->num_parents; i++)
info->parents[i] = response.parents[i];
info->flags = response.flags;
return 0;
}
static void tegra_bpmp_clk_info_dump(struct tegra_bpmp *bpmp,
const char *level,
const struct tegra_bpmp_clk_info *info)
{
const char *prefix = "";
struct seq_buf buf;
unsigned int i;
char flags[64];
seq_buf_init(&buf, flags, sizeof(flags));
if (info->flags)
seq_buf_printf(&buf, "(");
if (info->flags & TEGRA_BPMP_CLK_HAS_MUX) {
seq_buf_printf(&buf, "%smux", prefix);
prefix = ", ";
}
if ((info->flags & TEGRA_BPMP_CLK_HAS_SET_RATE) == 0) {
seq_buf_printf(&buf, "%sfixed", prefix);
prefix = ", ";
}
if (info->flags & TEGRA_BPMP_CLK_IS_ROOT) {
seq_buf_printf(&buf, "%sroot", prefix);
prefix = ", ";
}
if (info->flags)
seq_buf_printf(&buf, ")");
dev_printk(level, bpmp->dev, "%03u: %s\n", info->id, info->name);
dev_printk(level, bpmp->dev, " flags: %lx %s\n", info->flags, flags);
dev_printk(level, bpmp->dev, " parents: %u\n", info->num_parents);
for (i = 0; i < info->num_parents; i++)
dev_printk(level, bpmp->dev, " %03u\n", info->parents[i]);
}
static int tegra_bpmp_probe_clocks(struct tegra_bpmp *bpmp,
struct tegra_bpmp_clk_info **clocksp)
{
struct tegra_bpmp_clk_info *clocks;
unsigned int max_id, id, count = 0;
unsigned int holes = 0;
int err;
err = tegra_bpmp_clk_get_max_id(bpmp);
if (err < 0)
return err;
max_id = err;
dev_dbg(bpmp->dev, "maximum clock ID: %u\n", max_id);
clocks = kcalloc(max_id + 1, sizeof(*clocks), GFP_KERNEL);
if (!clocks)
return -ENOMEM;
for (id = 0; id <= max_id; id++) {
struct tegra_bpmp_clk_info *info = &clocks[count];
err = tegra_bpmp_clk_get_info(bpmp, id, info);
if (err < 0)
continue;
if (info->num_parents >= U8_MAX) {
dev_err(bpmp->dev,
"clock %u has too many parents (%u, max: %u)\n",
id, info->num_parents, U8_MAX);
continue;
}
/* clock not exposed by BPMP */
if (info->name[0] == '\0') {
holes++;
continue;
}
info->id = id;
count++;
if (TEGRA_BPMP_DUMP_CLOCK_INFO)
tegra_bpmp_clk_info_dump(bpmp, KERN_DEBUG, info);
}
dev_dbg(bpmp->dev, "holes: %u\n", holes);
*clocksp = clocks;
return count;
}
static unsigned int
tegra_bpmp_clk_id_to_index(const struct tegra_bpmp_clk_info *clocks,
unsigned int num_clocks, unsigned int id)
{
unsigned int i;
for (i = 0; i < num_clocks; i++)
if (clocks[i].id == id)
return i;
return UINT_MAX;
}
static const struct tegra_bpmp_clk_info *
tegra_bpmp_clk_find(const struct tegra_bpmp_clk_info *clocks,
unsigned int num_clocks, unsigned int id)
{
unsigned int i;
i = tegra_bpmp_clk_id_to_index(clocks, num_clocks, id);
if (i < num_clocks)
return &clocks[i];
return NULL;
}
static struct tegra_bpmp_clk *
tegra_bpmp_clk_register(struct tegra_bpmp *bpmp,
const struct tegra_bpmp_clk_info *info,
const struct tegra_bpmp_clk_info *clocks,
unsigned int num_clocks)
{
struct tegra_bpmp_clk *clk;
struct clk_init_data init;
const char **parents;
unsigned int i;
int err;
clk = devm_kzalloc(bpmp->dev, sizeof(*clk), GFP_KERNEL);
if (!clk)
return ERR_PTR(-ENOMEM);
clk->id = info->id;
clk->bpmp = bpmp;
clk->parents = devm_kcalloc(bpmp->dev, info->num_parents,
sizeof(*clk->parents), GFP_KERNEL);
if (!clk->parents)
return ERR_PTR(-ENOMEM);
clk->num_parents = info->num_parents;
/* hardware clock initialization */
memset(&init, 0, sizeof(init));
init.name = info->name;
clk->hw.init = &init;
if (info->flags & BPMP_CLK_STATE_CHANGE_DENIED) {
if ((info->flags & BPMP_CLK_RATE_PARENT_CHANGE_DENIED) == 0) {
dev_WARN(bpmp->dev,
"Firmware bug! Inconsistent permission bits for clock %s. State and parent/rate changes disabled.",
init.name);
}
if (info->flags & TEGRA_BPMP_CLK_HAS_MUX)
init.ops = &tegra_bpmp_clk_mux_read_only_ops;
else
init.ops = &tegra_bpmp_clk_read_only_ops;
} else if (info->flags & BPMP_CLK_RATE_PARENT_CHANGE_DENIED) {
if (info->flags & TEGRA_BPMP_CLK_HAS_MUX)
init.ops = &tegra_bpmp_clk_gate_mux_read_only_ops;
else
init.ops = &tegra_bpmp_clk_gate_ops;
} else if (info->flags & TEGRA_BPMP_CLK_HAS_MUX) {
if (info->flags & TEGRA_BPMP_CLK_HAS_SET_RATE)
init.ops = &tegra_bpmp_clk_mux_rate_ops;
else
init.ops = &tegra_bpmp_clk_mux_ops;
} else {
if (info->flags & TEGRA_BPMP_CLK_HAS_SET_RATE)
init.ops = &tegra_bpmp_clk_rate_ops;
else
init.ops = &tegra_bpmp_clk_gate_ops;
}
init.num_parents = info->num_parents;
parents = kcalloc(info->num_parents, sizeof(*parents), GFP_KERNEL);
if (!parents)
return ERR_PTR(-ENOMEM);
for (i = 0; i < info->num_parents; i++) {
const struct tegra_bpmp_clk_info *parent;
/* keep a private copy of the ID to parent index map */
clk->parents[i] = info->parents[i];
parent = tegra_bpmp_clk_find(clocks, num_clocks,
info->parents[i]);
if (!parent) {
dev_err(bpmp->dev, "no parent %u found for %u\n",
info->parents[i], info->id);
continue;
}
parents[i] = parent->name;
}
init.parent_names = parents;
err = devm_clk_hw_register(bpmp->dev, &clk->hw);
kfree(parents);
if (err < 0)
return ERR_PTR(err);
return clk;
}
static void tegra_bpmp_register_clocks_one(struct tegra_bpmp *bpmp,
struct tegra_bpmp_clk_info *infos,
unsigned int i,
unsigned int count)
{
unsigned int j;
struct tegra_bpmp_clk_info *info;
struct tegra_bpmp_clk *clk;
if (bpmp->clocks[i]) {
/* already registered */
return;
}
info = &infos[i];
for (j = 0; j < info->num_parents; ++j) {
unsigned int p_id = info->parents[j];
unsigned int p_i = tegra_bpmp_clk_id_to_index(infos, count,
p_id);
if (p_i < count)
tegra_bpmp_register_clocks_one(bpmp, infos, p_i, count);
}
clk = tegra_bpmp_clk_register(bpmp, info, infos, count);
if (IS_ERR(clk)) {
dev_err(bpmp->dev,
"failed to register clock %u (%s): %ld\n",
info->id, info->name, PTR_ERR(clk));
/* intentionally store the error pointer to
* bpmp->clocks[i] to avoid re-attempting the
* registration later
*/
}
bpmp->clocks[i] = clk;
}
static int tegra_bpmp_register_clocks(struct tegra_bpmp *bpmp,
struct tegra_bpmp_clk_info *infos,
unsigned int count)
{
unsigned int i;
bpmp->num_clocks = count;
bpmp->clocks = devm_kcalloc(bpmp->dev, count, sizeof(struct tegra_bpmp_clk), GFP_KERNEL);
if (!bpmp->clocks)
return -ENOMEM;
for (i = 0; i < count; i++) {
tegra_bpmp_register_clocks_one(bpmp, infos, i, count);
}
return 0;
}
static void tegra_bpmp_unregister_clocks(struct tegra_bpmp *bpmp)
{
unsigned int i;
for (i = 0; i < bpmp->num_clocks; i++)
clk_hw_unregister(&bpmp->clocks[i]->hw);
}
static struct clk_hw *tegra_bpmp_clk_of_xlate(struct of_phandle_args *clkspec,
void *data)
{
unsigned int id = clkspec->args[0], i;
struct tegra_bpmp *bpmp = data;
for (i = 0; i < bpmp->num_clocks; i++) {
struct tegra_bpmp_clk *clk = bpmp->clocks[i];
if (!clk)
continue;
if (clk->id == id)
return &clk->hw;
}
return NULL;
}
int tegra_bpmp_init_clocks(struct tegra_bpmp *bpmp)
{
struct tegra_bpmp_clk_info *clocks;
unsigned int count;
int err;
err = tegra_bpmp_probe_clocks(bpmp, &clocks);
if (err < 0)
return err;
count = err;
dev_dbg(bpmp->dev, "%u clocks probed\n", count);
err = tegra_bpmp_register_clocks(bpmp, clocks, count);
if (err < 0)
goto free;
err = of_clk_add_hw_provider(bpmp->dev->of_node,
tegra_bpmp_clk_of_xlate,
bpmp);
if (err < 0) {
tegra_bpmp_unregister_clocks(bpmp);
goto free;
}
free:
kfree(clocks);
return err;
}