summaryrefslogtreecommitdiffstats
path: root/drivers/clk/loongson/clk-ls1b200.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/loongson/clk-ls1b200.c')
-rw-r--r--drivers/clk/loongson/clk-ls1b200.c152
1 files changed, 152 insertions, 0 deletions
diff --git a/drivers/clk/loongson/clk-ls1b200.c b/drivers/clk/loongson/clk-ls1b200.c
new file mode 100644
index 0000000000..4e6aa94d52
--- /dev/null
+++ b/drivers/clk/loongson/clk-ls1b200.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Based on the ath79 clock code by Antony Pavlov <antonynpavlov@gmail.com>
+ * Barebox drivers/clk/clk-ar933x.c
+ *
+ * Copyright (C) 2020 Du Huanpeng <u74147@gmail.com>
+ */
+
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/err.h>
+
+#include <dt-bindings/clock/ls1b-clk.h>
+
+#define LS1B_CPU_DIV_SHIFT 20
+#define LS1B_CPU_DIV_WIDTH 4
+
+#define LS1B_DDR_DIV_SHIFT 14
+#define LS1B_DDR_DIV_WIDTH 4
+
+#define LS1B_DC_DIV_SHIFT 26
+#define LS1B_DC_DIV_WIDTH 4
+
+#define LS1B_CLK_APB_MULT 1
+#define LS1B_CLK_APB_DIV2 2
+
+/* register offset */
+#define PLL_FREQ 0
+#define PLL_DIV_PARAM 4
+
+static struct clk *clks[LS1B_CLK_END];
+static struct clk_onecell_data clk_data;
+
+struct clk_ls1b200 {
+ struct clk_hw hw;
+ void __iomem *base;
+ int div_shift;
+ int div_mask;
+ const char *parent;
+};
+
+static unsigned long clk_ls1b200_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ int n;
+ unsigned long rate;
+ int pll_freq;
+ struct clk_ls1b200 *ls1bclk;
+
+ ls1bclk = container_of(hw, struct clk_ls1b200, hw);
+ pll_freq = __raw_readl(ls1bclk->base);
+
+ n = 12 * 1024;
+ n += (pll_freq & 0x3F) * 1024;
+ n += (pll_freq >> 8) & 0x3FF;
+
+ rate = parent_rate / 2 / 1024;
+ /* avoid overflow. */
+ rate *= n;
+
+ return rate;
+}
+
+struct clk_ops clk_ls1b200_ops = {
+ .recalc_rate = clk_ls1b200_recalc_rate,
+};
+
+static struct clk *clk_ls1b200(const char *name, const char *parent,
+ void __iomem *base, int div_shift, int div_mask)
+{
+ struct clk_ls1b200 *f = xzalloc(sizeof(struct clk_ls1b200));
+
+ f->parent = parent;
+ f->base = base;
+ f->div_shift = div_shift;
+ f->div_mask = div_mask;
+
+ f->hw.clk.ops = &clk_ls1b200_ops;
+ f->hw.clk.name = name;
+ f->hw.clk.parent_names = &f->parent;
+ f->hw.clk.num_parents = 1;
+
+ bclk_register(&f->hw.clk);
+
+ return &f->hw.clk;
+}
+
+static const char * const cpu_mux[] = {"cpu_div", "oscillator", };
+static const char * const ddr_mux[] = {"ddr_div", "oscillator", };
+static const char * const dc_mux[] = {"dc_div", "oscillator", };
+
+static void ls1b200_pll_init(void __iomem *base)
+{
+ clks[LS1B_CLK_PLL] = clk_ls1b200("pll", "oscillator", base + PLL_FREQ, 0, 0);
+
+ clks[LS1B_CLK_CPU_DIV] = clk_divider("cpu_div", "pll", 0,
+ base + PLL_DIV_PARAM, LS1B_CPU_DIV_SHIFT, LS1B_CPU_DIV_WIDTH, CLK_DIVIDER_ONE_BASED);
+ clks[LS1B_CLK_CPU_MUX] = clk_mux("cpu_mux", 0, base + PLL_DIV_PARAM,
+ 8, 1, cpu_mux, ARRAY_SIZE(cpu_mux), 0);
+
+ clks[LS1B_CLK_DDR_DIV] = clk_divider("ddr_div", "pll", 0,
+ base + PLL_DIV_PARAM, LS1B_DDR_DIV_SHIFT, LS1B_DDR_DIV_WIDTH, CLK_DIVIDER_ONE_BASED);
+ clks[LS1B_CLK_DDR_MUX] = clk_mux("ddr_mux", 0, base + PLL_DIV_PARAM,
+ 10, 1, ddr_mux, ARRAY_SIZE(ddr_mux), 0);
+ clks[LS1B_CLK_APB_DIV] = clk_fixed_factor("apb_div", "ddr_mux", LS1B_CLK_APB_MULT, LS1B_CLK_APB_DIV2, 0);
+
+ clks[LS1B_CLK_DIV4] = clk_fixed_factor("dc_div4", "pll", 1, 4, 0);
+
+ clks[LS1B_CLK_DC_DIV] = clk_divider("dc_div", "dc_div4", 0,
+ base + PLL_DIV_PARAM, LS1B_DC_DIV_SHIFT, LS1B_DC_DIV_WIDTH, CLK_DIVIDER_ONE_BASED);
+ clks[LS1B_CLK_DC_MUX] = clk_mux("dc_mux", 0, base + PLL_DIV_PARAM,
+ 10, 1, dc_mux, ARRAY_SIZE(dc_mux), 0);
+}
+
+static int ls1b200_clk_probe(struct device *dev)
+{
+ struct resource *iores;
+ void __iomem *base;
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return PTR_ERR(iores);
+ base = IOMEM(iores->start);
+
+ /* now got the controller base address */
+ ls1b200_pll_init(base);
+
+ clk_data.clks = clks;
+ clk_data.clk_num = ARRAY_SIZE(clks);
+ of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, &clk_data);
+
+ return 0;
+}
+
+static __maybe_unused struct of_device_id ls1b200_clk_dt_ids[] = {
+ {
+ .compatible = "loongson,ls1b-pll",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, ls1b200_clk_dt_ids);
+
+static struct driver ls1b200_clk_driver = {
+ .probe = ls1b200_clk_probe,
+ .name = "ls1b-clk",
+ .of_compatible = DRV_OF_COMPAT(ls1b200_clk_dt_ids),
+};
+
+postcore_platform_driver(ls1b200_clk_driver);