// 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(s