kernel/drivers/clk/sophgo/clk-sg2042-clkgen.c

1153 lines
36 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: GPL-2.0
/*
* Sophgo SG2042 Clock Generator Driver
*
* Copyright (C) 2024 Sophgo Technology Inc.
* Copyright (C) 2024 Chen Wang <unicorn_wang@outlook.com>
*/
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <asm/div64.h>
#include <dt-bindings/clock/sophgo,sg2042-clkgen.h>
#include "clk-sg2042.h"
/* Registers defined in SYS_CTRL */
#define R_PLL_BEGIN 0xC0
#define R_PLL_STAT (0xC0 - R_PLL_BEGIN)
#define R_PLL_CLKEN_CONTROL (0xC4 - R_PLL_BEGIN)
#define R_MPLL_CONTROL (0xE8 - R_PLL_BEGIN)
#define R_FPLL_CONTROL (0xF4 - R_PLL_BEGIN)
#define R_DPLL0_CONTROL (0xF8 - R_PLL_BEGIN)
#define R_DPLL1_CONTROL (0xFC - R_PLL_BEGIN)
/* Registers defined in CLOCK */
#define R_CLKENREG0 0x00
#define R_CLKENREG1 0x04
#define R_CLKSELREG0 0x20
#define R_CLKDIVREG0 0x40
#define R_CLKDIVREG1 0x44
#define R_CLKDIVREG2 0x48
#define R_CLKDIVREG3 0x4C
#define R_CLKDIVREG4 0x50
#define R_CLKDIVREG5 0x54
#define R_CLKDIVREG6 0x58
#define R_CLKDIVREG7 0x5C
#define R_CLKDIVREG8 0x60
#define R_CLKDIVREG9 0x64
#define R_CLKDIVREG10 0x68
#define R_CLKDIVREG11 0x6C
#define R_CLKDIVREG12 0x70
#define R_CLKDIVREG13 0x74
#define R_CLKDIVREG14 0x78
#define R_CLKDIVREG15 0x7C
#define R_CLKDIVREG16 0x80
#define R_CLKDIVREG17 0x84
#define R_CLKDIVREG18 0x88
#define R_CLKDIVREG19 0x8C
#define R_CLKDIVREG20 0x90
#define R_CLKDIVREG21 0x94
#define R_CLKDIVREG22 0x98
#define R_CLKDIVREG23 0x9C
#define R_CLKDIVREG24 0xA0
#define R_CLKDIVREG25 0xA4
#define R_CLKDIVREG26 0xA8
#define R_CLKDIVREG27 0xAC
#define R_CLKDIVREG28 0xB0
#define R_CLKDIVREG29 0xB4
#define R_CLKDIVREG30 0xB8
/* All following shift value are the same for all DIV registers */
#define SHIFT_DIV_RESET_CTRL 0
#define SHIFT_DIV_FACTOR_SEL 3
#define SHIFT_DIV_FACTOR 16
/**
* struct sg2042_divider_clock - Divider clock
* @hw: clk_hw for initialization
* @id: used to map clk_onecell_data
* @reg: used for readl/writel.
* **NOTE**: DIV registers are ALL in CLOCK!
* @lock: spinlock to protect register access, modification of
* frequency can only be served one at the time
* @offset_ctrl: offset of divider control registers
* @shift: shift of "Clock Divider Factor" in divider control register
* @width: width of "Clock Divider Factor" in divider control register
* @div_flags: private flags for this clock, not for framework-specific
* @initval: In the divider control register, we can configure whether
* to use the value of "Clock Divider Factor" or just use
* the initial value pre-configured by IC. BIT[3] controls
* this and by default (value is 0), means initial value
* is used.
* **NOTE** that we cannot read the initial value (default
* value when poweron) and default value of "Clock Divider
* Factor" is zero, which I think is a hardware design flaw
* and should be sync-ed with the initial value. So in
* software we have to add a configuration item (initval)
* to manually configure this value and use it when BIT[3]
* is zero.
*/
struct sg2042_divider_clock {
struct clk_hw hw;
unsigned int id;
void __iomem *reg;
/* protect register access */
spinlock_t *lock;
u32 offset_ctrl;
u8 shift;
u8 width;
u8 div_flags;
u32 initval;
};
#define to_sg2042_clk_divider(_hw) \
container_of(_hw, struct sg2042_divider_clock, hw)
/**
* struct sg2042_gate_clock - Gate clock
* @hw: clk_hw for initialization
* @id: used to map clk_onecell_data
* @offset_enable: offset of gate enable registers
* @bit_idx: which bit in the register controls gating of this clock
*/
struct sg2042_gate_clock {
struct clk_hw hw;
unsigned int id;
u32 offset_enable;
u8 bit_idx;
};
/**
* struct sg2042_mux_clock - Mux clock
* @hw: clk_hw for initialization
* @id: used to map clk_onecell_data
* @offset_select: offset of mux selection registers
* **NOTE**: MUX registers are ALL in CLOCK!
* @shift: shift of "Clock Select" in mux selection register
* @width: width of "Clock Select" in mux selection register
* @clk_nb: used for notification
* @original_index: set by notifier callback
*/
struct sg2042_mux_clock {
struct clk_hw hw;
unsigned int id;
u32 offset_select;
u8 shift;
u8 width;
struct notifier_block clk_nb;
u8 original_index;
};
#define to_sg2042_mux_nb(_nb) container_of(_nb, struct sg2042_mux_clock, clk_nb)
static unsigned long sg2042_clk_divider_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct sg2042_divider_clock *divider = to_sg2042_clk_divider(hw);
unsigned long ret_rate;
u32 val;
if (!(readl(divider->reg) & BIT(SHIFT_DIV_FACTOR_SEL))) {
val = divider->initval;
} else {
val = readl(divider->reg) >> divider->shift;
val &= clk_div_mask(divider->width);
}
ret_rate = divider_recalc_rate(hw, parent_rate, val, NULL,
divider->div_flags, divider->width);
pr_debug("--> %s: divider_recalc_rate: ret_rate = %ld\n",
clk_hw_get_name(hw), ret_rate);
return ret_rate;
}
static long sg2042_clk_divider_round_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long *prate)
{
struct sg2042_divider_clock *divider = to_sg2042_clk_divider(hw);
unsigned long ret_rate;
u32 bestdiv;
/* if read only, just return current value */
if (divider->div_flags & CLK_DIVIDER_READ_ONLY) {
if (!(readl(divider->reg) & BIT(SHIFT_DIV_FACTOR_SEL))) {
bestdiv = divider->initval;
} else {
bestdiv = readl(divider->reg) >> divider->shift;
bestdiv &= clk_div_mask(divider->width);
}
ret_rate = DIV_ROUND_UP_ULL((u64)*prate, bestdiv);
} else {
ret_rate = divider_round_rate(hw, rate, prate, NULL,
divider->width, divider->div_flags);
}
pr_debug("--> %s: divider_round_rate: val = %ld\n",
clk_hw_get_name(hw), ret_rate);
return ret_rate;
}
static int sg2042_clk_divider_set_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate)
{
struct sg2042_divider_clock *divider = to_sg2042_clk_divider(hw);
unsigned long flags = 0;
u32 val, val2, value;
value = divider_get_val(rate, parent_rate, NULL,
divider->width, divider->div_flags);
if (divider->lock)
spin_lock_irqsave(divider->lock, flags);
else
__acquire(divider->lock);
/*
* The sequence of clock frequency modification is:
* Assert to reset divider.
* Modify the value of Clock Divide Factor (and High Wide if needed).
* De-assert to restore divided clock with new frequency.
*/
val = readl(divider->reg);
/* assert */
val &= ~BIT(SHIFT_DIV_RESET_CTRL);
writel(val, divider->reg);
if (divider->div_flags & CLK_DIVIDER_HIWORD_MASK) {
val = clk_div_mask(divider->width) << (divider->shift + 16);
} else {
val = readl(divider->reg);
val &= ~(clk_div_mask(divider->width) << divider->shift);
}
val |= value << divider->shift;
val |= BIT(SHIFT_DIV_FACTOR_SEL);
writel(val, divider->reg);
val2 = val;
/* de-assert */
val |= BIT(SHIFT_DIV_RESET_CTRL);
writel(val, divider->reg);
if (divider->lock)
spin_unlock_irqrestore(divider->lock, flags);
else
__release(divider->lock);
pr_debug("--> %s: divider_set_rate: register val = 0x%x\n",
clk_hw_get_name(hw), val2);
return 0;
}
static const struct clk_ops sg2042_clk_divider_ops = {
.recalc_rate = sg2042_clk_divider_recalc_rate,
.round_rate = sg2042_clk_divider_round_rate,
.set_rate = sg2042_clk_divider_set_rate,
};
static const struct clk_ops sg2042_clk_divider_ro_ops = {
.recalc_rate = sg2042_clk_divider_recalc_rate,
.round_rate = sg2042_clk_divider_round_rate,
};
/*
* Clock initialization macro naming rules:
* FW: use CLK_HW_INIT_FW_NAME
* HW: use CLK_HW_INIT_HW
* HWS: use CLK_HW_INIT_HWS
* RO: means Read-Only
*/
#define SG2042_DIV_FW(_id, _name, _parent, \
_r_ctrl, _shift, _width, \
_div_flag, _initval) { \
.id = _id, \
.hw.init = CLK_HW_INIT_FW_NAME( \
_name, \
_parent, \
&sg2042_clk_divider_ops, \
0), \
.offset_ctrl = _r_ctrl, \
.shift = _shift, \
.width = _width, \
.div_flags = _div_flag, \
.initval = _initval, \
}
#define SG2042_DIV_FW_RO(_id, _name, _parent, \
_r_ctrl, _shift, _width, \
_div_flag, _initval) { \
.id = _id, \
.hw.init = CLK_HW_INIT_FW_NAME( \
_name, \
_parent, \
&sg2042_clk_divider_ro_ops, \
0), \
.offset_ctrl = _r_ctrl, \
.shift = _shift, \
.width = _width, \
.div_flags = (_div_flag) | CLK_DIVIDER_READ_ONLY, \
.initval = _initval, \
}
#define SG2042_DIV_HW(_id, _name, _parent, \
_r_ctrl, _shift, _width, \
_div_flag, _initval) { \
.id = _id, \
.hw.init = CLK_HW_INIT_HW( \
_name, \
_parent, \
&sg2042_clk_divider_ops, \
0), \
.offset_ctrl = _r_ctrl, \
.shift = _shift, \
.width = _width, \
.div_flags = _div_flag, \
.initval = _initval, \
}
#define SG2042_DIV_HW_RO(_id, _name, _parent, \
_r_ctrl, _shift, _width, \
_div_flag, _initval) { \
.id = _id, \
.hw.init = CLK_HW_INIT_HW( \
_name, \
_parent, \
&sg2042_clk_divider_ro_ops, \
0), \
.offset_ctrl = _r_ctrl, \
.shift = _shift, \
.width = _width, \
.div_flags = (_div_flag) | CLK_DIVIDER_READ_ONLY, \
.initval = _initval, \
}
#define SG2042_DIV_HWS(_id, _name, _parent, \
_r_ctrl, _shift, _width, \
_div_flag, _initval) { \
.id = _id, \
.hw.init = CLK_HW_INIT_HWS( \
_name, \
_parent, \
&sg2042_clk_divider_ops, \
0), \
.offset_ctrl = _r_ctrl, \
.shift = _shift, \
.width = _width, \
.div_flags = _div_flag, \
.initval = _initval, \
}
#define SG2042_DIV_HWS_RO(_id, _name, _parent, \
_r_ctrl, _shift, _width, \
_div_flag, _initval) { \
.id = _id, \
.hw.init = CLK_HW_INIT_HWS( \
_name, \
_parent, \
&sg2042_clk_divider_ro_ops, \
0), \
.offset_ctrl = _r_ctrl, \
.shift = _shift, \
.width = _width, \
.div_flags = (_div_flag) | CLK_DIVIDER_READ_ONLY, \
.initval = _initval, \
}
#define SG2042_GATE_HWS(_id, _name, _parent, _flags, \
_r_enable, _bit_idx) { \
.id = _id, \
.hw.init = CLK_HW_INIT_HWS( \
_name, \
_parent, \
NULL, \
_flags), \
.offset_enable = _r_enable, \
.bit_idx = _bit_idx, \
}
#define SG2042_GATE_HW(_id, _name, _parent, _flags, \
_r_enable, _bit_idx) { \
.id = _id, \
.hw.init = CLK_HW_INIT_HW( \
_name, \
_parent, \
NULL, \
_flags), \
.offset_enable = _r_enable, \
.bit_idx = _bit_idx, \
}
#define SG2042_GATE_FW(_id, _name, _parent, _flags, \
_r_enable, _bit_idx) { \
.id = _id, \
.hw.init = CLK_HW_INIT_FW_NAME( \
_name, \
_parent, \
NULL, \
_flags), \
.offset_enable = _r_enable, \
.bit_idx = _bit_idx, \
}
#define SG2042_MUX(_id, _name, _parents, _flags, _r_select, _shift, _width) { \
.id = _id, \
.hw.init = CLK_HW_INIT_PARENTS_HW( \
_name, \
_parents, \
NULL, \
_flags), \
.offset_select = _r_select, \
.shift = _shift, \
.width = _width, \
}
/*
* Clock items in the array are sorted according to the clock-tree diagram,
* from top to bottom, from upstream to downstream. Read TRM for details.
*/
/* updated during probe/registration */
static const struct clk_hw *clk_gate_ddr01_div0[] = { NULL };
static const struct clk_hw *clk_gate_ddr01_div1[] = { NULL };
static const struct clk_hw *clk_gate_ddr23_div0[] = { NULL };
static const struct clk_hw *clk_gate_ddr23_div1[] = { NULL };
static const struct clk_hw *clk_gate_rp_cpu_normal_div0[] = { NULL };
static const struct clk_hw *clk_gate_rp_cpu_normal_div1[] = { NULL };
static const struct clk_hw *clk_gate_axi_ddr_div0[] = { NULL };
static const struct clk_hw *clk_gate_axi_ddr_div1[] = { NULL };
static const struct sg2042_gate_clock sg2042_gate_clks_level_1[] = {
SG2042_GATE_FW(GATE_CLK_DDR01_DIV0, "clk_gate_ddr01_div0", "dpll0",
CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED,
R_CLKDIVREG27, 4),
SG2042_GATE_FW(GATE_CLK_DDR01_DIV1, "clk_gate_ddr01_div1", "fpll",
CLK_IS_CRITICAL,
R_CLKDIVREG28, 4),
SG2042_GATE_FW(GATE_CLK_DDR23_DIV0, "clk_gate_ddr23_div0", "dpll1",
CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED,
R_CLKDIVREG29, 4),
SG2042_GATE_FW(GATE_CLK_DDR23_DIV1, "clk_gate_ddr23_div1", "fpll",
CLK_IS_CRITICAL,
R_CLKDIVREG30, 4),
SG2042_GATE_FW(GATE_CLK_RP_CPU_NORMAL_DIV0,
"clk_gate_rp_cpu_normal_div0", "mpll",
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
R_CLKDIVREG0, 4),
SG2042_GATE_FW(GATE_CLK_RP_CPU_NORMAL_DIV1,
"clk_gate_rp_cpu_normal_div1", "fpll",
CLK_IS_CRITICAL,
R_CLKDIVREG1, 4),
SG2042_GATE_FW(GATE_CLK_AXI_DDR_DIV0, "clk_gate_axi_ddr_div0", "mpll",
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
R_CLKDIVREG25, 4),
SG2042_GATE_FW(GATE_CLK_AXI_DDR_DIV1, "clk_gate_axi_ddr_div1", "fpll",
CLK_IS_CRITICAL,
R_CLKDIVREG26, 4),
};
#define DEF_DIVFLAG (CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO)
static struct sg2042_divider_clock sg2042_div_clks_level_1[] = {
SG2042_DIV_HWS_RO(DIV_CLK_DPLL0_DDR01_0,
"clk_div_ddr01_0", clk_gate_ddr01_div0,
R_CLKDIVREG27, 16, 5, DEF_DIVFLAG, 1),
SG2042_DIV_HWS_RO(DIV_CLK_FPLL_DDR01_1,
"clk_div_ddr01_1", clk_gate_ddr01_div1,
R_CLKDIVREG28, 16, 5, DEF_DIVFLAG, 1),
SG2042_DIV_HWS_RO(DIV_CLK_DPLL1_DDR23_0,
"clk_div_ddr23_0", clk_gate_ddr23_div0,
R_CLKDIVREG29, 16, 5, DEF_DIVFLAG, 1),
SG2042_DIV_HWS_RO(DIV_CLK_FPLL_DDR23_1,
"clk_div_ddr23_1", clk_gate_ddr23_div1,
R_CLKDIVREG30, 16, 5, DEF_DIVFLAG, 1),
SG2042_DIV_HWS(DIV_CLK_MPLL_RP_CPU_NORMAL_0,
"clk_div_rp_cpu_normal_0", clk_gate_rp_cpu_normal_div0,
R_CLKDIVREG0, 16, 5, DEF_DIVFLAG, 1),
SG2042_DIV_HWS(DIV_CLK_FPLL_RP_CPU_NORMAL_1,
"clk_div_rp_cpu_normal_1", clk_gate_rp_cpu_normal_div1,
R_CLKDIVREG1, 16, 5, DEF_DIVFLAG, 1),
SG2042_DIV_HWS(DIV_CLK_MPLL_AXI_DDR_0,
"clk_div_axi_ddr_0", clk_gate_axi_ddr_div0,
R_CLKDIVREG25, 16, 5, DEF_DIVFLAG, 2),
SG2042_DIV_HWS(DIV_CLK_FPLL_AXI_DDR_1,
"clk_div_axi_ddr_1", clk_gate_axi_ddr_div1,
R_CLKDIVREG26, 16, 5, DEF_DIVFLAG, 1),
};
/*
* Note: regarding names for mux clock, "0/1" or "div0/div1" means the
* first/second parent input source, not the register value.
* For example:
* "clk_div_ddr01_0" is the name of Clock divider 0 control of DDR01, and
* "clk_gate_ddr01_div0" is the gate clock in front of the "clk_div_ddr01_0",
* they are both controlled by register CLKDIVREG27;
* "clk_div_ddr01_1" is the name of Clock divider 1 control of DDR01, and
* "clk_gate_ddr01_div1" is the gate clock in front of the "clk_div_ddr01_1",
* they are both controlled by register CLKDIVREG28;
* While for register value of mux selection, use Clock Select for DDR01s clock
* as example, see CLKSELREG0, bit[2].
* 1: Select in_dpll0_clk as clock source, correspondng to the parent input
* source from "clk_div_ddr01_0".
* 0: Select in_fpll_clk as clock source, corresponding to the parent input
* source from "clk_div_ddr01_1".
* So we need a table to define the array of register values corresponding to
* the parent index and tell CCF about this when registering mux clock.
*/
static const u32 sg2042_mux_table[] = {1, 0};
/* Aliases just for easy reading */
#define clk_div_ddr01_0 (&sg2042_div_clks_level_1[0].hw)
#define clk_div_ddr01_1 (&sg2042_div_clks_level_1[1].hw)
#define clk_div_ddr23_0 (&sg2042_div_clks_level_1[2].hw)
#define clk_div_ddr23_1 (&sg2042_div_clks_level_1[3].hw)
#define clk_div_rp_cpu_normal_0 (&sg2042_div_clks_level_1[4].hw)
#define clk_div_rp_cpu_normal_1 (&sg2042_div_clks_level_1[5].hw)
#define clk_div_axi_ddr_0 (&sg2042_div_clks_level_1[6].hw)
#define clk_div_axi_ddr_1 (&sg2042_div_clks_level_1[7].hw)
static const struct clk_hw *clk_mux_ddr01_p[] = {
clk_div_ddr01_0,
clk_div_ddr01_1,
};
static const struct clk_hw *clk_mux_ddr23_p[] = {
clk_div_ddr23_0,
clk_div_ddr23_1,
};
static const struct clk_hw *clk_mux_rp_cpu_normal_p[] = {
clk_div_rp_cpu_normal_0,
clk_div_rp_cpu_normal_1,
};
static const struct clk_hw *clk_mux_axi_ddr_p[] = {
clk_div_axi_ddr_0,
clk_div_axi_ddr_1,
};
/* Mux clocks to be updated during probe/registration */
static const struct clk_hw *clk_mux_ddr01[] = { NULL };
static const struct clk_hw *clk_mux_ddr23[] = { NULL };
static const struct clk_hw *clk_mux_rp_cpu_normal[] = { NULL };
static const struct clk_hw *clk_mux_axi_ddr[] = { NULL };
static struct sg2042_mux_clock sg2042_mux_clks[] = {
SG2042_MUX(MUX_CLK_DDR01, "clk_mux_ddr01", clk_mux_ddr01_p,
CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT | CLK_MUX_READ_ONLY,
R_CLKSELREG0, 2, 1),
SG2042_MUX(MUX_CLK_DDR23, "clk_mux_ddr23", clk_mux_ddr23_p,
CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT | CLK_MUX_READ_ONLY,
R_CLKSELREG0, 3, 1),
SG2042_MUX(MUX_CLK_RP_CPU_NORMAL, "clk_mux_rp_cpu_normal", clk_mux_rp_cpu_normal_p,
CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT,
R_CLKSELREG0, 0, 1),
SG2042_MUX(MUX_CLK_AXI_DDR, "clk_mux_axi_ddr", clk_mux_axi_ddr_p,
CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT,
R_CLKSELREG0, 1, 1),
};
/* Aliases just for easy reading */
#define clk_div_top_rp_cmn_div2 (&sg2042_div_clks_level_2[0].hw)
#define clk_div_50m_a53 (&sg2042_div_clks_level_2[1].hw)
#define clk_div_timer1 (&sg2042_div_clks_level_2[2].hw)
#define clk_div_timer2 (&sg2042_div_clks_level_2[3].hw)
#define clk_div_timer3 (&sg2042_div_clks_level_2[4].hw)
#define clk_div_timer4 (&sg2042_div_clks_level_2[5].hw)
#define clk_div_timer5 (&sg2042_div_clks_level_2[6].hw)
#define clk_div_timer6 (&sg2042_div_clks_level_2[7].hw)
#define clk_div_timer7 (&sg2042_div_clks_level_2[8].hw)
#define clk_div_timer8 (&sg2042_div_clks_level_2[9].hw)
#define clk_div_uart_500m (&sg2042_div_clks_level_2[10].hw)
#define clk_div_ahb_lpc (&sg2042_div_clks_level_2[11].hw)
#define clk_div_efuse (&sg2042_div_clks_level_2[12].hw)
#define clk_div_tx_eth0 (&sg2042_div_clks_level_2[13].hw)
#define clk_div_ptp_ref_i_eth0 (&sg2042_div_clks_level_2[14].hw)
#define clk_div_ref_eth0 (&sg2042_div_clks_level_2[15].hw)
#define clk_div_emmc (&sg2042_div_clks_level_2[16].hw)
#define clk_div_sd (&sg2042_div_clks_level_2[17].hw)
#define clk_div_top_axi0 (&sg2042_div_clks_level_2[18].hw)
#define clk_div_100k_emmc (&sg2042_div_clks_level_2[19].hw)
#define clk_div_100k_sd (&sg2042_div_clks_level_2[20].hw)
#define clk_div_gpio_db (&sg2042_div_clks_level_2[21].hw)
#define clk_div_top_axi_hsperi (&sg2042_div_clks_level_2[22].hw)
static struct sg2042_divider_clock sg2042_div_clks_level_2[] = {
SG2042_DIV_HWS(DIV_CLK_FPLL_TOP_RP_CMN_DIV2,
"clk_div_top_rp_cmn_div2", clk_mux_rp_cpu_normal,
R_CLKDIVREG3, 16, 16, DEF_DIVFLAG, 2),
SG2042_DIV_FW(DIV_CLK_FPLL_50M_A53, "clk_div_50m_a53", "fpll",
R_CLKDIVREG2, 16, 8, DEF_DIVFLAG, 20),
/* downstream of div_50m_a53 */
SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER1, "clk_div_timer1", clk_div_50m_a53,
R_CLKDIVREG6, 16, 16, DEF_DIVFLAG, 1),
SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER2, "clk_div_timer2", clk_div_50m_a53,
R_CLKDIVREG7, 16, 16, DEF_DIVFLAG, 1),
SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER3, "clk_div_timer3", clk_div_50m_a53,
R_CLKDIVREG8, 16, 16, DEF_DIVFLAG, 1),
SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER4, "clk_div_timer4", clk_div_50m_a53,
R_CLKDIVREG9, 16, 16, DEF_DIVFLAG, 1),
SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER5, "clk_div_timer5", clk_div_50m_a53,
R_CLKDIVREG10, 16, 16, DEF_DIVFLAG, 1),
SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER6, "clk_div_timer6", clk_div_50m_a53,
R_CLKDIVREG11, 16, 16, DEF_DIVFLAG, 1),
SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER7, "clk_div_timer7", clk_div_50m_a53,
R_CLKDIVREG12, 16, 16, DEF_DIVFLAG, 1),
SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER8, "clk_div_timer8", clk_div_50m_a53,
R_CLKDIVREG13, 16, 16, DEF_DIVFLAG, 1),
/*
* Set clk_div_uart_500m as RO, because the width of CLKDIVREG4 is too
* narrow for us to produce 115200. Use UART internal divider directly.
*/
SG2042_DIV_FW_RO(DIV_CLK_FPLL_UART_500M, "clk_div_uart_500m", "fpll",
R_CLKDIVREG4, 16, 7, DEF_DIVFLAG, 2),
SG2042_DIV_FW(DIV_CLK_FPLL_AHB_LPC, "clk_div_ahb_lpc", "fpll",
R_CLKDIVREG5, 16, 16, DEF_DIVFLAG, 5),
SG2042_DIV_FW(DIV_CLK_FPLL_EFUSE, "clk_div_efuse", "fpll",
R_CLKDIVREG14, 16, 7, DEF_DIVFLAG, 40),
SG2042_DIV_FW(DIV_CLK_FPLL_TX_ETH0, "clk_div_tx_eth0", "fpll",
R_CLKDIVREG16, 16, 11, DEF_DIVFLAG, 8),
SG2042_DIV_FW(DIV_CLK_FPLL_PTP_REF_I_ETH0,
"clk_div_ptp_ref_i_eth0", "fpll",
R_CLKDIVREG17, 16, 8, DEF_DIVFLAG, 20),
SG2042_DIV_FW(DIV_CLK_FPLL_REF_ETH0, "clk_div_ref_eth0", "fpll",
R_CLKDIVREG18, 16, 8, DEF_DIVFLAG, 40),
SG2042_DIV_FW(DIV_CLK_FPLL_EMMC, "clk_div_emmc", "fpll",
R_CLKDIVREG19, 16, 5, DEF_DIVFLAG, 10),
SG2042_DIV_FW(DIV_CLK_FPLL_SD, "clk_div_sd", "fpll",
R_CLKDIVREG21, 16, 5, DEF_DIVFLAG, 10),
SG2042_DIV_FW(DIV_CLK_FPLL_TOP_AXI0, "clk_div_top_axi0", "fpll",
R_CLKDIVREG23, 16, 5, DEF_DIVFLAG, 10),
/* downstream of div_top_axi0 */
SG2042_DIV_HW(DIV_CLK_FPLL_100K_EMMC, "clk_div_100k_emmc", clk_div_top_axi0,
R_CLKDIVREG20, 16, 16, DEF_DIVFLAG, 1000),
SG2042_DIV_HW(DIV_CLK_FPLL_100K_SD, "clk_div_100k_sd", clk_div_top_axi0,
R_CLKDIVREG22, 16, 16, DEF_DIVFLAG, 1000),
SG2042_DIV_HW(DIV_CLK_FPLL_GPIO_DB, "clk_div_gpio_db", clk_div_top_axi0,
R_CLKDIVREG15, 16, 16, DEF_DIVFLAG, 1000),
SG2042_DIV_FW(DIV_CLK_FPLL_TOP_AXI_HSPERI,
"clk_div_top_axi_hsperi", "fpll",
R_CLKDIVREG24, 16, 5, DEF_DIVFLAG, 4),
};
/* Gate clocks to be updated during probe/registration */
static const struct clk_hw *clk_gate_rp_cpu_normal[] = { NULL };
static const struct clk_hw *clk_gate_top_rp_cmn_div2[] = { NULL };
static const struct sg2042_gate_clock sg2042_gate_clks_level_2[] = {
SG2042_GATE_HWS(GATE_CLK_DDR01, "clk_gate_ddr01", clk_mux_ddr01,
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
R_CLKENREG1, 14),
SG2042_GATE_HWS(GATE_CLK_DDR23, "clk_gate_ddr23", clk_mux_ddr23,
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
R_CLKENREG1, 15),
SG2042_GATE_HWS(GATE_CLK_RP_CPU_NORMAL,
"clk_gate_rp_cpu_normal", clk_mux_rp_cpu_normal,
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
R_CLKENREG0, 0),
SG2042_GATE_HWS(GATE_CLK_AXI_DDR, "clk_gate_axi_ddr", clk_mux_axi_ddr,
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
R_CLKENREG1, 13),
/* upon are gate clocks directly downstream of muxes */
/* downstream of clk_div_top_rp_cmn_div2 */
SG2042_GATE_HW(GATE_CLK_TOP_RP_CMN_DIV2,
"clk_gate_top_rp_cmn_div2", clk_div_top_rp_cmn_div2,
CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, R_CLKENREG0, 2),
SG2042_GATE_HWS(GATE_CLK_HSDMA, "clk_gate_hsdma", clk_gate_top_rp_cmn_div2,
CLK_SET_RATE_PARENT, R_CLKENREG1, 10),
/*
* downstream of clk_gate_rp_cpu_normal
*
* FIXME: there should be one 1/2 DIV between clk_gate_rp_cpu_normal
* and clk_gate_axi_pcie0/clk_gate_axi_pcie1.
* But the 1/2 DIV is fixed and no configurable register exported, so
* when reading from these two clocks, the rate value are still the
* same as that of clk_gate_rp_cpu_normal, it's not correct.
* This just affects the value read.
*/
SG2042_GATE_HWS(GATE_CLK_AXI_PCIE0,
"clk_gate_axi_pcie0", clk_gate_rp_cpu_normal,
CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, R_CLKENREG1, 8),
SG2042_GATE_HWS(GATE_CLK_AXI_PCIE1,
"clk_gate_axi_pcie1", clk_gate_rp_cpu_normal,
CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, R_CLKENREG1, 9),
/* downstream of div_50m_a53 */
SG2042_GATE_HW(GATE_CLK_A53_50M, "clk_gate_a53_50m", clk_div_50m_a53,
CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, R_CLKENREG0, 1),
SG2042_GATE_HW(GATE_CLK_TIMER1, "clk_gate_timer1", clk_div_timer1,
CLK_SET_RATE_PARENT, R_CLKENREG0, 12),
SG2042_GATE_HW(GATE_CLK_TIMER2, "clk_gate_timer2", clk_div_timer2,
CLK_SET_RATE_PARENT, R_CLKENREG0, 13),
SG2042_GATE_HW(GATE_CLK_TIMER3, "clk_gate_timer3", clk_div_timer3,
CLK_SET_RATE_PARENT, R_CLKENREG0, 14),
SG2042_GATE_HW(GATE_CLK_TIMER4, "clk_gate_timer4", clk_div_timer4,
CLK_SET_RATE_PARENT, R_CLKENREG0, 15),
SG2042_GATE_HW(GATE_CLK_TIMER5, "clk_gate_timer5", clk_div_timer5,
CLK_SET_RATE_PARENT, R_CLKENREG0, 16),
SG2042_GATE_HW(GATE_CLK_TIMER6, "clk_gate_timer6", clk_div_timer6,
CLK_SET_RATE_PARENT, R_CLKENREG0, 17),
SG2042_GATE_HW(GATE_CLK_TIMER7, "clk_gate_timer7", clk_div_timer7,
CLK_SET_RATE_PARENT, R_CLKENREG0, 18),
SG2042_GATE_HW(GATE_CLK_TIMER8, "clk_gate_timer8", clk_div_timer8,
CLK_SET_RATE_PARENT, R_CLKENREG0, 19),
/* gate clocks downstream from div clocks one-to-one */
SG2042_GATE_HW(GATE_CLK_UART_500M, "clk_gate_uart_500m", clk_div_uart_500m,
CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, R_CLKENREG0, 4),
SG2042_GATE_HW(GATE_CLK_AHB_LPC, "clk_gate_ahb_lpc", clk_div_ahb_lpc,
CLK_SET_RATE_PARENT, R_CLKENREG0, 7),
SG2042_GATE_HW(GATE_CLK_EFUSE, "clk_gate_efuse", clk_div_efuse,
CLK_SET_RATE_PARENT, R_CLKENREG0, 20),
SG2042_GATE_HW(GATE_CLK_TX_ETH0, "clk_gate_tx_eth0", clk_div_tx_eth0,
CLK_SET_RATE_PARENT, R_CLKENREG0, 30),
SG2042_GATE_HW(GATE_CLK_PTP_REF_I_ETH0,
"clk_gate_ptp_ref_i_eth0", clk_div_ptp_ref_i_eth0,
CLK_SET_RATE_PARENT, R_CLKENREG1, 0),
SG2042_GATE_HW(GATE_CLK_REF_ETH0, "clk_gate_ref_eth0", clk_div_ref_eth0,
CLK_SET_RATE_PARENT, R_CLKENREG1, 1),
SG2042_GATE_HW(GATE_CLK_EMMC_100M, "clk_gate_emmc", clk_div_emmc,
CLK_SET_RATE_PARENT, R_CLKENREG1, 3),
SG2042_GATE_HW(GATE_CLK_SD_100M, "clk_gate_sd", clk_div_sd,
CLK_SET_RATE_PARENT, R_CLKENREG1, 6),
/* downstream of clk_div_top_axi0 */
SG2042_GATE_HW(GATE_CLK_AHB_ROM, "clk_gate_ahb_rom", clk_div_top_axi0,
0, R_CLKENREG0, 8),
SG2042_GATE_HW(GATE_CLK_AHB_SF, "clk_gate_ahb_sf", clk_div_top_axi0,
0, R_CLKENREG0, 9),
SG2042_GATE_HW(GATE_CLK_AXI_SRAM, "clk_gate_axi_sram", clk_div_top_axi0,
CLK_IGNORE_UNUSED, R_CLKENREG0, 10),
SG2042_GATE_HW(GATE_CLK_APB_TIMER, "clk_gate_apb_timer", clk_div_top_axi0,
CLK_IGNORE_UNUSED, R_CLKENREG0, 11),
SG2042_GATE_HW(GATE_CLK_APB_EFUSE, "clk_gate_apb_efuse", clk_div_top_axi0,
0, R_CLKENREG0, 21),
SG2042_GATE_HW(GATE_CLK_APB_GPIO, "clk_gate_apb_gpio", clk_div_top_axi0,
0, R_CLKENREG0, 22),
SG2042_GATE_HW(GATE_CLK_APB_GPIO_INTR,
"clk_gate_apb_gpio_intr", clk_div_top_axi0,
CLK_IS_CRITICAL, R_CLKENREG0, 23),
SG2042_GATE_HW(GATE_CLK_APB_I2C, "clk_gate_apb_i2c", clk_div_top_axi0,
0, R_CLKENREG0, 26),
SG2042_GATE_HW(GATE_CLK_APB_WDT, "clk_gate_apb_wdt", clk_div_top_axi0,
0, R_CLKENREG0, 27),
SG2042_GATE_HW(GATE_CLK_APB_PWM, "clk_gate_apb_pwm", clk_div_top_axi0,
0, R_CLKENREG0, 28),
SG2042_GATE_HW(GATE_CLK_APB_RTC, "clk_gate_apb_rtc", clk_div_top_axi0,
0, R_CLKENREG0, 29),
SG2042_GATE_HW(GATE_CLK_TOP_AXI0, "clk_gate_top_axi0", clk_div_top_axi0,
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
R_CLKENREG1, 11),
/* downstream of DIV clocks which are sourced from clk_div_top_axi0 */
SG2042_GATE_HW(GATE_CLK_GPIO_DB, "clk_gate_gpio_db", clk_div_gpio_db,
CLK_SET_RATE_PARENT, R_CLKENREG0, 24),
SG2042_GATE_HW(GATE_CLK_100K_EMMC, "clk_gate_100k_emmc", clk_div_100k_emmc,
CLK_SET_RATE_PARENT, R_CLKENREG1, 4),
SG2042_GATE_HW(GATE_CLK_100K_SD, "clk_gate_100k_sd", clk_div_100k_sd,
CLK_SET_RATE_PARENT, R_CLKENREG1, 7),
/* downstream of clk_div_top_axi_hsperi */
SG2042_GATE_HW(GATE_CLK_SYSDMA_AXI,
"clk_gate_sysdma_axi", clk_div_top_axi_hsperi,
CLK_SET_RATE_PARENT, R_CLKENREG0, 3),
SG2042_GATE_HW(GATE_CLK_APB_UART,
"clk_gate_apb_uart", clk_div_top_axi_hsperi,
CLK_SET_RATE_PARENT, R_CLKENREG0, 5),
SG2042_GATE_HW(GATE_CLK_AXI_DBG_I2C,
"clk_gate_axi_dbg_i2c", clk_div_top_axi_hsperi,
CLK_SET_RATE_PARENT, R_CLKENREG0, 6),
SG2042_GATE_HW(GATE_CLK_APB_SPI,
"clk_gate_apb_spi", clk_div_top_axi_hsperi,
CLK_SET_RATE_PARENT, R_CLKENREG0, 25),
SG2042_GATE_HW(GATE_CLK_AXI_ETH0,
"clk_gate_axi_eth0", clk_div_top_axi_hsperi,
CLK_SET_RATE_PARENT, R_CLKENREG0, 31),
SG2042_GATE_HW(GATE_CLK_AXI_EMMC,
"clk_gate_axi_emmc", clk_div_top_axi_hsperi,
CLK_SET_RATE_PARENT, R_CLKENREG1, 2),
SG2042_GATE_HW(GATE_CLK_AXI_SD,
"clk_gate_axi_sd", clk_div_top_axi_hsperi,
CLK_SET_RATE_PARENT, R_CLKENREG1, 5),
SG2042_GATE_HW(GATE_CLK_TOP_AXI_HSPERI,
"clk_gate_top_axi_hsperi", clk_div_top_axi_hsperi,
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
R_CLKENREG1, 12),
};
static DEFINE_SPINLOCK(sg2042_clk_lock);
static int sg2042_clk_register_divs(struct device *dev,
struct sg2042_clk_data *clk_data,
struct sg2042_divider_clock div_clks[],
int num_div_clks)
{
struct sg2042_divider_clock *div;
struct clk_hw *hw;
int i, ret = 0;
for (i = 0; i < num_div_clks; i++) {
div = &div_clks[i];
if (div->div_flags & CLK_DIVIDER_HIWORD_MASK) {
if (div->width + div->shift > 16) {
pr_warn("divider value exceeds LOWORD field\n");
ret = -EINVAL;
break;
}
}
div->reg = clk_data->iobase + div->offset_ctrl;
div->lock = &sg2042_clk_lock;
hw = &div->hw;
ret = devm_clk_hw_register(dev, hw);
if (ret) {
pr_err("failed to register clock %s\n", div->hw.init->name);
break;
}
clk_data->onecell_data.hws[div->id] = hw;
}
return ret;
}
static int sg2042_clk_register_gates(struct device *dev,
struct sg2042_clk_data *clk_data,
const struct sg2042_gate_clock gate_clks[],
int num_gate_clks)
{
const struct sg2042_gate_clock *gate;
struct clk_hw *hw;
int i, ret = 0;
for (i = 0; i < num_gate_clks; i++) {
gate = &gate_clks[i];
hw = __devm_clk_hw_register_gate
(dev,
NULL,
gate->hw.init->name,
NULL,
gate->hw.init->parent_hws[0],
NULL,
gate->hw.init->flags,
clk_data->iobase + gate->offset_enable,
gate->bit_idx,
0,
&sg2042_clk_lock);
if (IS_ERR(hw)) {
pr_err("failed to register clock %s\n", gate->hw.init->name);
ret = PTR_ERR(hw);
break;
}
clk_data->onecell_data.hws[gate->id] = hw;
/* Updated some clocks which take the role of parent */
switch (gate->id) {
case GATE_CLK_RP_CPU_NORMAL:
*clk_gate_rp_cpu_normal = hw;
break;
case GATE_CLK_TOP_RP_CMN_DIV2:
*clk_gate_top_rp_cmn_div2 = hw;
break;
}
}
return ret;
}
static int sg2042_clk_register_gates_fw(struct device *dev,
struct sg2042_clk_data *clk_data,
const struct sg2042_gate_clock gate_clks[],
int num_gate_clks)
{
const struct sg2042_gate_clock *gate;
struct clk_hw *hw;
int i, ret = 0;
for (i = 0; i < num_gate_clks; i++) {
gate = &gate_clks[i];
hw = devm_clk_hw_register_gate_parent_data
(dev,
gate->hw.init->name,
gate->hw.init->parent_data,
gate->hw.init->flags,
clk_data->iobase + gate->offset_enable,
gate->bit_idx,
0,
&sg2042_clk_lock);
if (IS_ERR(hw)) {
pr_err("failed to register clock %s\n", gate->hw.init->name);
ret = PTR_ERR(hw);
break;
}
clk_data->onecell_data.hws[gate->id] = hw;
/* Updated some clocks which take the role of parent */
switch (gate->id) {
case GATE_CLK_DDR01_DIV0:
*clk_gate_ddr01_div0 = hw;
break;
case GATE_CLK_DDR01_DIV1:
*clk_gate_ddr01_div1 = hw;
break;
case GATE_CLK_DDR23_DIV0:
*clk_gate_ddr23_div0 = hw;
break;
case GATE_CLK_DDR23_DIV1:
*clk_gate_ddr23_div1 = hw;
break;
case GATE_CLK_RP_CPU_NORMAL_DIV0:
*clk_gate_rp_cpu_normal_div0 = hw;
break;
case GATE_CLK_RP_CPU_NORMAL_DIV1:
*clk_gate_rp_cpu_normal_div1 = hw;
break;
case GATE_CLK_AXI_DDR_DIV0:
*clk_gate_axi_ddr_div0 = hw;
break;
case GATE_CLK_AXI_DDR_DIV1:
*clk_gate_axi_ddr_div1 = hw;
break;
}
}
return ret;
}
static int sg2042_mux_notifier_cb(struct notifier_block *nb,
unsigned long event,
void *data)
{
struct sg2042_mux_clock *mux = to_sg2042_mux_nb(nb);
const struct clk_ops *ops = &clk_mux_ops;
struct clk_notifier_data *ndata = data;
struct clk_hw *hw;
int ret = 0;
hw = __clk_get_hw(ndata->clk);
/* To switch to fpll before changing rate and restore after that */
if (event == PRE_RATE_CHANGE) {
mux->original_index = ops->get_parent(hw);
/*
* "1" is the array index of the second parent input source of
* mux. For SG2042, it's fpll for all mux clocks.
* "0" is the array index of the frist parent input source of
* mux, For SG2042, it's mpll.
* FIXME, any good idea to avoid magic number?
*/
if (mux->original_index == 0)
ret = ops->set_parent(hw, 1);
} else if (event == POST_RATE_CHANGE) {
ret = ops->set_parent(hw, mux->original_index);
}
return notifier_from_errno(ret);
}
static int sg2042_clk_register_muxs(struct device *dev,
struct sg2042_clk_data *clk_data,
struct sg2042_mux_clock mux_clks[],
int num_mux_clks)
{
struct sg2042_mux_clock *mux;
struct clk_hw *hw;
int i, ret = 0;
for (i = 0; i < num_mux_clks; i++) {
mux = &mux_clks[i];
hw = __devm_clk_hw_register_mux
(dev,
NULL,
mux->hw.init->name,
mux->hw.init->num_parents,
NULL,
mux->hw.init->parent_hws,
NULL,
mux->hw.init->flags,
clk_data->iobase + mux->offset_select,
mux->shift,
BIT(mux->width) - 1,
0,
sg2042_mux_table,
&sg2042_clk_lock);
if (IS_ERR(hw)) {
pr_err("failed to register clock %s\n", mux->hw.init->name);
ret = PTR_ERR(hw);
break;
}
clk_data->onecell_data.hws[mux->id] = hw;
/* Updated some clocks which takes the role of parent */
switch (mux->id) {
case MUX_CLK_DDR01:
*clk_mux_ddr01 = hw;
break;
case MUX_CLK_DDR23:
*clk_mux_ddr23 = hw;
break;
case MUX_CLK_RP_CPU_NORMAL:
*clk_mux_rp_cpu_normal = hw;
break;
case MUX_CLK_AXI_DDR:
*clk_mux_axi_ddr = hw;
break;
}
/*
* FIXME: Theoretically, we should set parent for the
* mux, but seems hardware has done this for us with
* default value, so we don't set parent again here.
*/
if (!(mux->hw.init->flags & CLK_MUX_READ_ONLY)) {
mux->clk_nb.notifier_call = sg2042_mux_notifier_cb;
ret = devm_clk_notifier_register(dev, hw->clk, &mux->clk_nb);
if (ret) {
pr_err("failed to register clock notifier for %s\n",
mux->hw.init->name);
break;
}
}
}
return ret;
}
static int sg2042_init_clkdata(struct platform_device *pdev,
int num_clks,
struct sg2042_clk_data **pp_clk_data)
{
struct sg2042_clk_data *clk_data = NULL;
clk_data = devm_kzalloc(&pdev->dev,
struct_size(clk_data, onecell_data.hws, num_clks),
GFP_KERNEL);
if (!clk_data)
return -ENOMEM;
clk_data->iobase = devm_platform_ioremap_resource(pdev, 0);
if (WARN_ON(IS_ERR(clk_data->iobase)))
return PTR_ERR(clk_data->iobase);
clk_data->onecell_data.num = num_clks;
*pp_clk_data = clk_data;
return 0;
}
static int sg2042_clkgen_probe(struct platform_device *pdev)
{
struct sg2042_clk_data *clk_data = NULL;
int num_clks;
int ret;
num_clks = ARRAY_SIZE(sg2042_div_clks_level_1) +
ARRAY_SIZE(sg2042_div_clks_level_2) +
ARRAY_SIZE(sg2042_gate_clks_level_1) +
ARRAY_SIZE(sg2042_gate_clks_level_2) +
ARRAY_SIZE(sg2042_mux_clks);
ret = sg2042_init_clkdata(pdev, num_clks, &clk_data);
if (ret)
goto error_out;
/* level-1 gates */
ret = sg2042_clk_register_gates_fw(&pdev->dev, clk_data,
sg2042_gate_clks_level_1,
ARRAY_SIZE(sg2042_gate_clks_level_1));
if (ret)
goto error_out;
/* level-1 div */
ret = sg2042_clk_register_divs(&pdev->dev, clk_data, sg2042_div_clks_level_1,
ARRAY_SIZE(sg2042_div_clks_level_1));
if (ret)
goto error_out;
/* mux */
ret = sg2042_clk_register_muxs(&pdev->dev, clk_data, sg2042_mux_clks,
ARRAY_SIZE(sg2042_mux_clks));
if (ret)
goto error_out;
/* level 2 div */
ret = sg2042_clk_register_divs(&pdev->dev, clk_data, sg2042_div_clks_level_2,
ARRAY_SIZE(sg2042_div_clks_level_2));
if (ret)
goto error_out;
/* level 2 gate */
ret = sg2042_clk_register_gates(&pdev->dev, clk_data, sg2042_gate_clks_level_2,
ARRAY_SIZE(sg2042_gate_clks_level_2));
if (ret)
goto error_out;
return devm_of_clk_add_hw_provider(&pdev->dev,
of_clk_hw_onecell_get,
&clk_data->onecell_data);
error_out:
pr_err("%s failed error number %d\n", __func__, ret);
return ret;
}
static const struct of_device_id sg2042_clkgen_match[] = {
{ .compatible = "sophgo,sg2042-clkgen" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sg2042_clkgen_match);
static struct platform_driver sg2042_clkgen_driver = {
.probe = sg2042_clkgen_probe,
.driver = {
.name = "clk-sophgo-sg2042-clkgen",
.of_match_table = sg2042_clkgen_match,
.suppress_bind_attrs = true,
},
};
module_platform_driver(sg2042_clkgen_driver);
MODULE_AUTHOR("Chen Wang");
MODULE_DESCRIPTION("Sophgo SG2042 clock generator driver");
MODULE_LICENSE("GPL");