diff --git a/arch/arm/dts/zynqmp-clk.dtsi b/arch/arm/dts/zynqmp-clk.dtsi new file mode 100644 index 0000000..8d5ec37 --- /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 + */ + +#include + +&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 8c467ee..c03112d 100644 --- a/arch/arm/dts/zynqmp-zcu104-revA.dts +++ b/arch/arm/dts/zynqmp-zcu104-revA.dts @@ -8,3 +8,5 @@ */ #include +#include "zynqmp.dtsi" +#include "zynqmp-clk.dtsi" diff --git a/arch/arm/dts/zynqmp.dtsi b/arch/arm/dts/zynqmp.dtsi new file mode 100644 index 0000000..59984ee --- /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 + */ + +/ { + firmware { + zynqmp_firmware: zynqmp-firmware { + compatible = "xlnx,zynqmp-firmware"; + method = "smc"; + }; + }; +}; diff --git a/arch/arm/mach-zynqmp/firmware-zynqmp.c b/arch/arm/mach-zynqmp/firmware-zynqmp.c index a3ee992..f2187e9 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 @@ -577,6 +575,7 @@ 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; diff --git a/arch/arm/mach-zynqmp/include/mach/firmware-zynqmp.h b/arch/arm/mach-zynqmp/include/mach/firmware-zynqmp.h index 7a65f78..9e7a2e3 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, diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 5f28329..1bd5f90 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -9,6 +9,7 @@ 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 0000000..9432cd3 --- /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 0000000..2fe65b5 --- /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 + * + * Based on the Linux driver in drivers/clk/zynqmp/ + * + * Copyright (C) 2016-2018 Xilinx + */ + +#include +#include +#include + +#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 0000000..6f03357 --- /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 + * + * Based on the Linux driver in drivers/clk/zynqmp/ + * + * Copyright (C) 2016-2018 Xilinx + */ + +#include +#include +#include + +#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 0000000..4003267 --- /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 + * + * Based on the Linux driver in drivers/clk/zynqmp/ + * + * Copyright (C) 2016-2018 Xilinx + */ + +#include +#include +#include + +#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 0000000..e4b759b --- /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 + * + * Based on the Linux driver in drivers/clk/zynqmp/ + * + * Copyright (C) 2016-2018 Xilinx + */ + +#include +#include +#include + +#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 0000000..eeee9d2 --- /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 0000000..366a12e --- /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 + * + * Based on the Linux driver in drivers/clk/zynqmp/ + * + * Copyright (C) 2016-2018 Xilinx + */ + +#include +#include +#include +#include +#include +#include + +#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); diff --git a/drivers/of/base.c b/drivers/of/base.c index 318ba72..838f530 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1979,7 +1979,7 @@ int of_probe(void) { - struct device_node *memory; + struct device_node *memory, *firmware; if(!root_node) return -ENODEV; @@ -1996,6 +1996,10 @@ 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);