mirror of
https://github.com/torvalds/linux.git
synced 2026-01-12 00:42:35 +08:00
The clock controller of Loongson-2K0300 consists of three PLLs, requires an 120MHz external reference clock to function, and generates clocks in various frequencies for SoC peripherals. Clock definitions for previous SoC generations could be reused for most clock hardwares. There're two gates marked as critical, clk_node_gate and clk_boot_gate, which supply the CPU cores and the system configuration bus. Disabling them leads to a SoC hang. Signed-off-by: Yao Zi <ziyao@disroot.org> Signed-off-by: Stephen Boyd <sboyd@kernel.org>
461 lines
16 KiB
C
461 lines
16 KiB
C
// 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>
|
|
|
|
enum loongson2_clk_type {
|
|
CLK_TYPE_PLL,
|
|
CLK_TYPE_SCALE,
|
|
CLK_TYPE_DIVIDER,
|
|
CLK_TYPE_GATE,
|
|
CLK_TYPE_FIXED,
|
|
CLK_TYPE_NONE,
|
|
};
|
|
|
|
struct loongson2_clk_provider {
|
|
void __iomem *base;
|
|
struct device *dev;
|
|
spinlock_t clk_lock; /* protect access to DIV registers */
|
|
|
|
/* Must be last --ends in a flexible-array member. */
|
|
struct clk_hw_onecell_data clk_data;
|
|
};
|
|
|
|
struct loongson2_clk_data {
|
|
struct clk_hw hw;
|
|
void __iomem *reg;
|
|
u8 div_shift;
|
|
u8 div_width;
|
|
u8 mult_shift;
|
|
u8 mult_width;
|
|
u8 bit_idx;
|
|
};
|
|
|
|
struct loongson2_clk_board_info {
|
|
u8 id;
|
|
enum loongson2_clk_type type;
|
|
const char *name;
|
|
const char *parent_name;
|
|
unsigned long fixed_rate;
|
|
unsigned long flags;
|
|
u8 reg_offset;
|
|
u8 div_shift;
|
|
u8 div_width;
|
|
u8 mult_shift;
|
|
u8 mult_width;
|
|
u8 bit_idx;
|
|
};
|
|
|
|
#define CLK_DIV(_id, _name, _pname, _offset, _dshift, _dwidth) \
|
|
{ \
|
|
.id = _id, \
|
|
.type = CLK_TYPE_DIVIDER, \
|
|
.name = _name, \
|
|
.parent_name = _pname, \
|
|
.reg_offset = _offset, \
|
|
.div_shift = _dshift, \
|
|
.div_width = _dwidth, \
|
|
}
|
|
|
|
#define CLK_PLL(_id, _name, _offset, _mshift, _mwidth, \
|
|
_dshift, _dwidth) \
|
|
{ \
|
|
.id = _id, \
|
|
.type = CLK_TYPE_PLL, \
|
|
.name = _name, \
|
|
.parent_name = NULL, \
|
|
.reg_offset = _offset, \
|
|
.mult_shift = _mshift, \
|
|
.mult_width = _mwidth, \
|
|
.div_shift = _dshift, \
|
|
.div_width = _dwidth, \
|
|
}
|
|
|
|
#define CLK_SCALE(_id, _name, _pname, _offset, \
|
|
_dshift, _dwidth) \
|
|
{ \
|
|
.id = _id, \
|
|
.type = CLK_TYPE_SCALE, \
|
|
.name = _name, \
|
|
.parent_name = _pname, \
|
|
.reg_offset = _offset, \
|
|
.div_shift = _dshift, \
|
|
.div_width = _dwidth, \
|
|
}
|
|
|
|
#define CLK_SCALE_MODE(_id, _name, _pname, _offset, \
|
|
_dshift, _dwidth, _midx) \
|
|
{ \
|
|
.id = _id, \
|
|
.type = CLK_TYPE_SCALE, \
|
|
.name = _name, \
|
|
.parent_name = _pname, \
|
|
.reg_offset = _offset, \
|
|
.div_shift = _dshift, \
|
|
.div_width = _dwidth, \
|
|
.bit_idx = _midx + 1, \
|
|
}
|
|
|
|
#define CLK_GATE(_id, _name, _pname, _offset, _bidx) \
|
|
{ \
|
|
.id = _id, \
|
|
.type = CLK_TYPE_GATE, \
|
|
.name = _name, \
|
|
.parent_name = _pname, \
|
|
.reg_offset = _offset, \
|
|
.bit_idx = _bidx, \
|
|
}
|
|
|
|
#define CLK_GATE_FLAGS(_id, _name, _pname, _offset, _bidx, \
|
|
_flags) \
|
|
{ \
|
|
.id = _id, \
|
|
.type = CLK_TYPE_GATE, \
|
|
.name = _name, \
|
|
.parent_name = _pname, \
|
|
.reg_offset = _offset, \
|
|
.bit_idx = _bidx, \
|
|
.flags = _flags \
|
|
}
|
|
|
|
#define CLK_FIXED(_id, _name, _pname, _rate) \
|
|
{ \
|
|
.id = _id, \
|
|
.type = CLK_TYPE_FIXED, \
|
|
.name = _name, \
|
|
.parent_name = _pname, \
|
|
.fixed_rate = _rate, \
|
|
}
|
|
|
|
static const struct loongson2_clk_board_info ls2k0300_clks[] = {
|
|
/* Reference Clock */
|
|
CLK_PLL(LS2K0300_NODE_PLL, "pll_node", 0x00, 15, 9, 8, 7),
|
|
CLK_PLL(LS2K0300_DDR_PLL, "pll_ddr", 0x08, 15, 9, 8, 7),
|
|
CLK_PLL(LS2K0300_PIX_PLL, "pll_pix", 0x10, 15, 9, 8, 7),
|
|
CLK_FIXED(LS2K0300_CLK_STABLE, "clk_stable", NULL, 100000000),
|
|
CLK_FIXED(LS2K0300_CLK_THSENS, "clk_thsens", NULL, 10000000),
|
|
/* Node PLL */
|
|
CLK_DIV(LS2K0300_CLK_NODE_DIV, "clk_node_div", "pll_node", 0x00, 24, 7),
|
|
CLK_DIV(LS2K0300_CLK_GMAC_DIV, "clk_gmac_div", "pll_node", 0x04, 0, 7),
|
|
CLK_DIV(LS2K0300_CLK_I2S_DIV, "clk_i2s_div", "pll_node", 0x04, 8, 7),
|
|
CLK_GATE(LS2K0300_CLK_NODE_PLL_GATE, "clk_node_pll_gate", "clk_node_div", 0x00, 0),
|
|
CLK_GATE(LS2K0300_CLK_GMAC_GATE, "clk_gmac_gate", "clk_gmac_div", 0x00, 1),
|
|
CLK_GATE(LS2K0300_CLK_I2S_GATE, "clk_i2s_gate", "clk_i2s_div", 0x00, 2),
|
|
CLK_GATE_FLAGS(LS2K0300_CLK_NODE_GATE, "clk_node_gate", "clk_node_scale", 0x24, 0,
|
|
CLK_IS_CRITICAL),
|
|
CLK_SCALE_MODE(LS2K0300_CLK_NODE_SCALE, "clk_node_scale", "clk_node_pll_gate", 0x20, 0, 3,
|
|
3),
|
|
/* DDR PLL */
|
|
CLK_DIV(LS2K0300_CLK_DDR_DIV, "clk_ddr_div", "pll_ddr", 0x08, 24, 7),
|
|
CLK_DIV(LS2K0300_CLK_NET_DIV, "clk_net_div", "pll_ddr", 0x0c, 0, 7),
|
|
CLK_DIV(LS2K0300_CLK_DEV_DIV, "clk_dev_div", "pll_ddr", 0x0c, 8, 7),
|
|
CLK_GATE(LS2K0300_CLK_NET_GATE, "clk_net_gate", "clk_net_div", 0x08, 1),
|
|
CLK_GATE(LS2K0300_CLK_DEV_GATE, "clk_dev_gate", "clk_dev_div", 0x08, 2),
|
|
CLK_GATE_FLAGS(LS2K0300_CLK_DDR_GATE, "clk_ddr_gate", "clk_ddr_div", 0x08, 0,
|
|
CLK_IS_CRITICAL),
|
|
/* PIX PLL */
|
|
CLK_DIV(LS2K0300_CLK_PIX_DIV, "clk_pix_div", "pll_pix", 0x10, 24, 7),
|
|
CLK_DIV(LS2K0300_CLK_GMACBP_DIV, "clk_gmacbp_div", "pll_pix", 0x14, 0, 7),
|
|
CLK_GATE(LS2K0300_CLK_PIX_PLL_GATE, "clk_pix_pll_gate", "clk_pix_div", 0x10, 0),
|
|
CLK_GATE(LS2K0300_CLK_PIX_GATE, "clk_pix_gate", "clk_pix_scale", 0x24, 6),
|
|
CLK_GATE(LS2K0300_CLK_GMACBP_GATE, "clk_gmacbp_gate", "clk_gmacbp_div", 0x10, 1),
|
|
CLK_SCALE_MODE(LS2K0300_CLK_PIX_SCALE, "clk_pix_scale", "clk_pix_pll_gate", 0x20, 4, 3, 7),
|
|
/* clk_dev_gate */
|
|
CLK_DIV(LS2K0300_CLK_SDIO_SCALE, "clk_sdio_scale", "clk_dev_gate", 0x20, 24, 4),
|
|
CLK_GATE(LS2K0300_CLK_USB_GATE, "clk_usb_gate", "clk_usb_scale", 0x24, 2),
|
|
CLK_GATE(LS2K0300_CLK_SDIO_GATE, "clk_sdio_gate", "clk_sdio_scale", 0x24, 4),
|
|
CLK_GATE(LS2K0300_CLK_APB_GATE, "clk_apb_gate", "clk_apb_scale", 0x24, 3),
|
|
CLK_GATE_FLAGS(LS2K0300_CLK_BOOT_GATE, "clk_boot_gate", "clk_boot_scale", 0x24, 1,
|
|
CLK_IS_CRITICAL),
|
|
CLK_SCALE_MODE(LS2K0300_CLK_USB_SCALE, "clk_usb_scale", "clk_dev_gate", 0x20, 12, 3, 15),
|
|
CLK_SCALE_MODE(LS2K0300_CLK_APB_SCALE, "clk_apb_scale", "clk_dev_gate", 0x20, 16, 3, 19),
|
|
CLK_SCALE_MODE(LS2K0300_CLK_BOOT_SCALE, "clk_boot_scale", "clk_dev_gate", 0x20, 8, 3, 11),
|
|
};
|
|
|
|
static const struct loongson2_clk_board_info ls2k0500_clks[] = {
|
|
CLK_PLL(LOONGSON2_NODE_PLL, "pll_node", 0, 16, 8, 8, 6),
|
|
CLK_PLL(LOONGSON2_DDR_PLL, "pll_ddr", 0x8, 16, 8, 8, 6),
|
|
CLK_PLL(LOONGSON2_DC_PLL, "pll_soc", 0x10, 16, 8, 8, 6),
|
|
CLK_PLL(LOONGSON2_PIX0_PLL, "pll_pix0", 0x18, 16, 8, 8, 6),
|
|
CLK_PLL(LOONGSON2_PIX1_PLL, "pll_pix1", 0x20, 16, 8, 8, 6),
|
|
CLK_DIV(LOONGSON2_NODE_CLK, "clk_node", "pll_node", 0, 24, 6),
|
|
CLK_DIV(LOONGSON2_DDR_CLK, "clk_ddr", "pll_ddr", 0x8, 24, 6),
|
|
CLK_DIV(LOONGSON2_HDA_CLK, "clk_hda", "pll_ddr", 0xc, 8, 6),
|
|
CLK_DIV(LOONGSON2_GPU_CLK, "clk_gpu", "pll_soc", 0x10, 24, 6),
|
|
CLK_DIV(LOONGSON2_DC_CLK, "clk_sb", "pll_soc", 0x14, 0, 6),
|
|
CLK_DIV(LOONGSON2_GMAC_CLK, "clk_gmac", "pll_soc", 0x14, 8, 6),
|
|
CLK_DIV(LOONGSON2_PIX0_CLK, "clk_pix0", "pll_pix0", 0x18, 24, 6),
|
|
CLK_DIV(LOONGSON2_PIX1_CLK, "clk_pix1", "pll_pix1", 0x20, 24, 6),
|
|
CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", "clk_sb", 0x28, 8, 3),
|
|
CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_sb", 0x28, 12, 3),
|
|
CLK_SCALE(LOONGSON2_USB_CLK, "clk_usb", "clk_sb", 0x28, 16, 3),
|
|
CLK_SCALE(LOONGSON2_APB_CLK, "clk_apb", "clk_sb", 0x28, 20, 3),
|
|
{ /* Sentinel */ },
|
|
};
|
|
|
|
static const struct loongson2_clk_board_info ls2k1000_clks[] = {
|
|
CLK_PLL(LOONGSON2_NODE_PLL, "pll_node", 0, 32, 10, 26, 6),
|
|
CLK_PLL(LOONGSON2_DDR_PLL, "pll_ddr", 0x10, 32, 10, 26, 6),
|
|
CLK_PLL(LOONGSON2_DC_PLL, "pll_dc", 0x20, 32, 10, 26, 6),
|
|
CLK_PLL(LOONGSON2_PIX0_PLL, "pll_pix0", 0x30, 32, 10, 26, 6),
|
|
CLK_PLL(LOONGSON2_PIX1_PLL, "pll_pix1", 0x40, 32, 10, 26, 6),
|
|
CLK_DIV(LOONGSON2_NODE_CLK, "clk_node", "pll_node", 0x8, 0, 6),
|
|
CLK_DIV(LOONGSON2_DDR_CLK, "clk_ddr", "pll_ddr", 0x18, 0, 6),
|
|
CLK_DIV(LOONGSON2_GPU_CLK, "clk_gpu", "pll_ddr", 0x18, 22, 6),
|
|
/*
|
|
* 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
|
|
*/
|
|
CLK_DIV(LOONGSON2_HDA_CLK, "clk_hda", "pll_ddr", 0x22, 12, 7),
|
|
CLK_DIV(LOONGSON2_DC_CLK, "clk_dc", "pll_dc", 0x28, 0, 6),
|
|
CLK_DIV(LOONGSON2_GMAC_CLK, "clk_gmac", "pll_dc", 0x28, 22, 6),
|
|
CLK_DIV(LOONGSON2_PIX0_CLK, "clk_pix0", "pll_pix0", 0x38, 0, 6),
|
|
CLK_DIV(LOONGSON2_PIX1_CLK, "clk_pix1", "pll_pix1", 0x38, 0, 6),
|
|
CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", NULL, 0x50, 8, 3),
|
|
CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_gmac", 0x50, 12, 3),
|
|
CLK_SCALE(LOONGSON2_USB_CLK, "clk_usb", "clk_gmac", 0x50, 16, 3),
|
|
CLK_SCALE(LOONGSON2_APB_CLK, "clk_apb", "clk_gmac", 0x50, 20, 3),
|
|
{ /* Sentinel */ },
|
|
};
|
|
|
|
static const struct loongson2_clk_board_info ls2k2000_clks[] = {
|
|
CLK_PLL(LOONGSON2_DC_PLL, "pll_0", 0, 21, 9, 32, 6),
|
|
CLK_PLL(LOONGSON2_DDR_PLL, "pll_1", 0x10, 21, 9, 32, 6),
|
|
CLK_PLL(LOONGSON2_NODE_PLL, "pll_2", 0x20, 21, 9, 32, 6),
|
|
CLK_PLL(LOONGSON2_PIX0_PLL, "pll_pix0", 0x30, 21, 9, 32, 6),
|
|
CLK_PLL(LOONGSON2_PIX1_PLL, "pll_pix1", 0x40, 21, 9, 32, 6),
|
|
CLK_GATE(LOONGSON2_OUT0_GATE, "out0_gate", "pll_0", 0, 40),
|
|
CLK_GATE(LOONGSON2_GMAC_GATE, "gmac_gate", "pll_0", 0, 41),
|
|
CLK_GATE(LOONGSON2_RIO_GATE, "rio_gate", "pll_0", 0, 42),
|
|
CLK_GATE(LOONGSON2_DC_GATE, "dc_gate", "pll_1", 0x10, 40),
|
|
CLK_GATE(LOONGSON2_DDR_GATE, "ddr_gate", "pll_1", 0x10, 41),
|
|
CLK_GATE(LOONGSON2_GPU_GATE, "gpu_gate", "pll_1", 0x10, 42),
|
|
CLK_GATE(LOONGSON2_HDA_GATE, "hda_gate", "pll_2", 0x20, 40),
|
|
CLK_GATE(LOONGSON2_NODE_GATE, "node_gate", "pll_2", 0x20, 41),
|
|
CLK_GATE(LOONGSON2_EMMC_GATE, "emmc_gate", "pll_2", 0x20, 42),
|
|
CLK_GATE(LOONGSON2_PIX0_GATE, "pix0_gate", "pll_pix0", 0x30, 40),
|
|
CLK_GATE(LOONGSON2_PIX1_GATE, "pix1_gate", "pll_pix1", 0x40, 40),
|
|
CLK_DIV(LOONGSON2_OUT0_CLK, "clk_out0", "out0_gate", 0, 0, 6),
|
|
CLK_DIV(LOONGSON2_GMAC_CLK, "clk_gmac", "gmac_gate", 0, 7, 6),
|
|
CLK_DIV(LOONGSON2_RIO_CLK, "clk_rio", "rio_gate", 0, 14, 6),
|
|
CLK_DIV(LOONGSON2_DC_CLK, "clk_dc", "dc_gate", 0x10, 0, 6),
|
|
CLK_DIV(LOONGSON2_GPU_CLK, "clk_gpu", "gpu_gate", 0x10, 7, 6),
|
|
CLK_DIV(LOONGSON2_DDR_CLK, "clk_ddr", "ddr_gate", 0x10, 14, 6),
|
|
CLK_DIV(LOONGSON2_HDA_CLK, "clk_hda", "hda_gate", 0x20, 0, 6),
|
|
CLK_DIV(LOONGSON2_NODE_CLK, "clk_node", "node_gate", 0x20, 7, 6),
|
|
CLK_DIV(LOONGSON2_EMMC_CLK, "clk_emmc", "emmc_gate", 0x20, 14, 6),
|
|
CLK_DIV(LOONGSON2_PIX0_CLK, "clk_pix0", "pll_pix0", 0x30, 0, 6),
|
|
CLK_DIV(LOONGSON2_PIX1_CLK, "clk_pix1", "pll_pix1", 0x40, 0, 6),
|
|
CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_out0", 0x50, 12, 3),
|
|
CLK_SCALE(LOONGSON2_USB_CLK, "clk_usb", "clk_out0", 0x50, 16, 3),
|
|
CLK_SCALE(LOONGSON2_APB_CLK, "clk_apb", "clk_node", 0x50, 20, 3),
|
|
CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", NULL, 0x50, 23, 3),
|
|
CLK_SCALE(LOONGSON2_DES_CLK, "clk_des", "clk_node", 0x50, 40, 3),
|
|
CLK_SCALE(LOONGSON2_I2S_CLK, "clk_i2s", "clk_node", 0x50, 44, 3),
|
|
CLK_FIXED(LOONGSON2_MISC_CLK, "clk_misc", NULL, 50000000),
|
|
{ /* Sentinel */ },
|
|
};
|
|
|
|
static inline struct loongson2_clk_data *to_loongson2_clk(struct clk_hw *hw)
|
|
{
|
|
return container_of(hw, struct loongson2_clk_data, hw);
|
|
}
|
|
|
|
static inline unsigned long loongson2_rate_part(u64 val, u8 shift, u8 width)
|
|
{
|
|
return (val & GENMASK(shift + width - 1, shift)) >> shift;
|
|
}
|
|
|
|
static unsigned long loongson2_pll_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
u64 val, mult, div;
|
|
struct loongson2_clk_data *clk = to_loongson2_clk(hw);
|
|
|
|
val = readq(clk->reg);
|
|
mult = loongson2_rate_part(val, clk->mult_shift, clk->mult_width);
|
|
div = loongson2_rate_part(val, clk->div_shift, clk->div_width);
|
|
|
|
return div_u64((u64)parent_rate * mult, div);
|
|
}
|
|
|
|
static const struct clk_ops loongson2_pll_recalc_ops = {
|
|
.recalc_rate = loongson2_pll_recalc_rate,
|
|
};
|
|
|
|
static unsigned long loongson2_freqscale_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
u64 val, scale;
|
|
u32 mode = 0;
|
|
struct loongson2_clk_data *clk = to_loongson2_clk(hw);
|
|
|
|
val = readq(clk->reg);
|
|
scale = loongson2_rate_part(val, clk->div_shift, clk->div_width) + 1;
|
|
|
|
if (clk->bit_idx)
|
|
mode = val & BIT(clk->bit_idx - 1);
|
|
|
|
return mode == 0 ? div_u64((u64)parent_rate * scale, 8) :
|
|
div_u64((u64)parent_rate, scale);
|
|
}
|
|
|
|
static const struct clk_ops loongson2_freqscale_recalc_ops = {
|
|
.recalc_rate = loongson2_freqscale_recalc_rate,
|
|
};
|
|
|
|
static struct clk_hw *loongson2_clk_register(const char *parent,
|
|
struct loongson2_clk_provider *clp,
|
|
const struct loongson2_clk_board_info *cld,
|
|
const struct clk_ops *ops)
|
|
{
|
|
int ret;
|
|
struct clk_hw *hw;
|
|
struct loongson2_clk_data *clk;
|
|
struct clk_init_data init = { };
|
|
|
|
clk = devm_kzalloc(clp->dev, sizeof(*clk), GFP_KERNEL);
|
|
if (!clk)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
init.name = cld->name;
|
|
init.ops = ops;
|
|
init.flags = 0;
|
|
init.num_parents = 1;
|
|
init.parent_names = &parent;
|
|
|
|
clk->reg = clp->base + cld->reg_offset;
|
|
clk->div_shift = cld->div_shift;
|
|
clk->div_width = cld->div_width;
|
|
clk->mult_shift = cld->mult_shift;
|
|
clk->mult_width = cld->mult_width;
|
|
clk->bit_idx = cld->bit_idx;
|
|
clk->hw.init = &init;
|
|
|
|
hw = &clk->hw;
|
|
ret = devm_clk_hw_register(clp->dev, hw);
|
|
if (ret)
|
|
clk = ERR_PTR(ret);
|
|
|
|
return hw;
|
|
}
|
|
|
|
static int loongson2_clk_probe(struct platform_device *pdev)
|
|
{
|
|
int i, clks_num = 0;
|
|
struct clk_hw *hw;
|
|
struct device *dev = &pdev->dev;
|
|
struct loongson2_clk_provider *clp;
|
|
const struct loongson2_clk_board_info *p, *data;
|
|
const char *refclk_name, *parent_name;
|
|
|
|
data = device_get_match_data(dev);
|
|
if (!data)
|
|
return -EINVAL;
|
|
|
|
refclk_name = of_clk_get_parent_name(dev->of_node, 0);
|
|
if (IS_ERR(refclk_name))
|
|
return dev_err_probe(dev, PTR_ERR(refclk_name),
|
|
"failed to get refclk name\n");
|
|
|
|
for (p = data; p->name; p++)
|
|
clks_num = max(clks_num, p->id + 1);
|
|
|
|
clp = devm_kzalloc(dev, struct_size(clp, clk_data.hws, clks_num),
|
|
GFP_KERNEL);
|
|
if (!clp)
|
|
return -ENOMEM;
|
|
|
|
clp->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(clp->base))
|
|
return PTR_ERR(clp->base);
|
|
|
|
spin_lock_init(&clp->clk_lock);
|
|
clp->clk_data.num = clks_num;
|
|
clp->dev = dev;
|
|
|
|
/* Avoid returning NULL for unused id */
|
|
memset_p((void **)clp->clk_data.hws, ERR_PTR(-ENOENT), clks_num);
|
|
|
|
for (i = 0; i < clks_num; i++) {
|
|
p = &data[i];
|
|
parent_name = p->parent_name ? p->parent_name : refclk_name;
|
|
|
|
switch (p->type) {
|
|
case CLK_TYPE_PLL:
|
|
hw = loongson2_clk_register(parent_name, clp, p,
|
|
&loongson2_pll_recalc_ops);
|
|
break;
|
|
case CLK_TYPE_SCALE:
|
|
hw = loongson2_clk_register(parent_name, clp, p,
|
|
&loongson2_freqscale_recalc_ops);
|
|
break;
|
|
case CLK_TYPE_DIVIDER:
|
|
hw = devm_clk_hw_register_divider(dev, p->name,
|
|
parent_name, 0,
|
|
clp->base + p->reg_offset,
|
|
p->div_shift, p->div_width,
|
|
CLK_DIVIDER_ONE_BASED |
|
|
CLK_DIVIDER_ALLOW_ZERO,
|
|
&clp->clk_lock);
|
|
break;
|
|
case CLK_TYPE_GATE:
|
|
hw = devm_clk_hw_register_gate(dev, p->name, parent_name,
|
|
p->flags,
|
|
clp->base + p->reg_offset,
|
|
p->bit_idx, 0,
|
|
&clp->clk_lock);
|
|
break;
|
|
case CLK_TYPE_FIXED:
|
|
hw = devm_clk_hw_register_fixed_rate(dev, p->name, parent_name,
|
|
0, p->fixed_rate);
|
|
break;
|
|
default:
|
|
return dev_err_probe(dev, -EINVAL, "Invalid clk type\n");
|
|
}
|
|
|
|
if (IS_ERR(hw))
|
|
return dev_err_probe(dev, PTR_ERR(hw),
|
|
"Register clk: %s, type: %u failed!\n",
|
|
p->name, p->type);
|
|
|
|
clp->clk_data.hws[p->id] = hw;
|
|
}
|
|
|
|
return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, &clp->clk_data);
|
|
}
|
|
|
|
static const struct of_device_id loongson2_clk_match_table[] = {
|
|
{ .compatible = "loongson,ls2k0300-clk", .data = &ls2k0300_clks },
|
|
{ .compatible = "loongson,ls2k0500-clk", .data = &ls2k0500_clks },
|
|
{ .compatible = "loongson,ls2k-clk", .data = &ls2k1000_clks },
|
|
{ .compatible = "loongson,ls2k2000-clk", .data = &ls2k2000_clks },
|
|
{ }
|
|
};
|
|
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_AUTHOR("Loongson Technology Corporation Limited");
|
|
MODULE_LICENSE("GPL");
|