
From: Benjamin Tietz benjamin@micronet24.de
This implements an basic clock driver for the RCC-part of STM32 MCUs. Currently, only enabling and disabling of peripheral clocks and retrieving main frequency is implemented. --- drivers/clk/Kconfig | 4 ++ drivers/clk/Makefile | 1 drivers/clk/clk_stm32.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 drivers/clk/clk_stm32.c
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 6eee8eb..ebef031 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -23,4 +23,8 @@ config SPL_CLK source "drivers/clk/uniphier/Kconfig" source "drivers/clk/exynos/Kconfig"
+config CLK_STM32 + bool "Enable clock driver for STM32 devices" + depends on CLK + endmenu diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 81fe600..e7fd05a 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_SANDBOX) += clk_sandbox.o obj-$(CONFIG_MACH_PIC32) += clk_pic32.o obj-$(CONFIG_CLK_UNIPHIER) += uniphier/ obj-$(CONFIG_CLK_EXYNOS) += exynos/ +obj-$(CONFIG_CLK_STM32) += clk_stm32.o diff --git a/drivers/clk/clk_stm32.c b/drivers/clk/clk_stm32.c new file mode 100644 index 0000000..b9f6e11 --- /dev/null +++ b/drivers/clk/clk_stm32.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 Benjamin Tietz uboot@dresden.micronet24.de + * + * SPDX-License-Identifier: GPL-2.0+ + * + */ +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <asm/io.h> +#include <asm/arch/stm32.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct stm32_rcc_clk_priv { + struct stm32_rcc_regs *regs; +}; + +#define PLLCFGR_PLLN(reg) (((reg)>>6)&0x1ff) +#define PLLCFGR_PLLM(reg) (((reg)>>0)&0x3f) +#define PLLCFGR_PLLP(reg) (((reg)>>16)&0x3) +#define PLLCFGR_PLLQ(reg) (((reg)>>24)&0x0f) +#define PLLCFGR_HSE(reg) (reg & (1<<22)) + +static long stm32_rcc_get_vco_rate(struct udevice *dev, u32 *regptr) { + struct stm32_rcc_clk_priv *data = dev_get_priv(dev); + long freq = 16000000; + if(!(data && data->regs)) + return -EINVAL; + u32 reg = readl(&data->regs->pllcfgr); + if(regptr) *regptr = reg; + if(PLLCFGR_HSE(reg)) { + struct udevice *pclk; + int ret; + if((ret = clk_get_by_index(dev, 0, &pclk)) < 0) + return ret; + + freq = clk_get_rate(pclk); + } + if(freq < 0) return freq; + return freq * PLLCFGR_PLLN(reg) / PLLCFGR_PLLM(reg); +} + +static ulong stm32_rcc_get_rate(struct udevice *dev) { + u32 reg = 0; + long freq = stm32_rcc_get_vco_rate(dev, ®); + if(freq < 0) return freq; + return freq / ( 2 + 2 * PLLCFGR_PLLP(reg)); +} + +static int stm32_rcc_xxable(struct udevice *dev, int periph, int on) { + struct stm32_rcc_clk_priv *data = dev_get_priv(dev); + if(!(data && data->regs)) + return -EINVAL; + + int port = periph >> 5; + int bit = periph & 0x1f; + if(port >= 8) + return -ENOSYS; + + u32 *enr = &data->regs->ahb1enr; + on ? + setbits_le32(&enr[port], 1<<bit): + clrbits_le32(&enr[port], 1<<bit); + return 0; +} + +static int stm32_rcc_enable(struct udevice *dev, int periph) { + return stm32_rcc_xxable(dev, periph, 1); +} + +static int stm32_rcc_disable(struct udevice *dev, int periph) { + return stm32_rcc_xxable(dev, periph, 0); +} + +static struct clk_ops stm32_rcc_clk_ops = { + .get_rate = stm32_rcc_get_rate, + .enable = stm32_rcc_enable, + .disable = stm32_rcc_disable, +}; + +static int stm32_rcc_clk_probe(struct udevice *dev) { + struct stm32_rcc_clk_priv *priv = dev_get_priv(dev); + fdt_addr_t addr; + fdt_size_t size; + + addr = fdtdec_get_addr_size(gd->fdt_blob, dev->of_offset, "reg", &size); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + + if(size < sizeof(*priv->regs)) + return -EINVAL; + + priv->regs = (struct stm32_rcc_regs *) addr; + + return 0; +} + +static const struct udevice_id stm32_rcc_clk_ids[] = { + { .compatible = "st,stm32f42xx-rcc", }, + {} +}; + +U_BOOT_DRIVER(stm32_rcc_clk) = { + .name = "stm32_rcc_clk", + .id = UCLASS_CLK, + .of_match = stm32_rcc_clk_ids, + .flags = DM_FLAG_PRE_RELOC, + .ops = &stm32_rcc_clk_ops, + .probe = stm32_rcc_clk_probe, + .priv_auto_alloc_size = sizeof(struct stm32_rcc_clk_priv), +};