summaryrefslogtreecommitdiffstats
path: root/drivers/clk/zynqmp/clkc.c
diff options
context:
space:
mode:
authorMichael Tretter <m.tretter@pengutronix.de>2019-03-12 11:20:53 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2019-03-18 08:50:38 +0100
commita3167da3657ae807f702f7f54a7054d756174558 (patch)
treea89d51f86b45069d97af459399fe32c582dc5c9a /drivers/clk/zynqmp/clkc.c
parentbb6ac1cfc09ae06840d162fcc8bf8d31248e3800 (diff)
downloadbarebox-a3167da3657ae807f702f7f54a7054d756174558.tar.gz
barebox-a3167da3657ae807f702f7f54a7054d756174558.tar.xz
clk: add ZynqMP clock driver
The ZynqMP has a platform management unit (PMU) that is responsible for managing the clocks. Therefore, the clock driver uses the firmware driver to control the clocks. The Barebox driver is based on the Linux driver, but contains deviations to make the driver more readable and more consistent. Signed-off-by: Michael Tretter <m.tretter@pengutronix.de> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/clk/zynqmp/clkc.c')
-rw-r--r--drivers/clk/zynqmp/clkc.c582
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);