linux/drivers/clk/clk-loongson2.c

342 lines
8.8 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0+
/*
* Author: Yinbo Zhu <zhuyinbo@loongson.cn>
* Copyright (C) 2022-2023 Loongson Technology Corporation Limited
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/clk-provider.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <dt-bindings/clock/loongson,ls2k-clk.h>
#define LOONGSON2_PLL_MULT_SHIFT 32
#define LOONGSON2_PLL_MULT_WIDTH 10
#define LOONGSON2_PLL_DIV_SHIFT 26
#define LOONGSON2_PLL_DIV_WIDTH 6
#define LOONGSON2_APB_FREQSCALE_SHIFT 20
#define LOONGSON2_APB_FREQSCALE_WIDTH 3
#define LOONGSON2_USB_FREQSCALE_SHIFT 16
#define LOONGSON2_USB_FREQSCALE_WIDTH 3
#define LOONGSON2_SATA_FREQSCALE_SHIFT 12
#define LOONGSON2_SATA_FREQSCALE_WIDTH 3
#define LOONGSON2_BOOT_FREQSCALE_SHIFT 8
#define LOONGSON2_BOOT_FREQSCALE_WIDTH 3
static void __iomem *loongson2_pll_base;
static const struct clk_parent_data pdata[] = {
{ .fw_name = "ref_100m",},
};
static struct clk_hw *loongson2_clk_register(struct device *dev,
const char *name,
const char *parent_name,
const struct clk_ops *ops,
unsigned long flags)
{
int ret;
struct clk_hw *hw;
clk: clk-loongson2: Zero init clk_init_data As clk_core_populate_parent_map() checks clk_init_data.num_parents first, and checks clk_init_data.parent_names[] before clk_init_data.parent_data[] and clk_init_data.parent_hws[]. Therefore the clk_init_data structure needs to be explicitly initialised to prevent an unexpected crash if clk_init_data.parent_names[] is a random value. CPU 0 Unable to handle kernel paging request at virtual address 0000000000000dc0, era == 9000000002986290, ra == 900000000298624c Oops[#1]: CPU: 0 PID: 1 Comm: swapper/0 Not tainted 6.4.0-rc2+ #4582 pc 9000000002986290 ra 900000000298624c tp 9000000100094000 sp 9000000100097a60 a0 9000000104541e00 a1 0000000000000000 a2 0000000000000dc0 a3 0000000000000001 a4 90000001000979f0 a5 90000001800977d7 a6 0000000000000000 a7 900000000362a000 t0 90000000034f3548 t1 6f8c2a9cb5ab5f64 t2 0000000000011340 t3 90000000031cf5b0 t4 0000000000000dc0 t5 0000000000000004 t6 0000000000011300 t7 9000000104541e40 t8 000000000005a4f8 u0 9000000104541e00 s9 9000000104541e00 s0 9000000104bc4700 s1 9000000104541da8 s2 0000000000000001 s3 900000000356f9d8 s4 ffffffffffffffff s5 0000000000000000 s6 0000000000000dc0 s7 90000000030d0a88 s8 0000000000000000 ra: 900000000298624c __clk_register+0x228/0x84c ERA: 9000000002986290 __clk_register+0x26c/0x84c CRMD: 000000b0 (PLV0 -IE -DA +PG DACF=CC DACM=CC -WE) PRMD: 00000004 (PPLV0 +PIE -PWE) EUEN: 00000000 (-FPE -SXE -ASXE -BTE) ECFG: 00071c1c (LIE=2-4,10-12 VS=7) ESTAT: 00010000 [PIL] (IS= ECode=1 EsubCode=0) BADV: 0000000000000dc0 PRID: 0014a000 (Loongson-64bit, ) Modules linked in: Process swapper/0 (pid: 1, threadinfo=(____ptrval____), task=(____ptrval____)) Stack : 90000000031c1810 90000000030d0a88 900000000325bac0 90000000034f3548 90000001002ab410 9000000104541e00 0000000000000dc0 9000000003150098 90000000031c1810 90000000031a0460 900000000362a000 90000001002ab410 900000000362a000 9000000104541da8 9000000104541de8 90000001002ab410 900000000362a000 9000000002986a68 90000000034f3ed8 90000000030d0aa8 9000000104541da8 900000000298d3b8 90000000031c1810 0000000000000000 90000000034f3ed8 90000000030d0aa8 0000000000000dc0 90000000030d0a88 90000001002ab410 900000000298d401 0000000000000000 6f8c2a9cb5ab5f64 90000000034f4000 90000000030d0a88 9000000003a48a58 90000001002ab410 9000000104bd81a8 900000000298d484 9000000100020260 0000000000000000 ... Call Trace: [<9000000002986290>] __clk_register+0x26c/0x84c [<9000000002986a68>] devm_clk_hw_register+0x5c/0xe0 [<900000000298d3b8>] loongson2_clk_register.constprop.0+0xdc/0x10c [<900000000298d484>] loongson2_clk_probe+0x9c/0x4ac [<9000000002a4eba4>] platform_probe+0x68/0xc8 [<9000000002a4bf80>] really_probe+0xbc/0x2f0 [<9000000002a4c23c>] __driver_probe_device+0x88/0x128 [<9000000002a4c318>] driver_probe_device+0x3c/0x11c [<9000000002a4c5dc>] __driver_attach+0x98/0x18c [<9000000002a49ca0>] bus_for_each_dev+0x80/0xe0 [<9000000002a4b0dc>] bus_add_driver+0xfc/0x1ec [<9000000002a4d4a8>] driver_register+0x68/0x134 [<90000000020f0110>] do_one_initcall+0x50/0x188 [<9000000003150f00>] kernel_init_freeable+0x224/0x294 [<90000000030240fc>] kernel_init+0x20/0x110 [<90000000020f1568>] ret_from_kernel_thread+0xc/0xa4 Fixes: acc0ccffec50 ("clk: clk-loongson2: add clock controller driver support") Cc: stable@vger.kernel.org Cc: Yinbo Zhu <zhuyinbo@loongson.cn> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> Link: https://lore.kernel.org/r/20230524014924.2869051-1-zhoubinbin@loongson.cn Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2023-05-24 04:49:24 +03:00
struct clk_init_data init = { };
hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL);
if (!hw)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = ops;
init.flags = flags;
init.num_parents = 1;
if (!parent_name)
init.parent_data = pdata;
else
init.parent_names = &parent_name;
hw->init = &init;
ret = devm_clk_hw_register(dev, hw);
if (ret)
hw = ERR_PTR(ret);
return hw;
}
static unsigned long loongson2_calc_pll_rate(int offset, unsigned long rate)
{
u64 val;
u32 mult, div;
val = readq(loongson2_pll_base + offset);
mult = (val >> LOONGSON2_PLL_MULT_SHIFT) &
clk_div_mask(LOONGSON2_PLL_MULT_WIDTH);
div = (val >> LOONGSON2_PLL_DIV_SHIFT) &
clk_div_mask(LOONGSON2_PLL_DIV_WIDTH);
return div_u64((u64)rate * mult, div);
}
static unsigned long loongson2_node_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return loongson2_calc_pll_rate(0x0, parent_rate);
}
static const struct clk_ops loongson2_node_clk_ops = {
.recalc_rate = loongson2_node_recalc_rate,
};
static unsigned long loongson2_ddr_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return loongson2_calc_pll_rate(0x10, parent_rate);
}
static const struct clk_ops loongson2_ddr_clk_ops = {
.recalc_rate = loongson2_ddr_recalc_rate,
};
static unsigned long loongson2_dc_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return loongson2_calc_pll_rate(0x20, parent_rate);
}
static const struct clk_ops loongson2_dc_clk_ops = {
.recalc_rate = loongson2_dc_recalc_rate,
};
static unsigned long loongson2_pix0_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return loongson2_calc_pll_rate(0x30, parent_rate);
}
static const struct clk_ops loongson2_pix0_clk_ops = {
.recalc_rate = loongson2_pix0_recalc_rate,
};
static unsigned long loongson2_pix1_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return loongson2_calc_pll_rate(0x40, parent_rate);
}
static const struct clk_ops loongson2_pix1_clk_ops = {
.recalc_rate = loongson2_pix1_recalc_rate,
};
static unsigned long loongson2_calc_rate(unsigned long rate,
int shift, int width)
{
u64 val;
u32 mult;
val = readq(loongson2_pll_base + 0x50);
mult = (val >> shift) & clk_div_mask(width);
return div_u64((u64)rate * (mult + 1), 8);
}
static unsigned long loongson2_boot_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return loongson2_calc_rate(parent_rate,
LOONGSON2_BOOT_FREQSCALE_SHIFT,
LOONGSON2_BOOT_FREQSCALE_WIDTH);
}
static const struct clk_ops loongson2_boot_clk_ops = {
.recalc_rate = loongson2_boot_recalc_rate,
};
static unsigned long loongson2_apb_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return loongson2_calc_rate(parent_rate,
LOONGSON2_APB_FREQSCALE_SHIFT,
LOONGSON2_APB_FREQSCALE_WIDTH);
}
static const struct clk_ops loongson2_apb_clk_ops = {
.recalc_rate = loongson2_apb_recalc_rate,
};
static unsigned long loongson2_usb_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return loongson2_calc_rate(parent_rate,
LOONGSON2_USB_FREQSCALE_SHIFT,
LOONGSON2_USB_FREQSCALE_WIDTH);
}
static const struct clk_ops loongson2_usb_clk_ops = {
.recalc_rate = loongson2_usb_recalc_rate,
};
static unsigned long loongson2_sata_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return loongson2_calc_rate(parent_rate,
LOONGSON2_SATA_FREQSCALE_SHIFT,
LOONGSON2_SATA_FREQSCALE_WIDTH);
}
static const struct clk_ops loongson2_sata_clk_ops = {
.recalc_rate = loongson2_sata_recalc_rate,
};
static inline int loongson2_check_clk_hws(struct clk_hw *clks[], unsigned int count)
{
unsigned int i;
for (i = 0; i < count; i++)
if (IS_ERR(clks[i])) {
pr_err("Loongson2 clk %u: register failed with %ld\n",
i, PTR_ERR(clks[i]));
return PTR_ERR(clks[i]);
}
return 0;
}
static int loongson2_clk_probe(struct platform_device *pdev)
{
int ret;
struct clk_hw **hws;
struct clk_hw_onecell_data *clk_hw_data;
spinlock_t loongson2_clk_lock;
struct device *dev = &pdev->dev;
loongson2_pll_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(loongson2_pll_base))
return PTR_ERR(loongson2_pll_base);
clk_hw_data = devm_kzalloc(dev, struct_size(clk_hw_data, hws, LOONGSON2_CLK_END),
GFP_KERNEL);
if (WARN_ON(!clk_hw_data))
return -ENOMEM;
clk_hw_data->num = LOONGSON2_CLK_END;
hws = clk_hw_data->hws;
hws[LOONGSON2_NODE_PLL] = loongson2_clk_register(dev, "node_pll",
NULL,
&loongson2_node_clk_ops, 0);
hws[LOONGSON2_DDR_PLL] = loongson2_clk_register(dev, "ddr_pll",
NULL,
&loongson2_ddr_clk_ops, 0);
hws[LOONGSON2_DC_PLL] = loongson2_clk_register(dev, "dc_pll",
NULL,
&loongson2_dc_clk_ops, 0);
hws[LOONGSON2_PIX0_PLL] = loongson2_clk_register(dev, "pix0_pll",
NULL,
&loongson2_pix0_clk_ops, 0);
hws[LOONGSON2_PIX1_PLL] = loongson2_clk_register(dev, "pix1_pll",
NULL,
&loongson2_pix1_clk_ops, 0);
hws[LOONGSON2_BOOT_CLK] = loongson2_clk_register(dev, "boot",
NULL,
&loongson2_boot_clk_ops, 0);
hws[LOONGSON2_NODE_CLK] = devm_clk_hw_register_divider(dev, "node",
"node_pll", 0,
loongson2_pll_base + 0x8, 0,
6, CLK_DIVIDER_ONE_BASED,
&loongson2_clk_lock);
/*
* The hda clk divisor in the upper 32bits and the clk-prodiver
* layer code doesn't support 64bit io operation thus a conversion
* is required that subtract shift by 32 and add 4byte to the hda
* address
*/
hws[LOONGSON2_HDA_CLK] = devm_clk_hw_register_divider(dev, "hda",
"ddr_pll", 0,
loongson2_pll_base + 0x22, 12,
7, CLK_DIVIDER_ONE_BASED,
&loongson2_clk_lock);
hws[LOONGSON2_GPU_CLK] = devm_clk_hw_register_divider(dev, "gpu",
"ddr_pll", 0,
loongson2_pll_base + 0x18, 22,
6, CLK_DIVIDER_ONE_BASED,
&loongson2_clk_lock);
hws[LOONGSON2_DDR_CLK] = devm_clk_hw_register_divider(dev, "ddr",
"ddr_pll", 0,
loongson2_pll_base + 0x18, 0,
6, CLK_DIVIDER_ONE_BASED,
&loongson2_clk_lock);
hws[LOONGSON2_GMAC_CLK] = devm_clk_hw_register_divider(dev, "gmac",
"dc_pll", 0,
loongson2_pll_base + 0x28, 22,
6, CLK_DIVIDER_ONE_BASED,
&loongson2_clk_lock);
hws[LOONGSON2_DC_CLK] = devm_clk_hw_register_divider(dev, "dc",
"dc_pll", 0,
loongson2_pll_base + 0x28, 0,
6, CLK_DIVIDER_ONE_BASED,
&loongson2_clk_lock);
hws[LOONGSON2_APB_CLK] = loongson2_clk_register(dev, "apb",
"gmac",
&loongson2_apb_clk_ops, 0);
hws[LOONGSON2_USB_CLK] = loongson2_clk_register(dev, "usb",
"gmac",
&loongson2_usb_clk_ops, 0);
hws[LOONGSON2_SATA_CLK] = loongson2_clk_register(dev, "sata",
"gmac",
&loongson2_sata_clk_ops, 0);
hws[LOONGSON2_PIX0_CLK] = clk_hw_register_divider(NULL, "pix0",
"pix0_pll", 0,
loongson2_pll_base + 0x38, 0, 6,
CLK_DIVIDER_ONE_BASED,
&loongson2_clk_lock);
hws[LOONGSON2_PIX1_CLK] = clk_hw_register_divider(NULL, "pix1",
"pix1_pll", 0,
loongson2_pll_base + 0x48, 0, 6,
CLK_DIVIDER_ONE_BASED,
&loongson2_clk_lock);
ret = loongson2_check_clk_hws(hws, LOONGSON2_CLK_END);
if (ret)
return ret;
return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_hw_data);
}
static const struct of_device_id loongson2_clk_match_table[] = {
{ .compatible = "loongson,ls2k-clk" },
{ }
};
MODULE_DEVICE_TABLE(of, loongson2_clk_match_table);
static struct platform_driver loongson2_clk_driver = {
.probe = loongson2_clk_probe,
.driver = {
.name = "loongson2-clk",
.of_match_table = loongson2_clk_match_table,
},
};
module_platform_driver(loongson2_clk_driver);
MODULE_DESCRIPTION("Loongson2 clock driver");
MODULE_LICENSE("GPL");