mirror of
https://github.com/torvalds/linux.git
synced 2024-11-24 21:21:41 +00:00
clk: Add RISC-V Canaan Kendryte K210 clock driver
Add a clock provider driver for the Canaan Kendryte K210 RISC-V SoC. This new driver with the compatible string "canaan,k210-clk" implements support for the full clock structure of the K210 SoC. Since it is required for the correct operation of the SoC, this driver is selected by default for compilation when the SOC_CANAAN option is selected. With this change, the k210-sysctl driver is turned into a simple platform driver which enables its power bus clock and triggers populating its child nodes. The sysctl driver retains the SOC early initialization code, but the implementation now relies on the new function k210_clk_early_init() provided by the new clk-k210 driver. The clock structure implemented and many of the coding ideas for the driver come from the work by Sean Anderson on the K210 support for the U-Boot project. Cc: Stephen Boyd <sboyd@kernel.org> Cc: Michael Turquette <mturquette@baylibre.com> Cc: linux-clk@vger.kernel.org Signed-off-by: Damien Le Moal <damien.lemoal@wdc.com> Reviewed-by: Stephen Boyd <sboyd@kernel.org> Signed-off-by: Palmer Dabbelt <palmerdabbelt@google.com>
This commit is contained in:
parent
4bb875632a
commit
c6ca7616f7
@ -368,6 +368,13 @@ config COMMON_CLK_FIXED_MMIO
|
||||
help
|
||||
Support for Memory Mapped IO Fixed clocks
|
||||
|
||||
config COMMON_CLK_K210
|
||||
bool "Clock driver for the Canaan Kendryte K210 SoC"
|
||||
depends on OF && RISCV && SOC_CANAAN
|
||||
default SOC_CANAAN
|
||||
help
|
||||
Support for the Canaan Kendryte K210 RISC-V SoC clocks.
|
||||
|
||||
source "drivers/clk/actions/Kconfig"
|
||||
source "drivers/clk/analogbits/Kconfig"
|
||||
source "drivers/clk/baikal-t1/Kconfig"
|
||||
|
@ -37,6 +37,7 @@ obj-$(CONFIG_COMMON_CLK_ASPEED) += clk-aspeed.o
|
||||
obj-$(CONFIG_MACH_ASPEED_G6) += clk-ast2600.o
|
||||
obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o
|
||||
obj-$(CONFIG_CLK_HSDK) += clk-hsdk-pll.o
|
||||
obj-$(CONFIG_COMMON_CLK_K210) += clk-k210.o
|
||||
obj-$(CONFIG_COMMON_CLK_LOCHNAGAR) += clk-lochnagar.o
|
||||
obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
|
||||
obj-$(CONFIG_COMMON_CLK_MAX9485) += clk-max9485.o
|
||||
|
1007
drivers/clk/clk-k210.c
Normal file
1007
drivers/clk/clk-k210.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
if SOC_CANAAN
|
||||
|
||||
config K210_SYSCTL
|
||||
config SOC_K210_SYSCTL
|
||||
bool "Canaan Kendryte K210 SoC system controller"
|
||||
default y
|
||||
depends on RISCV
|
||||
depends on RISCV && SOC_CANAAN && OF
|
||||
default SOC_CANAAN
|
||||
select PM
|
||||
select SIMPLE_PM_BUS
|
||||
select SYSCON
|
||||
select MFD_SYSCON
|
||||
help
|
||||
Enables controlling the K210 various clocks and to enable
|
||||
general purpose use of the extra 2MB of SRAM normally
|
||||
reserved for the AI engine.
|
||||
|
||||
endif
|
||||
Canaan Kendryte K210 SoC system controller driver.
|
||||
|
@ -1,3 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_K210_SYSCTL) += k210-sysctl.o
|
||||
obj-$(CONFIG_SOC_K210_SYSCTL) += k210-sysctl.o
|
||||
|
@ -3,165 +3,45 @@
|
||||
* Copyright (c) 2019 Christoph Hellwig.
|
||||
* Copyright (c) 2019 Western Digital Corporation or its affiliates.
|
||||
*/
|
||||
#include <linux/types.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/clk.h>
|
||||
#include <asm/soc.h>
|
||||
|
||||
#include <soc/canaan/k210-sysctl.h>
|
||||
|
||||
#define K210_SYSCTL_CLK0_FREQ 26000000UL
|
||||
|
||||
/* Registers base address */
|
||||
#define K210_SYSCTL_SYSCTL_BASE_ADDR 0x50440000ULL
|
||||
|
||||
/* Register bits */
|
||||
/* K210_SYSCTL_PLL1: clkr: 4bits, clkf1: 6bits, clkod: 4bits, bwadj: 4bits */
|
||||
#define PLL_RESET (1 << 20)
|
||||
#define PLL_PWR (1 << 21)
|
||||
#define PLL_BYPASS (1 << 23)
|
||||
#define PLL_OUT_EN (1 << 25)
|
||||
/* K210_SYSCTL_PLL_LOCK */
|
||||
#define PLL1_LOCK1 (1 << 8)
|
||||
#define PLL1_LOCK2 (1 << 9)
|
||||
#define PLL1_SLIP_CLEAR (1 << 10)
|
||||
/* K210_SYSCTL_SEL0 */
|
||||
#define CLKSEL_ACLK (1 << 0)
|
||||
/* K210_SYSCTL_CLKEN_CENT */
|
||||
#define CLKEN_CPU (1 << 0)
|
||||
#define CLKEN_SRAM0 (1 << 1)
|
||||
#define CLKEN_SRAM1 (1 << 2)
|
||||
/* K210_SYSCTL_EN_PERI */
|
||||
#define CLKEN_ROM (1 << 0)
|
||||
#define CLKEN_TIMER0 (1 << 21)
|
||||
#define CLKEN_RTC (1 << 29)
|
||||
|
||||
struct k210_sysctl {
|
||||
void __iomem *regs;
|
||||
struct clk_hw hw;
|
||||
};
|
||||
|
||||
static void k210_set_bits(u32 val, void __iomem *reg)
|
||||
{
|
||||
writel(readl(reg) | val, reg);
|
||||
}
|
||||
|
||||
static void k210_clear_bits(u32 val, void __iomem *reg)
|
||||
{
|
||||
writel(readl(reg) & ~val, reg);
|
||||
}
|
||||
|
||||
static void k210_pll1_enable(void __iomem *regs)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(regs + K210_SYSCTL_PLL1);
|
||||
val &= ~GENMASK(19, 0); /* clkr1 = 0 */
|
||||
val |= FIELD_PREP(GENMASK(9, 4), 0x3B); /* clkf1 = 59 */
|
||||
val |= FIELD_PREP(GENMASK(13, 10), 0x3); /* clkod1 = 3 */
|
||||
val |= FIELD_PREP(GENMASK(19, 14), 0x3B); /* bwadj1 = 59 */
|
||||
writel(val, regs + K210_SYSCTL_PLL1);
|
||||
|
||||
k210_clear_bits(PLL_BYPASS, regs + K210_SYSCTL_PLL1);
|
||||
k210_set_bits(PLL_PWR, regs + K210_SYSCTL_PLL1);
|
||||
|
||||
/*
|
||||
* Reset the pll. The magic NOPs come from the Kendryte reference SDK.
|
||||
*/
|
||||
k210_clear_bits(PLL_RESET, regs + K210_SYSCTL_PLL1);
|
||||
k210_set_bits(PLL_RESET, regs + K210_SYSCTL_PLL1);
|
||||
nop();
|
||||
nop();
|
||||
k210_clear_bits(PLL_RESET, regs + K210_SYSCTL_PLL1);
|
||||
|
||||
for (;;) {
|
||||
val = readl(regs + K210_SYSCTL_PLL_LOCK);
|
||||
if (val & PLL1_LOCK2)
|
||||
break;
|
||||
writel(val | PLL1_SLIP_CLEAR, regs + K210_SYSCTL_PLL_LOCK);
|
||||
}
|
||||
|
||||
k210_set_bits(PLL_OUT_EN, regs + K210_SYSCTL_PLL1);
|
||||
}
|
||||
|
||||
static unsigned long k210_sysctl_clk_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct k210_sysctl *s = container_of(hw, struct k210_sysctl, hw);
|
||||
u32 clksel0, pll0;
|
||||
u64 pll0_freq, clkr0, clkf0, clkod0;
|
||||
|
||||
/*
|
||||
* If the clock selector is not set, use the base frequency.
|
||||
* Otherwise, use PLL0 frequency with a frequency divisor.
|
||||
*/
|
||||
clksel0 = readl(s->regs + K210_SYSCTL_SEL0);
|
||||
if (!(clksel0 & CLKSEL_ACLK))
|
||||
return K210_SYSCTL_CLK0_FREQ;
|
||||
|
||||
/*
|
||||
* Get PLL0 frequency:
|
||||
* freq = base frequency * clkf0 / (clkr0 * clkod0)
|
||||
*/
|
||||
pll0 = readl(s->regs + K210_SYSCTL_PLL0);
|
||||
clkr0 = 1 + FIELD_GET(GENMASK(3, 0), pll0);
|
||||
clkf0 = 1 + FIELD_GET(GENMASK(9, 4), pll0);
|
||||
clkod0 = 1 + FIELD_GET(GENMASK(13, 10), pll0);
|
||||
pll0_freq = clkf0 * K210_SYSCTL_CLK0_FREQ / (clkr0 * clkod0);
|
||||
|
||||
/* Get the frequency divisor from the clock selector */
|
||||
return pll0_freq / (2ULL << FIELD_GET(0x00000006, clksel0));
|
||||
}
|
||||
|
||||
static const struct clk_ops k210_sysctl_clk_ops = {
|
||||
.recalc_rate = k210_sysctl_clk_recalc_rate,
|
||||
};
|
||||
|
||||
static const struct clk_init_data k210_clk_init_data = {
|
||||
.name = "k210-sysctl-pll1",
|
||||
.ops = &k210_sysctl_clk_ops,
|
||||
};
|
||||
|
||||
static int k210_sysctl_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct k210_sysctl *s;
|
||||
int error;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct clk *pclk;
|
||||
int ret;
|
||||
|
||||
pr_info("Kendryte K210 SoC sysctl\n");
|
||||
dev_info(dev, "K210 system controller\n");
|
||||
|
||||
s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
/* Get power bus clock */
|
||||
pclk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(pclk))
|
||||
return dev_err_probe(dev, PTR_ERR(pclk),
|
||||
"Get bus clock failed\n");
|
||||
|
||||
s->regs = devm_ioremap_resource(&pdev->dev,
|
||||
platform_get_resource(pdev, IORESOURCE_MEM, 0));
|
||||
if (IS_ERR(s->regs))
|
||||
return PTR_ERR(s->regs);
|
||||
|
||||
s->hw.init = &k210_clk_init_data;
|
||||
error = devm_clk_hw_register(&pdev->dev, &s->hw);
|
||||
if (error) {
|
||||
dev_err(&pdev->dev, "failed to register clk");
|
||||
return error;
|
||||
ret = clk_prepare_enable(pclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Enable bus clock failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
error = devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get,
|
||||
&s->hw);
|
||||
if (error) {
|
||||
dev_err(&pdev->dev, "adding clk provider failed\n");
|
||||
return error;
|
||||
}
|
||||
/* Populate children */
|
||||
ret = devm_of_platform_populate(dev);
|
||||
if (ret)
|
||||
dev_err(dev, "Populate platform failed %d\n", ret);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id k210_sysctl_of_match[] = {
|
||||
{ .compatible = "kendryte,k210-sysctl", },
|
||||
{}
|
||||
{ .compatible = "canaan,k210-sysctl", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
|
||||
static struct platform_driver k210_sysctl_driver = {
|
||||
@ -171,12 +51,13 @@ static struct platform_driver k210_sysctl_driver = {
|
||||
},
|
||||
.probe = k210_sysctl_probe,
|
||||
};
|
||||
builtin_platform_driver(k210_sysctl_driver);
|
||||
|
||||
static int __init k210_sysctl_init(void)
|
||||
{
|
||||
return platform_driver_register(&k210_sysctl_driver);
|
||||
}
|
||||
core_initcall(k210_sysctl_init);
|
||||
/*
|
||||
* System controller registers base address and size.
|
||||
*/
|
||||
#define K210_SYSCTL_BASE_ADDR 0x50440000ULL
|
||||
#define K210_SYSCTL_BASE_SIZE 0x1000
|
||||
|
||||
/*
|
||||
* This needs to be called very early during initialization, given that
|
||||
@ -184,24 +65,14 @@ core_initcall(k210_sysctl_init);
|
||||
*/
|
||||
static void __init k210_soc_early_init(const void *fdt)
|
||||
{
|
||||
void __iomem *regs;
|
||||
void __iomem *sysctl_base;
|
||||
|
||||
regs = ioremap(K210_SYSCTL_SYSCTL_BASE_ADDR, 0x1000);
|
||||
if (!regs)
|
||||
panic("K210 sysctl ioremap");
|
||||
sysctl_base = ioremap(K210_SYSCTL_BASE_ADDR, K210_SYSCTL_BASE_SIZE);
|
||||
if (!sysctl_base)
|
||||
panic("k210-sysctl: ioremap failed");
|
||||
|
||||
/* Enable PLL1 to make the KPU SRAM useable */
|
||||
k210_pll1_enable(regs);
|
||||
k210_clk_early_init(sysctl_base);
|
||||
|
||||
k210_set_bits(PLL_OUT_EN, regs + K210_SYSCTL_PLL0);
|
||||
|
||||
k210_set_bits(CLKEN_CPU | CLKEN_SRAM0 | CLKEN_SRAM1,
|
||||
regs + K210_SYSCTL_EN_CENT);
|
||||
k210_set_bits(CLKEN_ROM | CLKEN_TIMER0 | CLKEN_RTC,
|
||||
regs + K210_SYSCTL_EN_PERI);
|
||||
|
||||
k210_set_bits(CLKSEL_ACLK, regs + K210_SYSCTL_SEL0);
|
||||
|
||||
iounmap(regs);
|
||||
iounmap(sysctl_base);
|
||||
}
|
||||
SOC_EARLY_INIT_DECLARE(generic_k210, "kendryte,k210", k210_soc_early_init);
|
||||
SOC_EARLY_INIT_DECLARE(k210_soc, "canaan,kendryte-k210", k210_soc_early_init);
|
||||
|
@ -38,4 +38,6 @@
|
||||
#define K210_SYSCTL_DMA_SEL1 0x68 /* DMA handshake selector 1 */
|
||||
#define K210_SYSCTL_POWER_SEL 0x6C /* IO Power Mode Select controller */
|
||||
|
||||
void k210_clk_early_init(void __iomem *regs);
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user