* [PATCH 0/6] ZynqMP clock driver
@ 2019-03-12 10:20 Michael Tretter
2019-03-12 10:20 ` [PATCH 1/6] of: populate "/firmware" while populating device tree Michael Tretter
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Michael Tretter @ 2019-03-12 10:20 UTC (permalink / raw)
To: barebox; +Cc: Michael Tretter
Hello,
This series adds a clock driver for the ZynqMP. On ZynqMP, Barebox does not
have direct access to the hardware but needs to go through the firmware to
configure the clock tree.
Patches 1 and 2 update how firmware device tree nodes are populated. The
behavior for this is copied from Linux.
Patch 3 provides the size of the return value from the ZynqMP firmware to
firmware client drivers. This is necessary to use the "query" call.
Patch 4 adds the actual driver.
Patches 5 and 6 add the firmware to the device tree and connect various
devices to the clock controller.
Michael
Michael Tretter (3):
ARM: zynqmp: move PAYLOAD_ARG_CNT to firmware header
clk: add ZynqMP clock driver
ARM: zynqmp: switch to firmware clock driver
Thomas Haemmerle (3):
of: populate "/firmware" while populating device tree
ARM: zynqmp: populate zynqmp_firmware dt node
ARM: zynqmp: add firmware DT node
arch/arm/dts/zynqmp-clk.dtsi | 155 +++++
arch/arm/dts/zynqmp-zcu104-revA.dts | 2 +
arch/arm/dts/zynqmp.dtsi | 17 +
arch/arm/mach-zynqmp/firmware-zynqmp.c | 3 +-
.../include/mach/firmware-zynqmp.h | 2 +
drivers/clk/Makefile | 1 +
drivers/clk/zynqmp/Makefile | 5 +
drivers/clk/zynqmp/clk-divider-zynqmp.c | 111 ++++
drivers/clk/zynqmp/clk-gate-zynqmp.c | 93 +++
drivers/clk/zynqmp/clk-mux-zynqmp.c | 102 +++
drivers/clk/zynqmp/clk-pll-zynqmp.c | 213 +++++++
drivers/clk/zynqmp/clk-zynqmp.h | 55 ++
drivers/clk/zynqmp/clkc.c | 582 ++++++++++++++++++
drivers/of/base.c | 6 +-
14 files changed, 1344 insertions(+), 3 deletions(-)
create mode 100644 arch/arm/dts/zynqmp-clk.dtsi
create mode 100644 arch/arm/dts/zynqmp.dtsi
create mode 100644 drivers/clk/zynqmp/Makefile
create mode 100644 drivers/clk/zynqmp/clk-divider-zynqmp.c
create mode 100644 drivers/clk/zynqmp/clk-gate-zynqmp.c
create mode 100644 drivers/clk/zynqmp/clk-mux-zynqmp.c
create mode 100644 drivers/clk/zynqmp/clk-pll-zynqmp.c
create mode 100644 drivers/clk/zynqmp/clk-zynqmp.h
create mode 100644 drivers/clk/zynqmp/clkc.c
--
2.20.1
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/6] of: populate "/firmware" while populating device tree
2019-03-12 10:20 [PATCH 0/6] ZynqMP clock driver Michael Tretter
@ 2019-03-12 10:20 ` Michael Tretter
2019-03-12 10:20 ` [PATCH 2/6] ARM: zynqmp: populate zynqmp_firmware dt node Michael Tretter
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Michael Tretter @ 2019-03-12 10:20 UTC (permalink / raw)
To: barebox; +Cc: Thomas Haemmerle, Michael Tretter
From: Thomas Haemmerle <thomas.haemmerle@wolfvision.net>
The sub-nodes of "/firmware" are not populated, since it has no
"compatible" property. Copy the behavior of Linux and call
of_platform_populate() on the "/firmware" node to probe firmware
drivers.
Signed-off-by: Thomas Haemmerle <thomas.haemmerle@wolfvision.net>
Signed-off-by: Michael Tretter <m.tretter@pengutronix.de>
---
drivers/of/base.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/of/base.c b/drivers/of/base.c
index b082f0c656..85c7953370 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -1979,7 +1979,7 @@ const struct of_device_id of_default_bus_match_table[] = {
int of_probe(void)
{
- struct device_node *memory;
+ struct device_node *memory, *firmware;
if(!root_node)
return -ENODEV;
@@ -1996,6 +1996,10 @@ int of_probe(void)
if (memory)
of_add_memory(memory, false);
+ firmware = of_find_node_by_path("/firmware");
+ if (firmware)
+ of_platform_populate(firmware, NULL, NULL);
+
of_clk_init(root_node, NULL);
of_platform_populate(root_node, of_default_bus_match_table, NULL);
--
2.20.1
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/6] ARM: zynqmp: populate zynqmp_firmware dt node
2019-03-12 10:20 [PATCH 0/6] ZynqMP clock driver Michael Tretter
2019-03-12 10:20 ` [PATCH 1/6] of: populate "/firmware" while populating device tree Michael Tretter
@ 2019-03-12 10:20 ` Michael Tretter
2019-03-12 10:20 ` [PATCH 3/6] ARM: zynqmp: move PAYLOAD_ARG_CNT to firmware header Michael Tretter
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Michael Tretter @ 2019-03-12 10:20 UTC (permalink / raw)
To: barebox; +Cc: Thomas Haemmerle, Michael Tretter
From: Thomas Haemmerle <thomas.haemmerle@wolfvision.net>
The zynqmp_firmware node has sub-nodes for the various APIs to expose
the platform management, as e.g. clock management. Therefore, the driver
must populate the subnodes to initialize these drivers.
Signed-off-by: Thomas Haemmerle <thomas.haemmerle@wolfvision.net>
Signed-off-by: Michael Tretter <m.tretter@pengutronix.de>
---
arch/arm/mach-zynqmp/firmware-zynqmp.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm/mach-zynqmp/firmware-zynqmp.c b/arch/arm/mach-zynqmp/firmware-zynqmp.c
index a3ee992832..d7e2a66d0f 100644
--- a/arch/arm/mach-zynqmp/firmware-zynqmp.c
+++ b/arch/arm/mach-zynqmp/firmware-zynqmp.c
@@ -577,6 +577,7 @@ static int zynqmp_firmware_probe(struct device_d *dev)
dev_dbg(dev, "Trustzone version v%d.%d\n",
pm_tz_version >> 16, pm_tz_version & 0xFFFF);
+ of_platform_populate(dev->device_node, NULL, dev);
out:
if (ret)
do_fw_call = do_fw_call_fail;
--
2.20.1
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 3/6] ARM: zynqmp: move PAYLOAD_ARG_CNT to firmware header
2019-03-12 10:20 [PATCH 0/6] ZynqMP clock driver Michael Tretter
2019-03-12 10:20 ` [PATCH 1/6] of: populate "/firmware" while populating device tree Michael Tretter
2019-03-12 10:20 ` [PATCH 2/6] ARM: zynqmp: populate zynqmp_firmware dt node Michael Tretter
@ 2019-03-12 10:20 ` Michael Tretter
2019-03-12 10:20 ` [PATCH 4/6] clk: add ZynqMP clock driver Michael Tretter
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Michael Tretter @ 2019-03-12 10:20 UTC (permalink / raw)
To: barebox; +Cc: Michael Tretter
In order to use the query() call, the users of the firmware driver need
to know the number of arguments.
Signed-off-by: Michael Tretter <m.tretter@pengutronix.de>
---
arch/arm/mach-zynqmp/firmware-zynqmp.c | 2 --
arch/arm/mach-zynqmp/include/mach/firmware-zynqmp.h | 2 ++
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm/mach-zynqmp/firmware-zynqmp.c b/arch/arm/mach-zynqmp/firmware-zynqmp.c
index d7e2a66d0f..f2187e97be 100644
--- a/arch/arm/mach-zynqmp/firmware-zynqmp.c
+++ b/arch/arm/mach-zynqmp/firmware-zynqmp.c
@@ -30,8 +30,6 @@
#define ZYNQMP_TZ_VERSION ((ZYNQMP_TZ_VERSION_MAJOR << 16) | \
ZYNQMP_TZ_VERSION_MINOR)
-#define PAYLOAD_ARG_CNT 4
-
/* SMC SIP service Call Function Identifier Prefix */
#define PM_SIP_SVC 0xC2000000
diff --git a/arch/arm/mach-zynqmp/include/mach/firmware-zynqmp.h b/arch/arm/mach-zynqmp/include/mach/firmware-zynqmp.h
index 7a65f781fb..9e7a2e34f7 100644
--- a/arch/arm/mach-zynqmp/include/mach/firmware-zynqmp.h
+++ b/arch/arm/mach-zynqmp/include/mach/firmware-zynqmp.h
@@ -15,6 +15,8 @@
#ifndef FIRMWARE_ZYNQMP_H_
#define FIRMWARE_ZYNQMP_H_
+#define PAYLOAD_ARG_CNT 4
+
enum pm_ioctl_id {
IOCTL_SET_PLL_FRAC_MODE = 8,
IOCTL_GET_PLL_FRAC_MODE,
--
2.20.1
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 4/6] clk: add ZynqMP clock driver
2019-03-12 10:20 [PATCH 0/6] ZynqMP clock driver Michael Tretter
` (2 preceding siblings ...)
2019-03-12 10:20 ` [PATCH 3/6] ARM: zynqmp: move PAYLOAD_ARG_CNT to firmware header Michael Tretter
@ 2019-03-12 10:20 ` Michael Tretter
2019-03-12 10:20 ` [PATCH 5/6] ARM: zynqmp: add firmware DT node Michael Tretter
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Michael Tretter @ 2019-03-12 10:20 UTC (permalink / raw)
To: barebox; +Cc: Michael Tretter
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>
---
drivers/clk/Makefile | 1 +
drivers/clk/zynqmp/Makefile | 5 +
drivers/clk/zynqmp/clk-divider-zynqmp.c | 111 +++++
drivers/clk/zynqmp/clk-gate-zynqmp.c | 93 ++++
drivers/clk/zynqmp/clk-mux-zynqmp.c | 102 +++++
drivers/clk/zynqmp/clk-pll-zynqmp.c | 213 +++++++++
drivers/clk/zynqmp/clk-zynqmp.h | 55 +++
drivers/clk/zynqmp/clkc.c | 582 ++++++++++++++++++++++++
8 files changed, 1162 insertions(+)
create mode 100644 drivers/clk/zynqmp/Makefile
create mode 100644 drivers/clk/zynqmp/clk-divider-zynqmp.c
create mode 100644 drivers/clk/zynqmp/clk-gate-zynqmp.c
create mode 100644 drivers/clk/zynqmp/clk-mux-zynqmp.c
create mode 100644 drivers/clk/zynqmp/clk-pll-zynqmp.c
create mode 100644 drivers/clk/zynqmp/clk-zynqmp.h
create mode 100644 drivers/clk/zynqmp/clkc.c
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 5bf76a4535..4ef41d6fbd 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_ARCH_MVEBU) += mvebu/
obj-$(CONFIG_ARCH_MXS) += mxs/
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
obj-$(CONFIG_ARCH_TEGRA) += tegra/
+obj-$(CONFIG_ARCH_ZYNQMP) += zynqmp/
obj-$(CONFIG_CLK_SOCFPGA) += socfpga/
obj-$(CONFIG_SOC_QCA_AR9331) += clk-ar933x.o
obj-$(CONFIG_SOC_QCA_AR9344) += clk-ar9344.o
diff --git a/drivers/clk/zynqmp/Makefile b/drivers/clk/zynqmp/Makefile
new file mode 100644
index 0000000000..9432cd3980
--- /dev/null
+++ b/drivers/clk/zynqmp/Makefile
@@ -0,0 +1,5 @@
+obj-y += clkc.o
+obj-y += clk-pll-zynqmp.o
+obj-y += clk-gate-zynqmp.o
+obj-y += clk-divider-zynqmp.o
+obj-y += clk-mux-zynqmp.o
diff --git a/drivers/clk/zynqmp/clk-divider-zynqmp.c b/drivers/clk/zynqmp/clk-divider-zynqmp.c
new file mode 100644
index 0000000000..2fe65b566a
--- /dev/null
+++ b/drivers/clk/zynqmp/clk-divider-zynqmp.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Zynq UltraScale+ MPSoC Clock Divider
+ *
+ * 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 <linux/clk.h>
+#include <mach/firmware-zynqmp.h>
+
+#include "clk-zynqmp.h"
+
+struct zynqmp_clk_divider {
+ struct clk clk;
+ unsigned int clk_id;
+ enum topology_type type;
+ const char *parent;
+ const struct zynqmp_eemi_ops *ops;
+};
+#define to_zynqmp_clk_divider(clk) \
+ container_of(clk, struct zynqmp_clk_divider, clk)
+
+static int zynqmp_clk_divider_bestdiv(unsigned long rate,
+ unsigned long *best_parent_rate)
+{
+ return DIV_ROUND_CLOSEST(*best_parent_rate, rate);
+}
+
+static unsigned long zynqmp_clk_divider_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct zynqmp_clk_divider *div = to_zynqmp_clk_divider(clk);
+ u32 value;
+
+ div->ops->clock_getdivider(div->clk_id, &value);
+ if (div->type == TYPE_DIV1)
+ value = value & 0xFFFF;
+ else
+ value = value >> 16;
+
+ return DIV_ROUND_UP(parent_rate, value);
+}
+
+static long zynqmp_clk_divider_round_rate(struct clk *clk, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ int bestdiv;
+
+ bestdiv = zynqmp_clk_divider_bestdiv(rate, parent_rate);
+
+ return *parent_rate / bestdiv;
+}
+
+static int zynqmp_clk_divider_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct zynqmp_clk_divider *div = to_zynqmp_clk_divider(clk);
+ u32 bestdiv;
+
+ bestdiv = zynqmp_clk_divider_bestdiv(rate, &parent_rate);
+ if (div->type == TYPE_DIV1)
+ bestdiv = (0xffff << 16) | (bestdiv & 0xffff);
+ else
+ bestdiv = (bestdiv << 16) | 0xffff;
+
+ return div->ops->clock_setdivider(div->clk_id, bestdiv);
+}
+
+static const struct clk_ops zynqmp_clk_divider_ops = {
+ .recalc_rate = zynqmp_clk_divider_recalc_rate,
+ .round_rate = zynqmp_clk_divider_round_rate,
+ .set_rate = zynqmp_clk_divider_set_rate,
+};
+
+struct clk *zynqmp_clk_register_divider(const char *name,
+ unsigned int clk_id,
+ const char **parents,
+ unsigned int num_parents,
+ const struct clock_topology *nodes)
+{
+ struct zynqmp_clk_divider *div;
+ int ret;
+
+ div = kzalloc(sizeof(*div), GFP_KERNEL);
+ if (!div)
+ return ERR_PTR(-ENOMEM);
+
+ div->clk_id = clk_id;
+ div->type = nodes->type;
+ div->ops = zynqmp_pm_get_eemi_ops();
+ div->parent = strdup(parents[0]);
+
+ div->clk.name = strdup(name);
+ div->clk.ops = &zynqmp_clk_divider_ops;
+ div->clk.flags = nodes->flag;
+ div->clk.parent_names = &div->parent;
+ div->clk.num_parents = 1;
+
+ ret = clk_register(&div->clk);
+ if (ret) {
+ kfree(div);
+ return ERR_PTR(ret);
+ }
+
+ return &div->clk;
+}
diff --git a/drivers/clk/zynqmp/clk-gate-zynqmp.c b/drivers/clk/zynqmp/clk-gate-zynqmp.c
new file mode 100644
index 0000000000..6f03357768
--- /dev/null
+++ b/drivers/clk/zynqmp/clk-gate-zynqmp.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Zynq UltraScale+ MPSoC Clock Gate
+ *
+ * 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 <linux/clk.h>
+#include <mach/firmware-zynqmp.h>
+
+#include "clk-zynqmp.h"
+
+struct zynqmp_clk_gate {
+ struct clk clk;
+ unsigned int clk_id;
+ const char *parent;
+ const struct zynqmp_eemi_ops *ops;
+};
+
+#define to_zynqmp_clk_gate(_hw) container_of(_hw, struct zynqmp_clk_gate, clk)
+
+static int zynqmp_clk_gate_enable(struct clk *clk)
+{
+ struct zynqmp_clk_gate *gate = to_zynqmp_clk_gate(clk);
+
+ return gate->ops->clock_enable(gate->clk_id);
+}
+
+static void zynqmp_clk_gate_disable(struct clk *clk)
+{
+ struct zynqmp_clk_gate *gate = to_zynqmp_clk_gate(clk);
+
+ gate->ops->clock_disable(gate->clk_id);
+}
+
+static int zynqmp_clk_gate_is_enabled(struct clk *clk)
+{
+ struct zynqmp_clk_gate *gate = to_zynqmp_clk_gate(clk);
+ u32 state;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ ret = eemi_ops->clock_getstate(gate->clk_id, &state);
+ if (ret)
+ return -EIO;
+
+ return !!state;
+}
+
+static const struct clk_ops zynqmp_clk_gate_ops = {
+ .set_rate = clk_parent_set_rate,
+ .round_rate = clk_parent_round_rate,
+ .enable = zynqmp_clk_gate_enable,
+ .disable = zynqmp_clk_gate_disable,
+ .is_enabled = zynqmp_clk_gate_is_enabled,
+};
+
+struct clk *zynqmp_clk_register_gate(const char *name,
+ unsigned int clk_id,
+ const char **parents,
+ unsigned int num_parents,
+ const struct clock_topology *nodes)
+{
+ struct zynqmp_clk_gate *gate;
+ int ret;
+
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ return ERR_PTR(-ENOMEM);
+
+ gate->clk_id = clk_id;
+ gate->ops = zynqmp_pm_get_eemi_ops();
+ gate->parent = strdup(parents[0]);
+
+ gate->clk.name = strdup(name);
+ gate->clk.ops = &zynqmp_clk_gate_ops;
+ gate->clk.flags = nodes->flag | CLK_SET_RATE_PARENT;
+ gate->clk.parent_names = &gate->parent;
+ gate->clk.num_parents = 1;
+
+ ret = clk_register(&gate->clk);
+ if (ret) {
+ kfree(gate);
+ return ERR_PTR(ret);
+ }
+
+ return &gate->clk;
+}
diff --git a/drivers/clk/zynqmp/clk-mux-zynqmp.c b/drivers/clk/zynqmp/clk-mux-zynqmp.c
new file mode 100644
index 0000000000..4003267bd1
--- /dev/null
+++ b/drivers/clk/zynqmp/clk-mux-zynqmp.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Zynq UltraScale+ MPSoC Clock Multiplexer
+ *
+ * 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 <linux/clk.h>
+#include <mach/firmware-zynqmp.h>
+
+#include "clk-zynqmp.h"
+
+#define CLK_MUX_READ_ONLY BIT(3)
+
+struct zynqmp_clk_mux {
+ struct clk clk;
+ u32 clk_id;
+ const struct zynqmp_eemi_ops *ops;
+};
+
+#define to_zynqmp_clk_mux(clk) \
+ container_of(clk, struct zynqmp_clk_mux, clk)
+
+static int zynqmp_clk_mux_get_parent(struct clk *clk)
+{
+ struct zynqmp_clk_mux *mux = to_zynqmp_clk_mux(clk);
+ u32 value;
+
+ mux->ops->clock_getparent(mux->clk_id, &value);
+
+ return value;
+}
+
+static int zynqmp_clk_mux_set_parent(struct clk *clk, u8 index)
+{
+ struct zynqmp_clk_mux *mux = to_zynqmp_clk_mux(clk);
+
+ return mux->ops->clock_setparent(mux->clk_id, index);
+}
+
+static const struct clk_ops zynqmp_clk_mux_ops = {
+ .set_rate = clk_parent_set_rate,
+ .round_rate = clk_parent_round_rate,
+ .get_parent = zynqmp_clk_mux_get_parent,
+ .set_parent = zynqmp_clk_mux_set_parent,
+};
+
+static const struct clk_ops zynqmp_clk_mux_ro_ops = {
+ .set_rate = clk_parent_set_rate,
+ .round_rate = clk_parent_round_rate,
+ .get_parent = zynqmp_clk_mux_get_parent,
+};
+
+struct clk *zynqmp_clk_register_mux(const char *name,
+ unsigned int clk_id,
+ const char **parents,
+ unsigned int num_parents,
+ const struct clock_topology *nodes)
+{
+ struct zynqmp_clk_mux *mux;
+ int ret;
+ int i;
+ const char **parent_names;
+
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return ERR_PTR(-ENOMEM);
+
+ parent_names = kcalloc(num_parents, sizeof(*parent_names), GFP_KERNEL);
+ if (!parent_names) {
+ kfree(mux);
+ return ERR_PTR(-ENOMEM);
+ }
+ for (i = 0; i < num_parents; i++)
+ parent_names[i] = strdup(parents[i]);
+
+ mux->clk_id = clk_id;
+ mux->ops = zynqmp_pm_get_eemi_ops();
+
+ mux->clk.name = strdup(name);
+ if (nodes->type_flag & CLK_MUX_READ_ONLY)
+ mux->clk.ops = &zynqmp_clk_mux_ro_ops;
+ else
+ mux->clk.ops = &zynqmp_clk_mux_ops;
+ mux->clk.flags = nodes->flag | CLK_SET_RATE_PARENT;
+ mux->clk.parent_names = parent_names;
+ mux->clk.num_parents = num_parents;
+
+ ret = clk_register(&mux->clk);
+ if (ret) {
+ kfree(parent_names);
+ kfree(mux);
+ return ERR_PTR(ret);
+ }
+
+ return &mux->clk;
+}
diff --git a/drivers/clk/zynqmp/clk-pll-zynqmp.c b/drivers/clk/zynqmp/clk-pll-zynqmp.c
new file mode 100644
index 0000000000..e4b759b73c
--- /dev/null
+++ b/drivers/clk/zynqmp/clk-pll-zynqmp.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Zynq UltraScale+ MPSoC PLL Clock
+ *
+ * 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 <linux/clk.h>
+#include <mach/firmware-zynqmp.h>
+
+#include "clk-zynqmp.h"
+
+struct zynqmp_pll {
+ struct clk clk;
+ unsigned int clk_id;
+ const char *parent;
+ const struct zynqmp_eemi_ops *ops;
+};
+
+#define to_zynqmp_pll(clk) \
+ container_of(clk, struct zynqmp_pll, clk)
+
+#define PLL_FBDIV_MIN 25
+#define PLL_FBDIV_MAX 125
+
+#define PS_PLL_VCO_MIN 1500000000
+#define PS_PLL_VCO_MAX 3000000000UL
+
+enum pll_mode {
+ PLL_MODE_INT,
+ PLL_MODE_FRAC,
+};
+
+#define FRAC_DIV (1 << 16)
+
+static inline enum pll_mode zynqmp_pll_get_mode(struct zynqmp_pll *pll)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+
+ pll->ops->ioctl(0, IOCTL_GET_PLL_FRAC_MODE, pll->clk_id, 0,
+ ret_payload);
+
+ return ret_payload[1];
+}
+
+static inline void zynqmp_pll_set_mode(struct zynqmp_pll *pll, enum pll_mode mode)
+{
+ pll->ops->ioctl(0, IOCTL_SET_PLL_FRAC_MODE, pll->clk_id, mode, NULL);
+}
+
+static long zynqmp_pll_round_rate(struct clk *clk, unsigned long rate,
+ unsigned long *prate)
+{
+ struct zynqmp_pll *pll = to_zynqmp_pll(clk);
+ u32 fbdiv;
+ long rate_div;
+
+ rate_div = (rate * FRAC_DIV) / *prate;
+ if (rate_div % FRAC_DIV)
+ zynqmp_pll_set_mode(pll, PLL_MODE_FRAC);
+ else
+ zynqmp_pll_set_mode(pll, PLL_MODE_INT);
+
+ if (zynqmp_pll_get_mode(pll) == PLL_MODE_FRAC) {
+ if (rate > PS_PLL_VCO_MAX) {
+ fbdiv = rate / PS_PLL_VCO_MAX;
+ rate = rate / (fbdiv + 1);
+ }
+ if (rate < PS_PLL_VCO_MIN) {
+ fbdiv = DIV_ROUND_UP(PS_PLL_VCO_MIN, rate);
+ rate = rate * fbdiv;
+ }
+ } else {
+ fbdiv = DIV_ROUND_CLOSEST(rate, *prate);
+ fbdiv = clamp_t(u32, fbdiv, PLL_FBDIV_MIN, PLL_FBDIV_MAX);
+ rate = *prate * fbdiv;
+ }
+
+ return rate;
+}
+
+static unsigned long zynqmp_pll_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct zynqmp_pll *pll = to_zynqmp_pll(clk);
+ u32 clk_id = pll->clk_id;
+ u32 fbdiv, data;
+ unsigned long rate, frac;
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ ret = pll->ops->clock_getdivider(clk_id, &fbdiv);
+
+ rate = parent_rate * fbdiv;
+
+ if (zynqmp_pll_get_mode(pll) == PLL_MODE_FRAC) {
+ pll->ops->ioctl(0, IOCTL_GET_PLL_FRAC_DATA, clk_id, 0,
+ ret_payload);
+ data = ret_payload[1];
+ frac = (parent_rate * data) / FRAC_DIV;
+ rate = rate + frac;
+ }
+
+ return rate;
+}
+
+static int zynqmp_pll_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct zynqmp_pll *pll = to_zynqmp_pll(clk);
+ u32 clk_id = pll->clk_id;
+ u32 fbdiv;
+ long rate_div, frac, m, f;
+
+ if (zynqmp_pll_get_mode(pll) == PLL_MODE_FRAC) {
+ rate_div = (rate * FRAC_DIV) / parent_rate;
+ m = rate_div / FRAC_DIV;
+ f = rate_div % FRAC_DIV;
+ m = clamp_t(u32, m, (PLL_FBDIV_MIN), (PLL_FBDIV_MAX));
+ rate = parent_rate * m;
+ frac = (parent_rate * f) / FRAC_DIV;
+
+ pll->ops->clock_setdivider(clk_id, m);
+ pll->ops->ioctl(0, IOCTL_SET_PLL_FRAC_DATA, clk_id, f, NULL);
+
+ return rate + frac;
+ } else {
+ fbdiv = DIV_ROUND_CLOSEST(rate, parent_rate);
+ fbdiv = clamp_t(u32, fbdiv, PLL_FBDIV_MIN, PLL_FBDIV_MAX);
+ pll->ops->clock_setdivider(clk_id, fbdiv);
+
+ return parent_rate * fbdiv;
+ }
+}
+
+static int zynqmp_pll_is_enabled(struct clk *clk)
+{
+ struct zynqmp_pll *pll = to_zynqmp_pll(clk);
+ u32 is_enabled;
+ int ret;
+
+ ret = pll->ops->clock_getstate(pll->clk_id, &is_enabled);
+ if (ret)
+ return -EIO;
+
+ return !!(is_enabled);
+}
+
+static int zynqmp_pll_enable(struct clk *clk)
+{
+ struct zynqmp_pll *pll = to_zynqmp_pll(clk);
+
+ if (zynqmp_pll_is_enabled(clk))
+ return 0;
+
+ return pll->ops->clock_enable(pll->clk_id);
+}
+
+static void zynqmp_pll_disable(struct clk *clk)
+{
+ struct zynqmp_pll *pll = to_zynqmp_pll(clk);
+
+ if (!zynqmp_pll_is_enabled(clk))
+ return;
+
+ pll->ops->clock_disable(pll->clk_id);
+}
+
+static const struct clk_ops zynqmp_pll_ops = {
+ .enable = zynqmp_pll_enable,
+ .disable = zynqmp_pll_disable,
+ .is_enabled = zynqmp_pll_is_enabled,
+ .round_rate = zynqmp_pll_round_rate,
+ .recalc_rate = zynqmp_pll_recalc_rate,
+ .set_rate = zynqmp_pll_set_rate,
+};
+
+struct clk *zynqmp_clk_register_pll(const char *name,
+ unsigned int clk_id,
+ const char **parents,
+ unsigned int num_parents,
+ const struct clock_topology *nodes)
+{
+ struct zynqmp_pll *pll;
+ int ret;
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->clk_id = clk_id;
+ pll->ops = zynqmp_pm_get_eemi_ops();
+ pll->parent = strdup(parents[0]);
+
+ pll->clk.name = strdup(name);
+ pll->clk.ops = &zynqmp_pll_ops;
+ pll->clk.flags = nodes->flag | CLK_SET_RATE_PARENT;
+ pll->clk.parent_names = &pll->parent;
+ pll->clk.num_parents = 1;
+
+ ret = clk_register(&pll->clk);
+ if (ret) {
+ kfree(pll);
+ return ERR_PTR(ret);
+ }
+
+ return &pll->clk;
+}
diff --git a/drivers/clk/zynqmp/clk-zynqmp.h b/drivers/clk/zynqmp/clk-zynqmp.h
new file mode 100644
index 0000000000..eeee9d2b5a
--- /dev/null
+++ b/drivers/clk/zynqmp/clk-zynqmp.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2016-2018 Xilinx
+ */
+
+#ifndef __LINUX_CLK_ZYNQMP_H_
+#define __LINUX_CLK_ZYNQMP_H_
+
+enum topology_type {
+ TYPE_INVALID,
+ TYPE_MUX,
+ TYPE_PLL,
+ TYPE_FIXEDFACTOR,
+ TYPE_DIV1,
+ TYPE_DIV2,
+ TYPE_GATE,
+};
+
+struct clock_topology {
+ enum topology_type type;
+ u32 flag;
+ u32 type_flag;
+};
+
+struct clk *zynqmp_clk_register_pll(const char *name,
+ unsigned int clk_id,
+ const char **parents,
+ unsigned int num_parents,
+ const struct clock_topology *node);
+
+struct clk *zynqmp_clk_register_gate(const char *name,
+ unsigned int clk_id,
+ const char **parents,
+ unsigned int num_parents,
+ const struct clock_topology *node);
+
+struct clk *zynqmp_clk_register_divider(const char *name,
+ unsigned int clk_id,
+ const char **parents,
+ unsigned int num_parents,
+ const struct clock_topology *node);
+
+struct clk *zynqmp_clk_register_mux(const char *name,
+ unsigned int clk_id,
+ const char **parents,
+ unsigned int num_parents,
+ const struct clock_topology *node);
+
+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 *node);
+
+#endif
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);
--
2.20.1
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 5/6] ARM: zynqmp: add firmware DT node
2019-03-12 10:20 [PATCH 0/6] ZynqMP clock driver Michael Tretter
` (3 preceding siblings ...)
2019-03-12 10:20 ` [PATCH 4/6] clk: add ZynqMP clock driver Michael Tretter
@ 2019-03-12 10:20 ` Michael Tretter
2019-03-12 10:20 ` [PATCH 6/6] ARM: zynqmp: switch to firmware clock driver Michael Tretter
2019-03-18 7:50 ` [PATCH 0/6] ZynqMP " Sascha Hauer
6 siblings, 0 replies; 8+ messages in thread
From: Michael Tretter @ 2019-03-12 10:20 UTC (permalink / raw)
To: barebox; +Cc: Thomas Haemmerle, Michael Tretter
From: Thomas Haemmerle <thomas.haemmerle@wolfvision.net>
Add firmware DT node in ZynqMP device tree. This node uses bindings as
per new firmware interface driver.
Signed-off-by: Thomas Haemmerle <thomas.haemmerle@wolfvision.net>
Signed-off-by: Michael Tretter <m.tretter@pengutronix.de>
---
arch/arm/dts/zynqmp-zcu104-revA.dts | 1 +
arch/arm/dts/zynqmp.dtsi | 17 +++++++++++++++++
2 files changed, 18 insertions(+)
create mode 100644 arch/arm/dts/zynqmp.dtsi
diff --git a/arch/arm/dts/zynqmp-zcu104-revA.dts b/arch/arm/dts/zynqmp-zcu104-revA.dts
index 8c467ee970..88f5152c9d 100644
--- a/arch/arm/dts/zynqmp-zcu104-revA.dts
+++ b/arch/arm/dts/zynqmp-zcu104-revA.dts
@@ -8,3 +8,4 @@
*/
#include <arm64/xilinx/zynqmp-zcu104-revA.dts>
+#include "zynqmp.dtsi"
diff --git a/arch/arm/dts/zynqmp.dtsi b/arch/arm/dts/zynqmp.dtsi
new file mode 100644
index 0000000000..59984ee758
--- /dev/null
+++ b/arch/arm/dts/zynqmp.dtsi
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * dts file for Xilinx ZynqMP
+ *
+ * (C) Copyright 2014 - 2015, Xilinx, Inc.
+ *
+ * Michal Simek <michal.simek@xilinx.com>
+ */
+
+/ {
+ firmware {
+ zynqmp_firmware: zynqmp-firmware {
+ compatible = "xlnx,zynqmp-firmware";
+ method = "smc";
+ };
+ };
+};
--
2.20.1
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 6/6] ARM: zynqmp: switch to firmware clock driver
2019-03-12 10:20 [PATCH 0/6] ZynqMP clock driver Michael Tretter
` (4 preceding siblings ...)
2019-03-12 10:20 ` [PATCH 5/6] ARM: zynqmp: add firmware DT node Michael Tretter
@ 2019-03-12 10:20 ` Michael Tretter
2019-03-18 7:50 ` [PATCH 0/6] ZynqMP " Sascha Hauer
6 siblings, 0 replies; 8+ messages in thread
From: Michael Tretter @ 2019-03-12 10:20 UTC (permalink / raw)
To: barebox; +Cc: Michael Tretter
In the device tree, the clock controller is a subnode of the firmware
node. Devices refer to the clocks by an id that is shared between the
ATF and the driver.
While the bindings for the clock controller are already upstream, the
device in mainline Linux does not use them, yet. Add them in the Barebox
device tree for now.
Signed-off-by: Michael Tretter <m.tretter@pengutronix.de>
---
arch/arm/dts/zynqmp-clk.dtsi | 155 ++++++++++++++++++++++++++++
arch/arm/dts/zynqmp-zcu104-revA.dts | 1 +
2 files changed, 156 insertions(+)
create mode 100644 arch/arm/dts/zynqmp-clk.dtsi
diff --git a/arch/arm/dts/zynqmp-clk.dtsi b/arch/arm/dts/zynqmp-clk.dtsi
new file mode 100644
index 0000000000..8d5ec37125
--- /dev/null
+++ b/arch/arm/dts/zynqmp-clk.dtsi
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Clock specification for Xilinx ZynqMP
+ *
+ * (C) Copyright 2017, Xilinx, Inc.
+ *
+ * Michal Simek <michal.simek@xilinx.com>
+ */
+
+#include <dt-bindings/clock/xlnx,zynqmp-clk.h>
+
+&zynqmp_firmware {
+ zynqmp_clk: clock-controller {
+ #clock-cells = <1>;
+ compatible = "xlnx,zynqmp-clk";
+ clocks = <&pss_ref_clk>, <&video_clk>, <&pss_alt_ref_clk>, <&aux_ref_clk>, <>_crx_ref_clk>;
+ clock-names = "pss_ref_clk", "video_clk", "pss_alt_ref_clk", "aux_ref_clk", "gt_crx_ref_clk";
+ };
+};
+
+/ {
+ pss_ref_clk: pss_ref_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <33333333>;
+ };
+
+ video_clk: video_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <27000000>;
+ };
+
+ pss_alt_ref_clk: pss_alt_ref_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <0>;
+ };
+
+ gt_crx_ref_clk: gt_crx_ref_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <108000000>;
+ };
+
+ aux_ref_clk: aux_ref_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <27000000>;
+ };
+};
+
+&can0 {
+ clocks = <&zynqmp_clk CAN0_REF>, <&zynqmp_clk LPD_LSBUS>;
+};
+
+&can1 {
+ clocks = <&zynqmp_clk CAN1_REF>, <&zynqmp_clk LPD_LSBUS>;
+};
+
+&cpu0 {
+ clocks = <&zynqmp_clk ACPU>;
+};
+
+&gem0 {
+ clocks = <&zynqmp_clk LPD_LSBUS>, <&zynqmp_clk LPD_LSBUS>, <&zynqmp_clk GEM0_TX>, <&zynqmp_clk GEM0_REF>, <&zynqmp_clk GEM_TSU>;
+ clock-names = "pclk", "hclk", "tx_clk", "rx_clk", "tsu_clk";
+};
+
+&gem1 {
+ clocks = <&zynqmp_clk LPD_LSBUS>, <&zynqmp_clk LPD_LSBUS>, <&zynqmp_clk GEM1_TX>, <&zynqmp_clk GEM1_REF>, <&zynqmp_clk GEM_TSU>;
+ clock-names = "pclk", "hclk", "tx_clk", "rx_clk", "tsu_clk";
+};
+
+&gem2 {
+ clocks = <&zynqmp_clk LPD_LSBUS>, <&zynqmp_clk LPD_LSBUS>, <&zynqmp_clk GEM2_TX>, <&zynqmp_clk GEM2_REF>, <&zynqmp_clk GEM_TSU>;
+ clock-names = "pclk", "hclk", "tx_clk", "rx_clk", "tsu_clk";
+};
+
+&gem3 {
+ clocks = <&zynqmp_clk LPD_LSBUS>, <&zynqmp_clk LPD_LSBUS>, <&zynqmp_clk GEM3_TX>, <&zynqmp_clk GEM3_REF>, <&zynqmp_clk GEM_TSU>;
+ clock-names = "pclk", "hclk", "tx_clk", "rx_clk", "tsu_clk";
+};
+
+&gpio {
+ clocks = <&zynqmp_clk LPD_LSBUS>;
+};
+
+&i2c0 {
+ clocks = <&zynqmp_clk I2C0_REF>;
+};
+
+&i2c1 {
+ clocks = <&zynqmp_clk I2C1_REF>;
+};
+
+&pcie {
+ clocks = <&zynqmp_clk PCIE_REF>;
+};
+
+&sata {
+ clocks = <&zynqmp_clk SATA_REF>;
+};
+
+&sdhci0 {
+ clocks = <&zynqmp_clk SDIO0_REF>, <&zynqmp_clk LPD_LSBUS>;
+};
+
+&sdhci1 {
+ clocks = <&zynqmp_clk SDIO1_REF>, <&zynqmp_clk LPD_LSBUS>;
+};
+
+&spi0 {
+ clocks = <&zynqmp_clk SPI0_REF>, <&zynqmp_clk LPD_LSBUS>;
+};
+
+&spi1 {
+ clocks = <&zynqmp_clk SPI0_REF>, <&zynqmp_clk LPD_LSBUS>;
+};
+
+&ttc0 {
+ clocks = <&zynqmp_clk LPD_LSBUS>;
+};
+
+&ttc1 {
+ clocks = <&zynqmp_clk LPD_LSBUS>;
+};
+
+&ttc2 {
+ clocks = <&zynqmp_clk LPD_LSBUS>;
+};
+
+&ttc3 {
+ clocks = <&zynqmp_clk LPD_LSBUS>;
+};
+
+&uart0 {
+ clocks = <&zynqmp_clk UART0_REF>, <&zynqmp_clk LPD_LSBUS>;
+};
+
+&uart1 {
+ clocks = <&zynqmp_clk UART1_REF>, <&zynqmp_clk LPD_LSBUS>;
+};
+
+&usb0 {
+ clocks = <&zynqmp_clk USB0_BUS_REF>, <&zynqmp_clk USB3_DUAL_REF>;
+};
+
+&usb1 {
+ clocks = <&zynqmp_clk USB1_BUS_REF>, <&zynqmp_clk USB3_DUAL_REF>;
+};
+
+&watchdog0 {
+ clocks = <&zynqmp_clk WDT>;
+};
diff --git a/arch/arm/dts/zynqmp-zcu104-revA.dts b/arch/arm/dts/zynqmp-zcu104-revA.dts
index 88f5152c9d..c03112d7a0 100644
--- a/arch/arm/dts/zynqmp-zcu104-revA.dts
+++ b/arch/arm/dts/zynqmp-zcu104-revA.dts
@@ -9,3 +9,4 @@
#include <arm64/xilinx/zynqmp-zcu104-revA.dts>
#include "zynqmp.dtsi"
+#include "zynqmp-clk.dtsi"
--
2.20.1
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/6] ZynqMP clock driver
2019-03-12 10:20 [PATCH 0/6] ZynqMP clock driver Michael Tretter
` (5 preceding siblings ...)
2019-03-12 10:20 ` [PATCH 6/6] ARM: zynqmp: switch to firmware clock driver Michael Tretter
@ 2019-03-18 7:50 ` Sascha Hauer
6 siblings, 0 replies; 8+ messages in thread
From: Sascha Hauer @ 2019-03-18 7:50 UTC (permalink / raw)
To: Michael Tretter; +Cc: barebox
On Tue, Mar 12, 2019 at 11:20:49AM +0100, Michael Tretter wrote:
> Hello,
>
> This series adds a clock driver for the ZynqMP. On ZynqMP, Barebox does not
> have direct access to the hardware but needs to go through the firmware to
> configure the clock tree.
>
> Patches 1 and 2 update how firmware device tree nodes are populated. The
> behavior for this is copied from Linux.
>
> Patch 3 provides the size of the return value from the ZynqMP firmware to
> firmware client drivers. This is necessary to use the "query" call.
>
> Patch 4 adds the actual driver.
>
> Patches 5 and 6 add the firmware to the device tree and connect various
> devices to the clock controller.
>
> Michael
Applied, thanks
Sascha
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2019-03-18 7:51 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-03-12 10:20 [PATCH 0/6] ZynqMP clock driver Michael Tretter
2019-03-12 10:20 ` [PATCH 1/6] of: populate "/firmware" while populating device tree Michael Tretter
2019-03-12 10:20 ` [PATCH 2/6] ARM: zynqmp: populate zynqmp_firmware dt node Michael Tretter
2019-03-12 10:20 ` [PATCH 3/6] ARM: zynqmp: move PAYLOAD_ARG_CNT to firmware header Michael Tretter
2019-03-12 10:20 ` [PATCH 4/6] clk: add ZynqMP clock driver Michael Tretter
2019-03-12 10:20 ` [PATCH 5/6] ARM: zynqmp: add firmware DT node Michael Tretter
2019-03-12 10:20 ` [PATCH 6/6] ARM: zynqmp: switch to firmware clock driver Michael Tretter
2019-03-18 7:50 ` [PATCH 0/6] ZynqMP " Sascha Hauer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox