[U-Boot] [PATCH v5.1 01/26] clk: Add Allwinner A64 CLK driver

Add initial clock driver for Allwinner A64.
Implement USB clock enable and disable functions for OHCI, EHCI, OTG and USBPHY gate and clock registers via ccu clk gate table.
Signed-off-by: Jagan Teki jagan@amarulasolutions.com Acked-by: Maxime Ripard maxime.ripard@bootlin.com --- Changes for v5.1: - s/CCU_CLK_F_INIT_DONE/CCU_CLK_F_IS_VALID - Use ccu_flags instead of ccu_clk_flags since reset flags is included
arch/arm/include/asm/arch-sunxi/ccu.h | 65 +++++++++++++++++++++++ drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/sunxi/Kconfig | 18 +++++++ drivers/clk/sunxi/Makefile | 9 ++++ drivers/clk/sunxi/clk_a64.c | 46 +++++++++++++++++ drivers/clk/sunxi/clk_sunxi.c | 74 +++++++++++++++++++++++++++ 7 files changed, 214 insertions(+) create mode 100644 arch/arm/include/asm/arch-sunxi/ccu.h create mode 100644 drivers/clk/sunxi/Kconfig create mode 100644 drivers/clk/sunxi/Makefile create mode 100644 drivers/clk/sunxi/clk_a64.c create mode 100644 drivers/clk/sunxi/clk_sunxi.c
diff --git a/arch/arm/include/asm/arch-sunxi/ccu.h b/arch/arm/include/asm/arch-sunxi/ccu.h new file mode 100644 index 0000000000..24efe0ab0a --- /dev/null +++ b/arch/arm/include/asm/arch-sunxi/ccu.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Amarula Solutions. + * Author: Jagan Teki jagan@amarulasolutions.com + */ + +#ifndef _ASM_ARCH_CCU_H +#define _ASM_ARCH_CCU_H + +/** + * enum ccu_flags - ccu clock flags + * + * @CCU_CLK_F_IS_VALID: is given clock gate is valid? + */ +enum ccu_flags { + CCU_CLK_F_IS_VALID = BIT(0), +}; + +/** + * struct ccu_clk_gate - ccu clock gate + * @off: gate offset + * @bit: gate bit + * @flags: ccu clock gate flags + */ +struct ccu_clk_gate { + u16 off; + u32 bit; + enum ccu_flags flags; +}; + +#define GATE(_off, _bit) { \ + .off = _off, \ + .bit = _bit, \ + .flags = CCU_CLK_F_IS_VALID, \ +} + +/** + * struct ccu_desc - clock control unit descriptor + * + * @gates: clock gates + */ +struct ccu_desc { + const struct ccu_clk_gate *gates; +}; + +/** + * struct ccu_priv - sunxi clock control unit + * + * @base: base address + * @desc: ccu descriptor + */ +struct ccu_priv { + void *base; + const struct ccu_desc *desc; +}; + +/** + * sunxi_clk_probe - common sunxi clock probe + * @dev: clock device + */ +int sunxi_clk_probe(struct udevice *dev); + +extern struct clk_ops sunxi_clk_ops; + +#endif /* _ASM_ARCH_CCU_H */ diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index eadf7f8250..51c931b906 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -104,6 +104,7 @@ source "drivers/clk/imx/Kconfig" source "drivers/clk/mvebu/Kconfig" source "drivers/clk/owl/Kconfig" source "drivers/clk/renesas/Kconfig" +source "drivers/clk/sunxi/Kconfig" source "drivers/clk/tegra/Kconfig" source "drivers/clk/uniphier/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 9acbb1a650..6a4ff9143b 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_CLK_HSDK) += clk-hsdk-cgu.o obj-$(CONFIG_CLK_MPC83XX) += mpc83xx_clk.o obj-$(CONFIG_CLK_OWL) += owl/ obj-$(CONFIG_CLK_RENESAS) += renesas/ +obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-$(CONFIG_CLK_STM32F) += clk_stm32f.o obj-$(CONFIG_CLK_STM32MP1) += clk_stm32mp1.o obj-$(CONFIG_CLK_UNIPHIER) += uniphier/ diff --git a/drivers/clk/sunxi/Kconfig b/drivers/clk/sunxi/Kconfig new file mode 100644 index 0000000000..bf5ecb3801 --- /dev/null +++ b/drivers/clk/sunxi/Kconfig @@ -0,0 +1,18 @@ +config CLK_SUNXI + bool "Clock support for Allwinner SoCs" + depends on CLK && ARCH_SUNXI + default y + help + This enables support for common clock driver API on Allwinner + SoCs. + +if CLK_SUNXI + +config CLK_SUN50I_A64 + bool "Clock driver for Allwinner A64" + default MACH_SUN50I + help + This enables common clock driver support for platforms based + on Allwinner A64 SoC. + +endif # CLK_SUNXI diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile new file mode 100644 index 0000000000..fb20d28333 --- /dev/null +++ b/drivers/clk/sunxi/Makefile @@ -0,0 +1,9 @@ +# +# Copyright (C) 2018 Amarula Solutions. +# +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-$(CONFIG_CLK_SUNXI) += clk_sunxi.o + +obj-$(CONFIG_CLK_SUN50I_A64) += clk_a64.o diff --git a/drivers/clk/sunxi/clk_a64.c b/drivers/clk/sunxi/clk_a64.c new file mode 100644 index 0000000000..803a2f711d --- /dev/null +++ b/drivers/clk/sunxi/clk_a64.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Amarula Solutions. + * Author: Jagan Teki jagan@amarulasolutions.com + */ + +#include <common.h> +#include <clk-uclass.h> +#include <dm.h> +#include <errno.h> +#include <asm/arch/ccu.h> +#include <dt-bindings/clock/sun50i-a64-ccu.h> + +static const struct ccu_clk_gate a64_gates[] = { + [CLK_BUS_OTG] = GATE(0x060, BIT(23)), + [CLK_BUS_EHCI0] = GATE(0x060, BIT(24)), + [CLK_BUS_EHCI1] = GATE(0x060, BIT(25)), + [CLK_BUS_OHCI0] = GATE(0x060, BIT(28)), + [CLK_BUS_OHCI1] = GATE(0x060, BIT(29)), + + [CLK_USB_PHY0] = GATE(0x0cc, BIT(8)), + [CLK_USB_PHY1] = GATE(0x0cc, BIT(9)), + [CLK_USB_HSIC] = GATE(0x0cc, BIT(10)), + [CLK_USB_HSIC_12M] = GATE(0x0cc, BIT(11)), + [CLK_USB_OHCI0] = GATE(0x0cc, BIT(16)), + [CLK_USB_OHCI1] = GATE(0x0cc, BIT(17)), +}; + +static const struct ccu_desc a64_ccu_desc = { + .gates = a64_gates, +}; + +static const struct udevice_id a64_ccu_ids[] = { + { .compatible = "allwinner,sun50i-a64-ccu", + .data = (ulong)&a64_ccu_desc }, + { } +}; + +U_BOOT_DRIVER(clk_sun50i_a64) = { + .name = "sun50i_a64_ccu", + .id = UCLASS_CLK, + .of_match = a64_ccu_ids, + .priv_auto_alloc_size = sizeof(struct ccu_priv), + .ops = &sunxi_clk_ops, + .probe = sunxi_clk_probe, +}; diff --git a/drivers/clk/sunxi/clk_sunxi.c b/drivers/clk/sunxi/clk_sunxi.c new file mode 100644 index 0000000000..62ce2994e4 --- /dev/null +++ b/drivers/clk/sunxi/clk_sunxi.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Amarula Solutions. + * Author: Jagan Teki jagan@amarulasolutions.com + */ + +#include <common.h> +#include <clk-uclass.h> +#include <dm.h> +#include <errno.h> +#include <asm/io.h> +#include <asm/arch/ccu.h> +#include <linux/log2.h> + +static const struct ccu_clk_gate *priv_to_gate(struct ccu_priv *priv, + unsigned long id) +{ + return &priv->desc->gates[id]; +} + +static int sunxi_set_gate(struct clk *clk, bool on) +{ + struct ccu_priv *priv = dev_get_priv(clk->dev); + const struct ccu_clk_gate *gate = priv_to_gate(priv, clk->id); + u32 reg; + + if (!(gate->flags & CCU_CLK_F_IS_VALID)) { + printf("%s: (CLK#%ld) unhandled\n", __func__, clk->id); + return 0; + } + + debug("%s: (CLK#%ld) off#0x%x, BIT(%d)\n", __func__, + clk->id, gate->off, ilog2(gate->bit)); + + reg = readl(priv->base + gate->off); + if (on) + reg |= gate->bit; + else + reg &= ~gate->bit; + + writel(reg, priv->base + gate->off); + + return 0; +} + +static int sunxi_clk_enable(struct clk *clk) +{ + return sunxi_set_gate(clk, true); +} + +static int sunxi_clk_disable(struct clk *clk) +{ + return sunxi_set_gate(clk, false); +} + +struct clk_ops sunxi_clk_ops = { + .enable = sunxi_clk_enable, + .disable = sunxi_clk_disable, +}; + +int sunxi_clk_probe(struct udevice *dev) +{ + struct ccu_priv *priv = dev_get_priv(dev); + + priv->base = dev_read_addr_ptr(dev); + if (!priv->base) + return -ENOMEM; + + priv->desc = (const struct ccu_desc *)dev_get_driver_data(dev); + if (!priv->desc) + return -EINVAL; + + return 0; +}

Add common reset driver for all Allwinner SoC's.
Since CLK and RESET share common DT compatible, it is CLK driver job is to bind the reset driver. So add CLK bind call on respective SoC driver by passing ccu map descriptor so-that reset deassert, deassert operations held based on ccu reset table defined from CLK driver.
Select DM_RESET via CLK_SUNXI, this make hidden section of RESET since CLK and RESET share common DT compatible and code.
Signed-off-by: Jagan Teki jagan@amarulasolutions.com Acked-by: Maxime Ripard maxime.ripard@bootlin.com --- Changes for v5.1: - s/CCU_CLK_F_INIT_DONE/CCU_CLK_F_IS_VALID - Add CCU_RST_F_IS_VALID flag - Use ccu_flags instead of ccu_clk_flags since reset flags is included
arch/arm/include/asm/arch-sunxi/ccu.h | 33 ++++++- drivers/clk/sunxi/Kconfig | 1 + drivers/clk/sunxi/clk_a64.c | 20 +++++ drivers/reset/Kconfig | 8 ++ drivers/reset/Makefile | 1 + drivers/reset/reset-sunxi.c | 125 ++++++++++++++++++++++++++ 6 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 drivers/reset/reset-sunxi.c
diff --git a/arch/arm/include/asm/arch-sunxi/ccu.h b/arch/arm/include/asm/arch-sunxi/ccu.h index 24efe0ab0a..5dd97ab227 100644 --- a/arch/arm/include/asm/arch-sunxi/ccu.h +++ b/arch/arm/include/asm/arch-sunxi/ccu.h @@ -8,12 +8,14 @@ #define _ASM_ARCH_CCU_H
/** - * enum ccu_flags - ccu clock flags + * enum ccu_flags - ccu clock/reset flags * * @CCU_CLK_F_IS_VALID: is given clock gate is valid? + * @CCU_RST_F_IS_VALID: is given reset control is valid? */ enum ccu_flags { CCU_CLK_F_IS_VALID = BIT(0), + CCU_RST_F_IS_VALID = BIT(1), };
/** @@ -34,13 +36,33 @@ struct ccu_clk_gate { .flags = CCU_CLK_F_IS_VALID, \ }
+/** + * struct ccu_reset - ccu reset + * @off: reset offset + * @bit: reset bit + * @flags: ccu reset control flags + */ +struct ccu_reset { + u16 off; + u32 bit; + enum ccu_flags flags; +}; + +#define RESET(_off, _bit) { \ + .off = _off, \ + .bit = _bit, \ + .flags = CCU_RST_F_IS_VALID, \ +} + /** * struct ccu_desc - clock control unit descriptor * * @gates: clock gates + * @resets: reset unit */ struct ccu_desc { const struct ccu_clk_gate *gates; + const struct ccu_reset *resets; };
/** @@ -62,4 +84,13 @@ int sunxi_clk_probe(struct udevice *dev);
extern struct clk_ops sunxi_clk_ops;
+/** + * sunxi_reset_bind() - reset binding + * + * @dev: reset device + * @count: reset count + * @return 0 success, or error value + */ +int sunxi_reset_bind(struct udevice *dev, ulong count); + #endif /* _ASM_ARCH_CCU_H */ diff --git a/drivers/clk/sunxi/Kconfig b/drivers/clk/sunxi/Kconfig index bf5ecb3801..041d711e58 100644 --- a/drivers/clk/sunxi/Kconfig +++ b/drivers/clk/sunxi/Kconfig @@ -1,6 +1,7 @@ config CLK_SUNXI bool "Clock support for Allwinner SoCs" depends on CLK && ARCH_SUNXI + select DM_RESET default y help This enables support for common clock driver API on Allwinner diff --git a/drivers/clk/sunxi/clk_a64.c b/drivers/clk/sunxi/clk_a64.c index 803a2f711d..28bda1f497 100644 --- a/drivers/clk/sunxi/clk_a64.c +++ b/drivers/clk/sunxi/clk_a64.c @@ -10,6 +10,7 @@ #include <errno.h> #include <asm/arch/ccu.h> #include <dt-bindings/clock/sun50i-a64-ccu.h> +#include <dt-bindings/reset/sun50i-a64-ccu.h>
static const struct ccu_clk_gate a64_gates[] = { [CLK_BUS_OTG] = GATE(0x060, BIT(23)), @@ -26,10 +27,28 @@ static const struct ccu_clk_gate a64_gates[] = { [CLK_USB_OHCI1] = GATE(0x0cc, BIT(17)), };
+static const struct ccu_reset a64_resets[] = { + [RST_USB_PHY0] = RESET(0x0cc, BIT(0)), + [RST_USB_PHY1] = RESET(0x0cc, BIT(1)), + [RST_USB_HSIC] = RESET(0x0cc, BIT(2)), + + [RST_BUS_OTG] = RESET(0x2c0, BIT(23)), + [RST_BUS_EHCI0] = RESET(0x2c0, BIT(24)), + [RST_BUS_EHCI1] = RESET(0x2c0, BIT(25)), + [RST_BUS_OHCI0] = RESET(0x2c0, BIT(28)), + [RST_BUS_OHCI1] = RESET(0x2c0, BIT(29)), +}; + static const struct ccu_desc a64_ccu_desc = { .gates = a64_gates, + .resets = a64_resets, };
+static int a64_clk_bind(struct udevice *dev) +{ + return sunxi_reset_bind(dev, 50); +} + static const struct udevice_id a64_ccu_ids[] = { { .compatible = "allwinner,sun50i-a64-ccu", .data = (ulong)&a64_ccu_desc }, @@ -43,4 +62,5 @@ U_BOOT_DRIVER(clk_sun50i_a64) = { .priv_auto_alloc_size = sizeof(struct ccu_priv), .ops = &sunxi_clk_ops, .probe = sunxi_clk_probe, + .bind = a64_clk_bind, }; diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 9c5208b7da..b6b40b6ce9 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -106,4 +106,12 @@ config RESET_SOCFPGA help Support for reset controller on SoCFPGA platform.
+config RESET_SUNXI + bool "RESET support for Allwinner SoCs" + depends on DM_RESET && ARCH_SUNXI + default y + help + This enables support for common reset driver for + Allwinner SoCs. + endmenu diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index f4520878b7..377c038163 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_AST2500_RESET) += ast2500-reset.o obj-$(CONFIG_RESET_ROCKCHIP) += reset-rockchip.o obj-$(CONFIG_RESET_MESON) += reset-meson.o obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o +obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o diff --git a/drivers/reset/reset-sunxi.c b/drivers/reset/reset-sunxi.c new file mode 100644 index 0000000000..9a35837d07 --- /dev/null +++ b/drivers/reset/reset-sunxi.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (C) 2018 Amarula Solutions. + * Author: Jagan Teki jagan@amarulasolutions.com + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <reset-uclass.h> +#include <asm/io.h> +#include <dm/lists.h> +#include <linux/log2.h> +#include <asm/arch/ccu.h> + +struct sunxi_reset_priv { + void *base; + ulong count; + const struct ccu_desc *desc; +}; + +static const struct ccu_reset *priv_to_reset(struct sunxi_reset_priv *priv, + unsigned long id) +{ + return &priv->desc->resets[id]; +} + +static int sunxi_reset_request(struct reset_ctl *reset_ctl) +{ + struct sunxi_reset_priv *priv = dev_get_priv(reset_ctl->dev); + + debug("%s: (RST#%ld)\n", __func__, reset_ctl->id); + + /* check dt-bindings/reset/sun8i-h3-ccu.h for max id */ + if (reset_ctl->id >= priv->count) + return -EINVAL; + + return 0; +} + +static int sunxi_reset_free(struct reset_ctl *reset_ctl) +{ + debug("%s: (RST#%ld)\n", __func__, reset_ctl->id); + + return 0; +} + +static int sunxi_set_reset(struct reset_ctl *reset_ctl, bool on) +{ + struct sunxi_reset_priv *priv = dev_get_priv(reset_ctl->dev); + const struct ccu_reset *reset = priv_to_reset(priv, reset_ctl->id); + u32 reg; + + if (!(reset->flags & CCU_RST_F_IS_VALID)) { + printf("%s: (RST#%ld) unhandled\n", __func__, reset_ctl->id); + return 0; + } + + debug("%s: (RST#%ld) off#0x%x, BIT(%d)\n", __func__, + reset_ctl->id, reset->off, ilog2(reset->bit)); + + reg = readl(priv->base + reset->off); + if (on) + reg |= reset->bit; + else + reg &= ~reset->bit; + + writel(reg, priv->base + reset->off); + + return 0; +} + +static int sunxi_reset_assert(struct reset_ctl *reset_ctl) +{ + return sunxi_set_reset(reset_ctl, false); +} + +static int sunxi_reset_deassert(struct reset_ctl *reset_ctl) +{ + return sunxi_set_reset(reset_ctl, true); +} + +struct reset_ops sunxi_reset_ops = { + .request = sunxi_reset_request, + .free = sunxi_reset_free, + .rst_assert = sunxi_reset_assert, + .rst_deassert = sunxi_reset_deassert, +}; + +static int sunxi_reset_probe(struct udevice *dev) +{ + struct sunxi_reset_priv *priv = dev_get_priv(dev); + + priv->base = dev_read_addr_ptr(dev); + + return 0; +} + +int sunxi_reset_bind(struct udevice *dev, ulong count) +{ + struct udevice *rst_dev; + struct sunxi_reset_priv *priv; + int ret; + + ret = device_bind_driver_to_node(dev, "sunxi_reset", "reset", + dev_ofnode(dev), &rst_dev); + if (ret) { + debug("failed to bind sunxi_reset driver (ret=%d)\n", ret); + return ret; + } + priv = malloc(sizeof(struct sunxi_reset_priv)); + priv->count = count; + priv->desc = (const struct ccu_desc *)dev_get_driver_data(dev); + rst_dev->priv = priv; + + return 0; +} + +U_BOOT_DRIVER(sunxi_reset) = { + .name = "sunxi_reset", + .id = UCLASS_RESET, + .ops = &sunxi_reset_ops, + .probe = sunxi_reset_probe, + .priv_auto_alloc_size = sizeof(struct sunxi_reset_priv), +};
participants (1)
-
Jagan Teki