diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2019-04-09 12:24:49 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2019-04-09 12:24:49 +0200 |
commit | 86762248de707c1d5bc59f7ddf73c47fef2e2999 (patch) | |
tree | 48811e5cfee861b1711b40e67b32b28ce85f2b71 /drivers/clk/zynqmp/clkc.c | |
parent | 0272ec9ca9fe43a5d57a603093dc6787e6a3a7e4 (diff) | |
parent | 6daccb9de039ab8b2db41200d64b530d14e428a6 (diff) | |
download | barebox-86762248de707c1d5bc59f7ddf73c47fef2e2999.tar.gz barebox-86762248de707c1d5bc59f7ddf73c47fef2e2999.tar.xz |
Merge branch 'for-next/zynqmp'
Diffstat (limited to 'drivers/clk/zynqmp/clkc.c')
-rw-r--r-- | drivers/clk/zynqmp/clkc.c | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/drivers/clk/zynqmp/clkc.c b/drivers/clk/zynqmp/clkc.c new file mode 100644 index 0000000000..366a12e70a --- /dev/null +++ b/drivers/clk/zynqmp/clkc.c @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Zynq UltraScale+ MPSoC Clock Controller + * + * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@pengutronix.de> + * + * Based on the Linux driver in drivers/clk/zynqmp/ + * + * Copyright (C) 2016-2018 Xilinx + */ + +#include <common.h> +#include <init.h> +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <mach/firmware-zynqmp.h> + +#include "clk-zynqmp.h" + +#define MAX_PARENT 100 +#define MAX_NODES 6 +#define MAX_NAME_LEN 50 + +#define CLK_TYPE_FIELD_MASK GENMASK(3, 0) +#define CLK_FLAG_FIELD_MASK GENMASK(21, 8) +#define CLK_TYPE_FLAG_FIELD_MASK GENMASK(31, 24) + +#define CLK_PARENTS_ID_MASK GENMASK(15, 0) +#define CLK_PARENTS_TYPE_MASK GENMASK(31, 16) + +#define CLK_GET_ATTR_VALID BIT(0) +#define CLK_GET_ATTR_TYPE BIT(2) + +#define CLK_NAME_RESERVED "" +#define CLK_NAME_DUMMY "dummy_name" + +#define CLK_GET_NAME_RESP_LEN 16 +#define CLK_GET_TOPOLOGY_RESP_WORDS 3 +#define CLK_GET_PARENTS_RESP_WORDS 3 + +#define CLK_GET_PARENTS_NONE (-1) +#define CLK_GET_PARENTS_DUMMY (-2) + +enum clk_type { + CLK_TYPE_INVALID, + CLK_TYPE_OUTPUT, + CLK_TYPE_EXTERNAL, +}; + +enum parent_type { + PARENT_CLK_SELF, + PARENT_CLK_NODE1, + PARENT_CLK_NODE2, + PARENT_CLK_NODE3, + PARENT_CLK_NODE4, + PARENT_CLK_EXTERNAL, +}; + +struct clock_parent { + char name[MAX_NAME_LEN]; + enum parent_type type; +}; + +struct zynqmp_clock_info { + char clk_name[MAX_NAME_LEN]; + bool valid; + enum clk_type type; + struct clock_topology node[MAX_NODES]; + unsigned int num_nodes; + struct clock_parent parent[MAX_PARENT]; + unsigned int num_parents; +}; + +static const char clk_type_postfix[][10] = { + [TYPE_INVALID] = "", + [TYPE_MUX] = "_mux", + [TYPE_GATE] = "", + [TYPE_DIV1] = "_div1", + [TYPE_DIV2] = "_div2", + [TYPE_FIXEDFACTOR] = "_ff", + [TYPE_PLL] = "" +}; + +static struct clk *(*const clk_topology[]) (const char *name, + unsigned int clk_id, + const char **parents, + unsigned int num_parents, + const struct clock_topology *nodes) + = { + [TYPE_INVALID] = NULL, + [TYPE_MUX] = zynqmp_clk_register_mux, + [TYPE_PLL] = zynqmp_clk_register_pll, + [TYPE_FIXEDFACTOR] = zynqmp_clk_register_fixed_factor, + [TYPE_DIV1] = zynqmp_clk_register_divider, + [TYPE_DIV2] = zynqmp_clk_register_divider, + [TYPE_GATE] = zynqmp_clk_register_gate +}; + +static struct zynqmp_clock_info *clock_info; +static const struct zynqmp_eemi_ops *eemi_ops; + +static inline bool zynqmp_is_valid_clock(unsigned int id) +{ + return clock_info[id].valid; +} + +static char *zynqmp_get_clock_name(unsigned int id) +{ + if (!zynqmp_is_valid_clock(id)) + return ERR_PTR(-EINVAL); + + return clock_info[id].clk_name; +} + +static enum clk_type zynqmp_get_clock_type(unsigned int id) +{ + if (!zynqmp_is_valid_clock(id)) + return CLK_TYPE_INVALID; + + return clock_info[id].type; +} + +static int zynqmp_get_parent_names(struct device_node *np, + unsigned int clk_id, + const char **parent_names) +{ + int i; + struct clock_topology *clk_nodes; + struct clock_parent *parents; + int ret; + + clk_nodes = clock_info[clk_id].node; + parents = clock_info[clk_id].parent; + + for (i = 0; i < clock_info[clk_id].num_parents; i++) { + switch (parents[i].type) { + case PARENT_CLK_SELF: + break; + case PARENT_CLK_EXTERNAL: + ret = of_property_match_string(np, "clock-names", + parents[i].name); + if (ret < 0) + strcpy(parents[i].name, CLK_NAME_DUMMY); + break; + default: + strcat(parents[i].name, + clk_type_postfix[clk_nodes[parents[i].type - 1].type]); + break; + } + parent_names[i] = parents[i].name; + } + + return clock_info[clk_id].num_parents; +} + +static int zynqmp_pm_clock_query_num_clocks(void) +{ + struct zynqmp_pm_query_data qdata = {0}; + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + qdata.qid = PM_QID_CLOCK_GET_NUM_CLOCKS; + + ret = eemi_ops->query_data(qdata, ret_payload); + if (ret) + return ret; + + return ret_payload[1]; +} + +static int zynqmp_pm_clock_query_name(unsigned int id, char *response) +{ + struct zynqmp_pm_query_data qdata = {0}; + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + qdata.qid = PM_QID_CLOCK_GET_NAME; + qdata.arg1 = id; + + ret = eemi_ops->query_data(qdata, ret_payload); + if (ret) + return ret; + + memcpy(response, ret_payload, CLK_GET_NAME_RESP_LEN); + + return 0; +} + +struct zynqmp_pm_query_fixed_factor { + unsigned int mul; + unsigned int div; +}; + +static int zynqmp_pm_clock_query_fixed_factor(unsigned int id, + struct zynqmp_pm_query_fixed_factor *response) +{ + struct zynqmp_pm_query_data qdata = {0}; + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + qdata.qid = PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS; + qdata.arg1 = id; + + ret = eemi_ops->query_data(qdata, ret_payload); + if (ret) + return ret; + + response->mul = ret_payload[1]; + response->div = ret_payload[2]; + + return 0; +} + +struct zynqmp_pm_query_topology { + unsigned int node[CLK_GET_TOPOLOGY_RESP_WORDS]; +}; + +static int zynqmp_pm_clock_query_topology(unsigned int id, unsigned int index, + struct zynqmp_pm_query_topology *response) +{ + struct zynqmp_pm_query_data qdata; + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + memset(&qdata, 0, sizeof(qdata)); + qdata.qid = PM_QID_CLOCK_GET_TOPOLOGY; + qdata.arg1 = id; + qdata.arg2 = index; + + ret = eemi_ops->query_data(qdata, ret_payload); + if (ret) + return ret; + + memcpy(response, &ret_payload[1], sizeof(*response)); + + return 0; +} + +struct zynqmp_pm_query_parents { + unsigned int parent[CLK_GET_PARENTS_RESP_WORDS]; +}; + +static int zynqmp_pm_clock_query_parents(unsigned int id, unsigned int index, + struct zynqmp_pm_query_parents *response) +{ + struct zynqmp_pm_query_data qdata; + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + memset(&qdata, 0, sizeof(qdata)); + qdata.qid = PM_QID_CLOCK_GET_PARENTS; + qdata.arg1 = id; + qdata.arg2 = index; + + ret = eemi_ops->query_data(qdata, ret_payload); + if (ret) + return ret; + + memcpy(response, &ret_payload[1], sizeof(*response)); + + return 0; +} + +static int zynqmp_pm_clock_query_attributes(unsigned int id, unsigned int *attr) +{ + struct zynqmp_pm_query_data qdata; + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + memset(&qdata, 0, sizeof(qdata)); + qdata.qid = PM_QID_CLOCK_GET_ATTRIBUTES; + qdata.arg1 = id; + + ret = eemi_ops->query_data(qdata, ret_payload); + if (ret) + return ret; + + *attr = ret_payload[1]; + + return 0; +} + +static int zynqmp_clock_parse_topology(struct clock_topology *topology, + struct zynqmp_pm_query_topology *response, + size_t max_nodes) +{ + int i; + enum topology_type type; + + for (i = 0; i < max_nodes && i < ARRAY_SIZE(response->node); i++) { + type = FIELD_GET(CLK_TYPE_FIELD_MASK, response->node[i]); + if (type == TYPE_INVALID) + break; + + topology[i].type = type; + topology[i].flag = + FIELD_GET(CLK_FLAG_FIELD_MASK, response->node[i]); + topology[i].type_flag = + FIELD_GET(CLK_TYPE_FLAG_FIELD_MASK, response->node[i]); + } + + return i; +} + +static int zynqmp_clock_parse_parents(struct clock_parent *parents, + struct zynqmp_pm_query_parents *response, + size_t max_parents) +{ + int i; + struct clock_parent *parent; + char *parent_name; + int parent_id; + + for (i = 0; i < max_parents && i < ARRAY_SIZE(response->parent); i++) { + if (response->parent[i] == CLK_GET_PARENTS_NONE) + break; + + parent = &parents[i]; + if (response->parent[i] == CLK_GET_PARENTS_DUMMY) { + parent->type = PARENT_CLK_SELF; + strncpy(parent->name, CLK_NAME_DUMMY, MAX_NAME_LEN); + continue; + } + + parent_id = FIELD_GET(CLK_PARENTS_ID_MASK, response->parent[i]); + parent_name = zynqmp_get_clock_name(parent_id); + if (IS_ERR(parent_name)) + continue; + + strncpy(parent->name, parent_name, MAX_NAME_LEN); + parent->type = FIELD_GET(CLK_PARENTS_TYPE_MASK, response->parent[i]); + } + + return i; +} + +static int zynqmp_clock_get_topology(unsigned int id, + struct clock_topology *topology, + size_t max_nodes) +{ + int ret; + int i; + struct zynqmp_pm_query_topology response; + + for (i = 0; i < max_nodes; i += ret) { + ret = zynqmp_pm_clock_query_topology(id, i, &response); + if (ret) + return ret; + + ret = zynqmp_clock_parse_topology(&topology[i], &response, + max_nodes - i); + if (ret == 0) + break; + } + + return i; +} + +static int zynqmp_clock_get_parents(unsigned int clk_id, + struct clock_parent *parents, + size_t max_parents) +{ + int ret; + int i; + struct zynqmp_pm_query_parents response; + + for (i = 0; i < max_parents; i += ret) { + ret = zynqmp_pm_clock_query_parents(clk_id, i, &response); + if (ret) + return ret; + + ret = zynqmp_clock_parse_parents(&parents[i], &response, + max_parents - i); + if (ret == 0) + break; + } + + return i; +} + +struct clk *zynqmp_clk_register_fixed_factor(const char *name, + unsigned int clk_id, + const char **parents, + unsigned int num_parents, + const struct clock_topology *topology) +{ + unsigned int err; + struct zynqmp_pm_query_fixed_factor response = {0}; + unsigned flags; + + err = zynqmp_pm_clock_query_fixed_factor(clk_id, &response); + if (err) + return ERR_PTR(err); + + flags = topology->flag; + + return clk_fixed_factor(strdup(name), strdup(parents[0]), + response.mul, response.div, flags); +} + +static struct clk *zynqmp_register_clk_topology(char *clk_name, + int clk_id, + int num_parents, + const char **parent_names) +{ + int i; + unsigned int num_nodes; + char tmp_name[MAX_NAME_LEN]; + char parent_name[MAX_NAME_LEN]; + struct clock_topology *nodes; + struct clk *clk = NULL; + + nodes = clock_info[clk_id].node; + num_nodes = clock_info[clk_id].num_nodes; + + for (i = 0; i < num_nodes; i++) { + if (!clk_topology[nodes[i].type]) + continue; + + /* + * Register only the last sub-node in the chain with the name of the + * original clock, but postfix other sub-node inside the chain with + * their type. + */ + snprintf(tmp_name, MAX_NAME_LEN, "%s%s", clk_name, + (i == num_nodes - 1) ? "" : clk_type_postfix[nodes[i].type]); + + clk = (*clk_topology[nodes[i].type])(tmp_name, clk_id, + parent_names, + num_parents, + &nodes[i]); + if (IS_ERR(clk)) + pr_warn("failed to register node %s of clock %s\n", + tmp_name, clk_name); + + /* + * Only link the first sub-node to the original (first) + * parent, but link every other sub-node with their preceeding + * sub-clock via the first parent. + */ + parent_names[0] = parent_name; + strncpy(parent_name, tmp_name, MAX_NAME_LEN); + } + + return clk; +} + +static int zynqmp_register_clocks(struct device_d *dev, + struct clk **clks, size_t num_clocks) +{ + unsigned int i; + const char *parent_names[MAX_PARENT]; + char *name; + struct device_node *node = dev->device_node; + unsigned int num_parents; + + for (i = 0; i < num_clocks; i++) { + if (zynqmp_get_clock_type(i) != CLK_TYPE_OUTPUT) + continue; + name = zynqmp_get_clock_name(i); + if (IS_ERR(name)) + continue; + + num_parents = zynqmp_get_parent_names(node, i, parent_names); + if (num_parents < 0) { + dev_warn(dev, "failed to find parents for %s\n", name); + continue; + } + + clks[i] = zynqmp_register_clk_topology(name, i, num_parents, + parent_names); + if (IS_ERR_OR_NULL(clks[i])) + dev_warn(dev, "failed to register clock %s: %ld\n", + name, PTR_ERR(clks[i])); + } + + return 0; +} + +static void zynqmp_fill_clock_info(struct zynqmp_clock_info *clock_info, + size_t num_clocks) +{ + int i; + int ret; + unsigned int attr; + + for (i = 0; i < num_clocks; i++) { + zynqmp_pm_clock_query_name(i, clock_info[i].clk_name); + if (!strcmp(clock_info[i].clk_name, CLK_NAME_RESERVED)) + continue; + + ret = zynqmp_pm_clock_query_attributes(i, &attr); + if (ret) + continue; + + clock_info[i].valid = attr & CLK_GET_ATTR_VALID; + clock_info[i].type = attr & CLK_GET_ATTR_TYPE ? + CLK_TYPE_EXTERNAL : CLK_TYPE_OUTPUT; + } + + for (i = 0; i < num_clocks; i++) { + if (zynqmp_get_clock_type(i) != CLK_TYPE_OUTPUT) + continue; + clock_info[i].num_nodes = + zynqmp_clock_get_topology(i, clock_info[i].node, + ARRAY_SIZE(clock_info[i].node)); + } + + for (i = 0; i < num_clocks; i++) { + if (zynqmp_get_clock_type(i) != CLK_TYPE_OUTPUT) + continue; + ret = zynqmp_clock_get_parents(i, clock_info[i].parent, + ARRAY_SIZE(clock_info[i].parent)); + if (ret < 0) + continue; + clock_info[i].num_parents = ret; + } +} + +static int zynqmp_clock_probe(struct device_d *dev) +{ + int err; + u32 api_version; + int num_clocks; + struct clk_onecell_data *clk_data; + + eemi_ops = zynqmp_pm_get_eemi_ops(); + if (!eemi_ops) + return -ENODEV; + + /* Check version to make sure firmware is available */ + err = eemi_ops->get_api_version(&api_version); + if (err) { + dev_err(dev, "Firmware not available\n"); + return err; + } + + num_clocks = zynqmp_pm_clock_query_num_clocks(); + if (num_clocks < 1) + return num_clocks; + + clock_info = kcalloc(num_clocks, sizeof(*clock_info), GFP_KERNEL); + if (!clock_info) + return -ENOMEM; + + zynqmp_fill_clock_info(clock_info, num_clocks); + + clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clks = kcalloc(num_clocks, sizeof(*clk_data->clks), GFP_KERNEL); + if (!clk_data->clks) { + kfree(clk_data); + return -ENOMEM; + } + + zynqmp_register_clocks(dev, clk_data->clks, num_clocks); + clk_data->clk_num = num_clocks; + of_clk_add_provider(dev->device_node, of_clk_src_onecell_get, clk_data); + + /* + * We can free clock_info now, as is only used to store clock info + * from firmware for registering the clocks. + */ + kfree(clock_info); + + return 0; +} + +static struct of_device_id zynqmp_clock_of_match[] = { + {.compatible = "xlnx,zynqmp-clk"}, + {}, +}; + +static struct driver_d zynqmp_clock_driver = { + .probe = zynqmp_clock_probe, + .name = "zynqmp_clock", + .of_compatible = DRV_OF_COMPAT(zynqmp_clock_of_match), +}; +postcore_platform_driver(zynqmp_clock_driver); |