Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
# SPDX-License-Identifier: GPL-2.0
# common clock support for SOPHGO SoC family.
config CLK_SOPHGO_CV1800
tristate "Support for the Sophgo CV1800 series SoCs clock controller"
depends on ARCH_SOPHGO || COMPILE_TEST
help
This driver supports clock controller of Sophgo CV18XX series SoC.
The driver require a 25MHz Oscillator to function generate clock.
It includes PLLs, common clock function and some vendor clock for
IPs of CV18XX series SoC
config CLK_SOPHGO_SG2042_PLL
tristate "Sophgo SG2042 PLL clock support"
depends on ARCH_SOPHGO || COMPILE_TEST
help
This driver supports the PLL clock controller on the
Sophgo SG2042 SoC. This clock IP uses three oscillators with
frequency of 25 MHz as input, which are used for Main/Fixed
PLL, DDR PLL 0 and DDR PLL 1 respectively.
config CLK_SOPHGO_SG2042_CLKGEN
tristate "Sophgo SG2042 Clock Generator support"
depends on CLK_SOPHGO_SG2042_PLL
help
This driver supports the Clock Generator on the
Sophgo SG2042 SoC. This clock IP depends on SG2042 PLL clock
because it uses PLL clocks as input.
This driver provides clock function such as DIV/Mux/Gate.
config CLK_SOPHGO_SG2042_RPGATE
tristate "Sophgo SG2042 RP subsystem clock controller support"
depends on CLK_SOPHGO_SG2042_CLKGEN
help
This driver supports the RP((Riscv Processors)) subsystem clock
controller on the Sophgo SG2042 SoC.
This clock IP depends on SG2042 Clock Generator because it uses
clock from Clock Generator IP as input.
This driver provides Gate function for RP.
config CLK_SOPHGO_SG2044
tristate "Sophgo SG2044 clock controller support"
depends on ARCH_SOPHGO || COMPILE_TEST
help
This driver supports the clock controller on the Sophgo SG2044
SoC. This controller requires mulitple PLL clock as input.
This clock control provides PLL clocks and common clock function
for various IPs on the SoC.
config CLK_SOPHGO_SG2044_PLL
tristate "Sophgo SG2044 PLL clock controller support"
depends on ARCH_SOPHGO || COMPILE_TEST
select MFD_SYSCON
select REGMAP_MMIO
help
This driver supports the PLL clock controller on the Sophgo
SG2044 SoC. This controller requires 25M oscillator as input.
This clock control provides PLL clocks on the SoC.
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CLK_SOPHGO_CV1800) += clk-sophgo-cv1800.o
clk-sophgo-cv1800-y += clk-cv1800.o
clk-sophgo-cv1800-y += clk-cv18xx-common.o
clk-sophgo-cv1800-y += clk-cv18xx-ip.o
clk-sophgo-cv1800-y += clk-cv18xx-pll.o
obj-$(CONFIG_CLK_SOPHGO_SG2042_CLKGEN) += clk-sg2042-clkgen.o
obj-$(CONFIG_CLK_SOPHGO_SG2042_PLL) += clk-sg2042-pll.o
obj-$(CONFIG_CLK_SOPHGO_SG2042_RPGATE) += clk-sg2042-rpgate.o
obj-$(CONFIG_CLK_SOPHGO_SG2044) += clk-sg2044.o
obj-$(CONFIG_CLK_SOPHGO_SG2044_PLL) += clk-sg2044-pll.o
File diff suppressed because it is too large Load Diff
+123
View File
@@ -0,0 +1,123 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Inochi Amaoto <inochiama@outlook.com>
*/
#ifndef _CLK_SOPHGO_CV1800_H_
#define _CLK_SOPHGO_CV1800_H_
#include <dt-bindings/clock/sophgo,cv1800.h>
#define CV1800_CLK_MAX (CLK_XTAL_AP + 1)
#define CV1810_CLK_MAX (CLK_DISP_SRC_VIP + 1)
#define REG_PLL_G2_CTRL 0x800
#define REG_PLL_G2_STATUS 0x804
#define REG_MIPIMPLL_CSR 0x808
#define REG_A0PLL_CSR 0x80C
#define REG_DISPPLL_CSR 0x810
#define REG_CAM0PLL_CSR 0x814
#define REG_CAM1PLL_CSR 0x818
#define REG_PLL_G2_SSC_SYN_CTRL 0x840
#define REG_A0PLL_SSC_SYN_CTRL 0x850
#define REG_A0PLL_SSC_SYN_SET 0x854
#define REG_A0PLL_SSC_SYN_SPAN 0x858
#define REG_A0PLL_SSC_SYN_STEP 0x85C
#define REG_DISPPLL_SSC_SYN_CTRL 0x860
#define REG_DISPPLL_SSC_SYN_SET 0x864
#define REG_DISPPLL_SSC_SYN_SPAN 0x868
#define REG_DISPPLL_SSC_SYN_STEP 0x86C
#define REG_CAM0PLL_SSC_SYN_CTRL 0x870
#define REG_CAM0PLL_SSC_SYN_SET 0x874
#define REG_CAM0PLL_SSC_SYN_SPAN 0x878
#define REG_CAM0PLL_SSC_SYN_STEP 0x87C
#define REG_CAM1PLL_SSC_SYN_CTRL 0x880
#define REG_CAM1PLL_SSC_SYN_SET 0x884
#define REG_CAM1PLL_SSC_SYN_SPAN 0x888
#define REG_CAM1PLL_SSC_SYN_STEP 0x88C
#define REG_APLL_FRAC_DIV_CTRL 0x890
#define REG_APLL_FRAC_DIV_M 0x894
#define REG_APLL_FRAC_DIV_N 0x898
#define REG_MIPIMPLL_CLK_CSR 0x8A0
#define REG_A0PLL_CLK_CSR 0x8A4
#define REG_DISPPLL_CLK_CSR 0x8A8
#define REG_CAM0PLL_CLK_CSR 0x8AC
#define REG_CAM1PLL_CLK_CSR 0x8B0
#define REG_CLK_CAM0_SRC_DIV 0x8C0
#define REG_CLK_CAM1_SRC_DIV 0x8C4
/* top_pll_g6 */
#define REG_PLL_G6_CTRL 0x900
#define REG_PLL_G6_STATUS 0x904
#define REG_MPLL_CSR 0x908
#define REG_TPLL_CSR 0x90C
#define REG_FPLL_CSR 0x910
#define REG_PLL_G6_SSC_SYN_CTRL 0x940
#define REG_DPLL_SSC_SYN_CTRL 0x950
#define REG_DPLL_SSC_SYN_SET 0x954
#define REG_DPLL_SSC_SYN_SPAN 0x958
#define REG_DPLL_SSC_SYN_STEP 0x95C
#define REG_MPLL_SSC_SYN_CTRL 0x960
#define REG_MPLL_SSC_SYN_SET 0x964
#define REG_MPLL_SSC_SYN_SPAN 0x968
#define REG_MPLL_SSC_SYN_STEP 0x96C
#define REG_TPLL_SSC_SYN_CTRL 0x970
#define REG_TPLL_SSC_SYN_SET 0x974
#define REG_TPLL_SSC_SYN_SPAN 0x978
#define REG_TPLL_SSC_SYN_STEP 0x97C
/* clkgen */
#define REG_CLK_EN_0 0x000
#define REG_CLK_EN_1 0x004
#define REG_CLK_EN_2 0x008
#define REG_CLK_EN_3 0x00C
#define REG_CLK_EN_4 0x010
#define REG_CLK_SEL_0 0x020
#define REG_CLK_BYP_0 0x030
#define REG_CLK_BYP_1 0x034
#define REG_DIV_CLK_A53_0 0x040
#define REG_DIV_CLK_A53_1 0x044
#define REG_DIV_CLK_CPU_AXI0 0x048
#define REG_DIV_CLK_CPU_GIC 0x050
#define REG_DIV_CLK_TPU 0x054
#define REG_DIV_CLK_EMMC 0x064
#define REG_DIV_CLK_EMMC_100K 0x06C
#define REG_DIV_CLK_SD0 0x070
#define REG_DIV_CLK_SD0_100K 0x078
#define REG_DIV_CLK_SD1 0x07C
#define REG_DIV_CLK_SD1_100K 0x084
#define REG_DIV_CLK_SPI_NAND 0x088
#define REG_DIV_CLK_ETH0_500M 0x08C
#define REG_DIV_CLK_ETH1_500M 0x090
#define REG_DIV_CLK_GPIO_DB 0x094
#define REG_DIV_CLK_SDMA_AUD0 0x098
#define REG_DIV_CLK_SDMA_AUD1 0x09C
#define REG_DIV_CLK_SDMA_AUD2 0x0A0
#define REG_DIV_CLK_SDMA_AUD3 0x0A4
#define REG_DIV_CLK_CAM0_200 0x0A8
#define REG_DIV_CLK_AXI4 0x0B8
#define REG_DIV_CLK_AXI6 0x0BC
#define REG_DIV_CLK_DSI_ESC 0x0C4
#define REG_DIV_CLK_AXI_VIP 0x0C8
#define REG_DIV_CLK_SRC_VIP_SYS_0 0x0D0
#define REG_DIV_CLK_SRC_VIP_SYS_1 0x0D8
#define REG_DIV_CLK_DISP_SRC_VIP 0x0E0
#define REG_DIV_CLK_AXI_VIDEO_CODEC 0x0E4
#define REG_DIV_CLK_VC_SRC0 0x0EC
#define REG_DIV_CLK_1M 0x0FC
#define REG_DIV_CLK_SPI 0x100
#define REG_DIV_CLK_I2C 0x104
#define REG_DIV_CLK_SRC_VIP_SYS_2 0x110
#define REG_DIV_CLK_AUDSRC 0x118
#define REG_DIV_CLK_PWM_SRC_0 0x120
#define REG_DIV_CLK_AP_DEBUG 0x128
#define REG_DIV_CLK_RTCSYS_SRC_0 0x12C
#define REG_DIV_CLK_C906_0_0 0x130
#define REG_DIV_CLK_C906_0_1 0x134
#define REG_DIV_CLK_C906_1_0 0x138
#define REG_DIV_CLK_C906_1_1 0x13C
#define REG_DIV_CLK_SRC_VIP_SYS_3 0x140
#define REG_DIV_CLK_SRC_VIP_SYS_4 0x144
#endif /* _CLK_SOPHGO_CV1800_H_ */
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2023 Inochi Amaoto <inochiama@outlook.com>
*/
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/spinlock.h>
#include <linux/bug.h>
#include "clk-cv18xx-common.h"
int cv1800_clk_setbit(struct cv1800_clk_common *common,
struct cv1800_clk_regbit *field)
{
u32 mask = BIT(field->shift);
u32 value;
unsigned long flags;
spin_lock_irqsave(common->lock, flags);
value = readl(common->base + field->reg);
writel(value | mask, common->base + field->reg);
spin_unlock_irqrestore(common->lock, flags);
return 0;
}
int cv1800_clk_clearbit(struct cv1800_clk_common *common,
struct cv1800_clk_regbit *field)
{
u32 mask = BIT(field->shift);
u32 value;
unsigned long flags;
spin_lock_irqsave(common->lock, flags);
value = readl(common->base + field->reg);
writel(value & ~mask, common->base + field->reg);
spin_unlock_irqrestore(common->lock, flags);
return 0;
}
int cv1800_clk_checkbit(struct cv1800_clk_common *common,
struct cv1800_clk_regbit *field)
{
return readl(common->base + field->reg) & BIT(field->shift);
}
#define PLL_LOCK_TIMEOUT_US (200 * 1000)
void cv1800_clk_wait_for_lock(struct cv1800_clk_common *common,
u32 reg, u32 lock)
{
void __iomem *addr = common->base + reg;
u32 regval;
if (!lock)
return;
WARN_ON(readl_relaxed_poll_timeout(addr, regval, regval & lock,
100, PLL_LOCK_TIMEOUT_US));
}
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Inochi Amaoto <inochiama@outlook.com>
*/
#ifndef _CLK_SOPHGO_CV18XX_IP_H_
#define _CLK_SOPHGO_CV18XX_IP_H_
#include <linux/compiler.h>
#include <linux/clk-provider.h>
#include <linux/bitfield.h>
struct cv1800_clk_common {
void __iomem *base;
spinlock_t *lock;
struct clk_hw hw;
unsigned long features;
};
#define CV1800_CLK_COMMON(_name, _parents, _op, _flags) \
{ \
.hw.init = CLK_HW_INIT_PARENTS_DATA(_name, _parents, \
_op, _flags), \
}
static inline struct cv1800_clk_common *
hw_to_cv1800_clk_common(struct clk_hw *hw)
{
return container_of(hw, struct cv1800_clk_common, hw);
}
struct cv1800_clk_regbit {
u16 reg;
s8 shift;
};
struct cv1800_clk_regfield {
u16 reg;
u8 shift;
u8 width;
s16 initval;
unsigned long flags;
};
#define CV1800_CLK_BIT(_reg, _shift) \
{ \
.reg = _reg, \
.shift = _shift, \
}
#define CV1800_CLK_REG(_reg, _shift, _width, _initval, _flags) \
{ \
.reg = _reg, \
.shift = _shift, \
.width = _width, \
.initval = _initval, \
.flags = _flags, \
}
#define cv1800_clk_regfield_genmask(_reg) \
GENMASK((_reg)->shift + (_reg)->width - 1, (_reg)->shift)
#define cv1800_clk_regfield_get(_val, _reg) \
(((_val) >> (_reg)->shift) & GENMASK((_reg)->width - 1, 0))
#define cv1800_clk_regfield_set(_val, _new, _reg) \
(((_val) & ~cv1800_clk_regfield_genmask((_reg))) | \
(((_new) & GENMASK((_reg)->width - 1, 0)) << (_reg)->shift))
#define _CV1800_SET_FIELD(_reg, _val, _field) \
(((_reg) & ~(_field)) | FIELD_PREP((_field), (_val)))
int cv1800_clk_setbit(struct cv1800_clk_common *common,
struct cv1800_clk_regbit *field);
int cv1800_clk_clearbit(struct cv1800_clk_common *common,
struct cv1800_clk_regbit *field);
int cv1800_clk_checkbit(struct cv1800_clk_common *common,
struct cv1800_clk_regbit *field);
void cv1800_clk_wait_for_lock(struct cv1800_clk_common *common,
u32 reg, u32 lock);
#endif // _CLK_SOPHGO_CV18XX_IP_H_
@@ -0,0 +1,905 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2023 Inochi Amaoto <inochiama@outlook.com>
*/
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/gcd.h>
#include <linux/spinlock.h>
#include "clk-cv18xx-ip.h"
/* GATE */
static inline struct cv1800_clk_gate *hw_to_cv1800_clk_gate(struct clk_hw *hw)
{
struct cv1800_clk_common *common = hw_to_cv1800_clk_common(hw);
return container_of(common, struct cv1800_clk_gate, common);
}
static int gate_enable(struct clk_hw *hw)
{
struct cv1800_clk_gate *gate = hw_to_cv1800_clk_gate(hw);
return cv1800_clk_setbit(&gate->common, &gate->gate);
}
static void gate_disable(struct clk_hw *hw)
{
struct cv1800_clk_gate *gate = hw_to_cv1800_clk_gate(hw);
cv1800_clk_clearbit(&gate->common, &gate->gate);
}
static int gate_is_enabled(struct clk_hw *hw)
{
struct cv1800_clk_gate *gate = hw_to_cv1800_clk_gate(hw);
return cv1800_clk_checkbit(&gate->common, &gate->gate);
}
static unsigned long gate_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return parent_rate;
}
static int gate_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
req->rate = req->best_parent_rate;
return 0;
}
static int gate_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
return 0;
}
const struct clk_ops cv1800_clk_gate_ops = {
.disable = gate_disable,
.enable = gate_enable,
.is_enabled = gate_is_enabled,
.recalc_rate = gate_recalc_rate,
.determine_rate = gate_determine_rate,
.set_rate = gate_set_rate,
};
/* DIV */
#define _DIV_EN_CLK_DIV_FACTOR_FIELD BIT(3)
#define DIV_GET_EN_CLK_DIV_FACTOR(_reg) \
FIELD_GET(_DIV_EN_CLK_DIV_FACTOR_FIELD, _reg)
#define DIV_SET_EN_DIV_FACTOR(_reg) \
_CV1800_SET_FIELD(_reg, 1, _DIV_EN_CLK_DIV_FACTOR_FIELD)
static inline struct cv1800_clk_div *hw_to_cv1800_clk_div(struct clk_hw *hw)
{
struct cv1800_clk_common *common = hw_to_cv1800_clk_common(hw);
return container_of(common, struct cv1800_clk_div, common);
}
static int div_enable(struct clk_hw *hw)
{
struct cv1800_clk_div *div = hw_to_cv1800_clk_div(hw);
return cv1800_clk_setbit(&div->common, &div->gate);
}
static void div_disable(struct clk_hw *hw)
{
struct cv1800_clk_div *div = hw_to_cv1800_clk_div(hw);
cv1800_clk_clearbit(&div->common, &div->gate);
}
static int div_is_enabled(struct clk_hw *hw)
{
struct cv1800_clk_div *div = hw_to_cv1800_clk_div(hw);
return cv1800_clk_checkbit(&div->common, &div->gate);
}
static int div_helper_set_rate(struct cv1800_clk_common *common,
struct cv1800_clk_regfield *div,
unsigned long val)
{
unsigned long flags;
u32 reg;
if (div->width == 0)
return 0;
spin_lock_irqsave(common->lock, flags);
reg = readl(common->base + div->reg);
reg = cv1800_clk_regfield_set(reg, val, div);
if (div->initval > 0)
reg = DIV_SET_EN_DIV_FACTOR(reg);
writel(reg, common->base + div->reg);
spin_unlock_irqrestore(common->lock, flags);
return 0;
}
static u32 div_helper_get_clockdiv(struct cv1800_clk_common *common,
struct cv1800_clk_regfield *div)
{
u32 clockdiv = 1;
u32 reg;
if (!div || div->initval < 0 || (div->width == 0 && div->initval <= 0))
return 1;
if (div->width == 0 && div->initval > 0)
return div->initval;
reg = readl(common->base + div->reg);
if (div->initval == 0 || DIV_GET_EN_CLK_DIV_FACTOR(reg))
clockdiv = cv1800_clk_regfield_get(reg, div);
else if (div->initval > 0)
clockdiv = div->initval;
return clockdiv;
}
static int div_helper_determine_rate(struct cv1800_clk_regfield *div,
struct clk_hw *hw,
struct clk_rate_request *req)
{
if (div->width == 0) {
if (div->initval <= 0)
req->rate = DIV_ROUND_UP_ULL(req->best_parent_rate, 1);
else
req->rate = DIV_ROUND_UP_ULL(req->best_parent_rate, div->initval);
return 0;
}
return divider_determine_rate(hw, req, NULL, div->width, div->flags);
}
static int do_div_determine_rate(struct clk_rate_request *req, int id, void *data)
{
struct cv1800_clk_div *div = data;
return div_helper_determine_rate(&div->div, &div->common.hw, req);
}
static bool div_is_better_rate(struct cv1800_clk_common *common,
unsigned long target, unsigned long now,
unsigned long best)
{
if (common->features & CLK_DIVIDER_ROUND_CLOSEST)
return abs_diff(target, now) < abs_diff(target, best);
return now <= target && now > best;
}
static int mux_helper_determine_rate(struct cv1800_clk_common *common,
struct clk_rate_request *req,
int (*round)(struct clk_rate_request *,
int,
void *),
void *data)
{
unsigned long best_parent_rate = 0, best_rate = 0;
struct clk_hw *best_parent, *hw = &common->hw;
unsigned int i;
int ret;
if (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT) {
struct clk_rate_request tmp_req = *req;
best_parent = clk_hw_get_parent(hw);
tmp_req.best_parent_hw = best_parent;
tmp_req.best_parent_rate = clk_hw_get_rate(best_parent);
ret = round(&tmp_req, -1, data);
if (ret)
return ret;
best_parent_rate = tmp_req.best_parent_rate;
best_rate = tmp_req.rate;
goto find;
}
for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
struct clk_rate_request tmp_req = *req;
struct clk_hw *parent;
parent = clk_hw_get_parent_by_index(hw, i);
if (!parent)
continue;
tmp_req.best_parent_hw = parent;
tmp_req.best_parent_rate = clk_hw_get_rate(parent);
ret = round(&tmp_req, i, data);
if (ret)
continue;
if (tmp_req.rate == req->rate) {
best_parent = parent;
best_parent_rate = tmp_req.best_parent_rate;
best_rate = tmp_req.rate;
goto find;
}
if (div_is_better_rate(common, req->rate,
tmp_req.rate, best_rate)) {
best_parent = parent;
best_parent_rate = tmp_req.best_parent_rate;
best_rate = tmp_req.rate;
}
}
if (best_rate == 0)
return -EINVAL;
find:
req->best_parent_hw = best_parent;
req->best_parent_rate = best_parent_rate;
req->rate = best_rate;
return 0;
}
static int div_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct cv1800_clk_div *div = hw_to_cv1800_clk_div(hw);
return mux_helper_determine_rate(&div->common, req,
do_div_determine_rate, div);
}
static unsigned long div_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct cv1800_clk_div *div = hw_to_cv1800_clk_div(hw);
unsigned long val;
val = div_helper_get_clockdiv(&div->common, &div->div);
if (val == 0)
return 0;
return divider_recalc_rate(hw, parent_rate, val, NULL,
div->div.flags, div->div.width);
}
static int div_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct cv1800_clk_div *div = hw_to_cv1800_clk_div(hw);
unsigned long val;
val = divider_get_val(rate, parent_rate, NULL,
div->div.width, div->div.flags);
return div_helper_set_rate(&div->common, &div->div, val);
}
const struct clk_ops cv1800_clk_div_ops = {
.disable = div_disable,
.enable = div_enable,
.is_enabled = div_is_enabled,
.determine_rate = div_determine_rate,
.recalc_rate = div_recalc_rate,
.set_rate = div_set_rate,
};
static inline struct cv1800_clk_bypass_div *
hw_to_cv1800_clk_bypass_div(struct clk_hw *hw)
{
struct cv1800_clk_div *div = hw_to_cv1800_clk_div(hw);
return container_of(div, struct cv1800_clk_bypass_div, div);
}
static int do_bypass_div_determine_rate(struct clk_rate_request *req, int id,
void *data)
{
struct cv1800_clk_bypass_div *div = data;
if (id == -1) {
if (cv1800_clk_checkbit(&div->div.common, &div->bypass)) {
req->rate = req->best_parent_rate;
return 0;
}
return do_div_determine_rate(req, -1, &div->div);
}
if (id == 0) {
req->rate = req->best_parent_rate;
return 0;
}
return do_div_determine_rate(req, id - 1, &div->div);
}
static int bypass_div_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct cv1800_clk_bypass_div *div = hw_to_cv1800_clk_bypass_div(hw);
return mux_helper_determine_rate(&div->div.common, req,
do_bypass_div_determine_rate, div);
}
static unsigned long bypass_div_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct cv1800_clk_bypass_div *div = hw_to_cv1800_clk_bypass_div(hw);
if (cv1800_clk_checkbit(&div->div.common, &div->bypass))
return parent_rate;
return div_recalc_rate(hw, parent_rate);
}
static int bypass_div_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct cv1800_clk_bypass_div *div = hw_to_cv1800_clk_bypass_div(hw);
if (cv1800_clk_checkbit(&div->div.common, &div->bypass))
return 0;
return div_set_rate(hw, rate, parent_rate);
}
static u8 bypass_div_get_parent(struct clk_hw *hw)
{
struct cv1800_clk_bypass_div *div = hw_to_cv1800_clk_bypass_div(hw);
if (cv1800_clk_checkbit(&div->div.common, &div->bypass))
return 0;
return 1;
}
static int bypass_div_set_parent(struct clk_hw *hw, u8 index)
{
struct cv1800_clk_bypass_div *div = hw_to_cv1800_clk_bypass_div(hw);
if (index)
return cv1800_clk_clearbit(&div->div.common, &div->bypass);
return cv1800_clk_setbit(&div->div.common, &div->bypass);
}
const struct clk_ops cv1800_clk_bypass_div_ops = {
.disable = div_disable,
.enable = div_enable,
.is_enabled = div_is_enabled,
.determine_rate = bypass_div_determine_rate,
.recalc_rate = bypass_div_recalc_rate,
.set_rate = bypass_div_set_rate,
.set_parent = bypass_div_set_parent,
.get_parent = bypass_div_get_parent,
};
/* MUX */
static inline struct cv1800_clk_mux *hw_to_cv1800_clk_mux(struct clk_hw *hw)
{
struct cv1800_clk_common *common = hw_to_cv1800_clk_common(hw);
return container_of(common, struct cv1800_clk_mux, common);
}
static int mux_enable(struct clk_hw *hw)
{
struct cv1800_clk_mux *mux = hw_to_cv1800_clk_mux(hw);
return cv1800_clk_setbit(&mux->common, &mux->gate);
}
static void mux_disable(struct clk_hw *hw)
{
struct cv1800_clk_mux *mux = hw_to_cv1800_clk_mux(hw);
cv1800_clk_clearbit(&mux->common, &mux->gate);
}
static int mux_is_enabled(struct clk_hw *hw)
{
struct cv1800_clk_mux *mux = hw_to_cv1800_clk_mux(hw);
return cv1800_clk_checkbit(&mux->common, &mux->gate);
}
static int do_mux_determine_rate(struct clk_rate_request *req, int id, void *data)
{
struct cv1800_clk_mux *mux = data;
return div_helper_determine_rate(&mux->div, &mux->common.hw, req);
}
static int mux_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct cv1800_clk_mux *mux = hw_to_cv1800_clk_mux(hw);
return mux_helper_determine_rate(&mux->common, req,
do_mux_determine_rate, mux);
}
static unsigned long mux_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct cv1800_clk_mux *mux = hw_to_cv1800_clk_mux(hw);
unsigned long val;
val = div_helper_get_clockdiv(&mux->common, &mux->div);
if (val == 0)
return 0;
return divider_recalc_rate(hw, parent_rate, val, NULL,
mux->div.flags, mux->div.width);
}
static int mux_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct cv1800_clk_mux *mux = hw_to_cv1800_clk_mux(hw);
unsigned long val;
val = divider_get_val(rate, parent_rate, NULL,
mux->div.width, mux->div.flags);
return div_helper_set_rate(&mux->common, &mux->div, val);
}
static u8 mux_get_parent(struct clk_hw *hw)
{
struct cv1800_clk_mux *mux = hw_to_cv1800_clk_mux(hw);
u32 reg = readl(mux->common.base + mux->mux.reg);
return cv1800_clk_regfield_get(reg, &mux->mux);
}
static int _mux_set_parent(struct cv1800_clk_mux *mux, u8 index)
{
u32 reg;
reg = readl(mux->common.base + mux->mux.reg);
reg = cv1800_clk_regfield_set(reg, index, &mux->mux);
writel(reg, mux->common.base + mux->mux.reg);
return 0;
}
static int mux_set_parent(struct clk_hw *hw, u8 index)
{
struct cv1800_clk_mux *mux = hw_to_cv1800_clk_mux(hw);
unsigned long flags;
spin_lock_irqsave(mux->common.lock, flags);
_mux_set_parent(mux, index);
spin_unlock_irqrestore(mux->common.lock, flags);
return 0;
}
const struct clk_ops cv1800_clk_mux_ops = {
.disable = mux_disable,
.enable = mux_enable,
.is_enabled = mux_is_enabled,
.determine_rate = mux_determine_rate,
.recalc_rate = mux_recalc_rate,
.set_rate = mux_set_rate,
.set_parent = mux_set_parent,
.get_parent = mux_get_parent,
};
static inline struct cv1800_clk_bypass_mux *
hw_to_cv1800_clk_bypass_mux(struct clk_hw *hw)
{
struct cv1800_clk_mux *mux = hw_to_cv1800_clk_mux(hw);
return container_of(mux, struct cv1800_clk_bypass_mux, mux);
}
static int do_bypass_mux_determine_rate(struct clk_rate_request *req, int id,
void *data)
{
struct cv1800_clk_bypass_mux *mux = data;
if (id == -1) {
if (cv1800_clk_checkbit(&mux->mux.common, &mux->bypass)) {
req->rate = req->best_parent_rate;
return 0;
}
return do_mux_determine_rate(req, -1, &mux->mux);
}
if (id == 0) {
req->rate = req->best_parent_rate;
return 0;
}
return do_mux_determine_rate(req, id - 1, &mux->mux);
}
static int bypass_mux_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct cv1800_clk_bypass_mux *mux = hw_to_cv1800_clk_bypass_mux(hw);
return mux_helper_determine_rate(&mux->mux.common, req,
do_bypass_mux_determine_rate, mux);
}
static unsigned long bypass_mux_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct cv1800_clk_bypass_mux *mux = hw_to_cv1800_clk_bypass_mux(hw);
if (cv1800_clk_checkbit(&mux->mux.common, &mux->bypass))
return parent_rate;
return mux_recalc_rate(hw, parent_rate);
}
static int bypass_mux_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct cv1800_clk_bypass_mux *mux = hw_to_cv1800_clk_bypass_mux(hw);
if (cv1800_clk_checkbit(&mux->mux.common, &mux->bypass))
return 0;
return mux_set_rate(hw, rate, parent_rate);
}
static u8 bypass_mux_get_parent(struct clk_hw *hw)
{
struct cv1800_clk_bypass_mux *mux = hw_to_cv1800_clk_bypass_mux(hw);
if (cv1800_clk_checkbit(&mux->mux.common, &mux->bypass))
return 0;
return mux_get_parent(hw) + 1;
}
static int bypass_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct cv1800_clk_bypass_mux *mux = hw_to_cv1800_clk_bypass_mux(hw);
if (index == 0)
return cv1800_clk_setbit(&mux->mux.common, &mux->bypass);
return cv1800_clk_clearbit(&mux->mux.common, &mux->bypass);
}
const struct clk_ops cv1800_clk_bypass_mux_ops = {
.disable = mux_disable,
.enable = mux_enable,
.is_enabled = mux_is_enabled,
.determine_rate = bypass_mux_determine_rate,
.recalc_rate = bypass_mux_recalc_rate,
.set_rate = bypass_mux_set_rate,
.set_parent = bypass_mux_set_parent,
.get_parent = bypass_mux_get_parent,
};
/* MMUX */
static inline struct cv1800_clk_mmux *hw_to_cv1800_clk_mmux(struct clk_hw *hw)
{
struct cv1800_clk_common *common = hw_to_cv1800_clk_common(hw);
return container_of(common, struct cv1800_clk_mmux, common);
}
static u8 mmux_get_parent_id(struct cv1800_clk_mmux *mmux)
{
struct clk_hw *hw = &mmux->common.hw;
struct clk_hw *parent = clk_hw_get_parent(hw);
unsigned int i;
for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
if (parent == clk_hw_get_parent_by_index(hw, i))
return i;
}
BUG();
}
static int mmux_enable(struct clk_hw *hw)
{
struct cv1800_clk_mmux *mmux = hw_to_cv1800_clk_mmux(hw);
return cv1800_clk_setbit(&mmux->common, &mmux->gate);
}
static void mmux_disable(struct clk_hw *hw)
{
struct cv1800_clk_mmux *mmux = hw_to_cv1800_clk_mmux(hw);
cv1800_clk_clearbit(&mmux->common, &mmux->gate);
}
static int mmux_is_enabled(struct clk_hw *hw)
{
struct cv1800_clk_mmux *mmux = hw_to_cv1800_clk_mmux(hw);
return cv1800_clk_checkbit(&mmux->common, &mmux->gate);
}
static int do_mmux_determine_rate(struct clk_rate_request *req, int id, void *data)
{
struct cv1800_clk_mmux *mmux = data;
s8 div_id;
if (id == -1) {
if (cv1800_clk_checkbit(&mmux->common, &mmux->bypass)) {
req->rate = req->best_parent_rate;
return 0;
}
id = mmux_get_parent_id(mmux);
}
div_id = mmux->parent2sel[id];
if (div_id < 0) {
req->rate = req->best_parent_rate;
return 0;
}
return div_helper_determine_rate(&mmux->div[div_id], &mmux->common.hw,
req);
}
static int mmux_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct cv1800_clk_mmux *mmux = hw_to_cv1800_clk_mmux(hw);
return mux_helper_determine_rate(&mmux->common, req,
do_mmux_determine_rate, mmux);
}
static unsigned long mmux_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct cv1800_clk_mmux *mmux = hw_to_cv1800_clk_mmux(hw);
unsigned long val;
struct cv1800_clk_regfield *div;
if (cv1800_clk_checkbit(&mmux->common, &mmux->bypass))
return parent_rate;
if (cv1800_clk_checkbit(&mmux->common, &mmux->clk_sel))
div = &mmux->div[0];
else
div = &mmux->div[1];
val = div_helper_get_clockdiv(&mmux->common, div);
if (val == 0)
return 0;
return divider_recalc_rate(hw, parent_rate, val, NULL,
div->flags, div->width);
}
static int mmux_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct cv1800_clk_mmux *mmux = hw_to_cv1800_clk_mmux(hw);
struct cv1800_clk_regfield *div;
unsigned long val;
if (cv1800_clk_checkbit(&mmux->common, &mmux->bypass))
return parent_rate;
if (cv1800_clk_checkbit(&mmux->common, &mmux->clk_sel))
div = &mmux->div[0];
else
div = &mmux->div[1];
val = divider_get_val(rate, parent_rate, NULL,
div->width, div->flags);
return div_helper_set_rate(&mmux->common, div, val);
}
static u8 mmux_get_parent(struct clk_hw *hw)
{
struct cv1800_clk_mmux *mmux = hw_to_cv1800_clk_mmux(hw);
struct cv1800_clk_regfield *mux;
u32 reg;
s8 clk_sel;
if (cv1800_clk_checkbit(&mmux->common, &mmux->bypass))
return 0;
if (cv1800_clk_checkbit(&mmux->common, &mmux->clk_sel))
clk_sel = 0;
else
clk_sel = 1;
mux = &mmux->mux[clk_sel];
reg = readl(mmux->common.base + mux->reg);
return mmux->sel2parent[clk_sel][cv1800_clk_regfield_get(reg, mux)];
}
static int mmux_set_parent(struct clk_hw *hw, u8 index)
{
struct cv1800_clk_mmux *mmux = hw_to_cv1800_clk_mmux(hw);
struct cv1800_clk_regfield *mux;
unsigned long flags;
u32 reg;
s8 clk_sel = mmux->parent2sel[index];
if (index == 0 || clk_sel == -1) {
cv1800_clk_setbit(&mmux->common, &mmux->bypass);
goto release;
}
cv1800_clk_clearbit(&mmux->common, &mmux->bypass);
if (clk_sel)
cv1800_clk_clearbit(&mmux->common, &mmux->clk_sel);
else
cv1800_clk_setbit(&mmux->common, &mmux->clk_sel);
spin_lock_irqsave(mmux->common.lock, flags);
mux = &mmux->mux[clk_sel];
reg = readl(mmux->common.base + mux->reg);
reg = cv1800_clk_regfield_set(reg, index, mux);
writel(reg, mmux->common.base + mux->reg);
spin_unlock_irqrestore(mmux->common.lock, flags);
release:
return 0;
}
const struct clk_ops cv1800_clk_mmux_ops = {
.disable = mmux_disable,
.enable = mmux_enable,
.is_enabled = mmux_is_enabled,
.determine_rate = mmux_determine_rate,
.recalc_rate = mmux_recalc_rate,
.set_rate = mmux_set_rate,
.set_parent = mmux_set_parent,
.get_parent = mmux_get_parent,
};
/* AUDIO CLK */
static inline struct cv1800_clk_audio *
hw_to_cv1800_clk_audio(struct clk_hw *hw)
{
struct cv1800_clk_common *common = hw_to_cv1800_clk_common(hw);
return container_of(common, struct cv1800_clk_audio, common);
}
static int aclk_enable(struct clk_hw *hw)
{
struct cv1800_clk_audio *aclk = hw_to_cv1800_clk_audio(hw);
cv1800_clk_setbit(&aclk->common, &aclk->src_en);
return cv1800_clk_setbit(&aclk->common, &aclk->output_en);
}
static void aclk_disable(struct clk_hw *hw)
{
struct cv1800_clk_audio *aclk = hw_to_cv1800_clk_audio(hw);
cv1800_clk_clearbit(&aclk->common, &aclk->output_en);
cv1800_clk_clearbit(&aclk->common, &aclk->src_en);
}
static int aclk_is_enabled(struct clk_hw *hw)
{
struct cv1800_clk_audio *aclk = hw_to_cv1800_clk_audio(hw);
return cv1800_clk_checkbit(&aclk->common, &aclk->output_en);
}
static int aclk_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct cv1800_clk_audio *aclk = hw_to_cv1800_clk_audio(hw);
req->rate = aclk->target_rate;
return 0;
}
static unsigned long aclk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct cv1800_clk_audio *aclk = hw_to_cv1800_clk_audio(hw);
u64 rate = parent_rate;
u64 factor = 2;
u32 regval;
if (!cv1800_clk_checkbit(&aclk->common, &aclk->div_en))
return 0;
regval = readl(aclk->common.base + aclk->m.reg);
factor *= cv1800_clk_regfield_get(regval, &aclk->m);
regval = readl(aclk->common.base + aclk->n.reg);
rate *= cv1800_clk_regfield_get(regval, &aclk->n);
return DIV64_U64_ROUND_UP(rate, factor);
}
static void aclk_determine_mn(unsigned long parent_rate, unsigned long rate,
u32 *m, u32 *n)
{
u32 tm = parent_rate / 2;
u32 tn = rate;
u32 tcommon = gcd(tm, tn);
*m = tm / tcommon;
*n = tn / tcommon;
}
static int aclk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct cv1800_clk_audio *aclk = hw_to_cv1800_clk_audio(hw);
unsigned long flags;
u32 m, n;
aclk_determine_mn(parent_rate, rate,
&m, &n);
spin_lock_irqsave(aclk->common.lock, flags);
writel(m, aclk->common.base + aclk->m.reg);
writel(n, aclk->common.base + aclk->n.reg);
cv1800_clk_setbit(&aclk->common, &aclk->div_en);
cv1800_clk_setbit(&aclk->common, &aclk->div_up);
spin_unlock_irqrestore(aclk->common.lock, flags);
return 0;
}
const struct clk_ops cv1800_clk_audio_ops = {
.disable = aclk_disable,
.enable = aclk_enable,
.is_enabled = aclk_is_enabled,
.determine_rate = aclk_determine_rate,
.recalc_rate = aclk_recalc_rate,
.set_rate = aclk_set_rate,
};
@@ -0,0 +1,261 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Inochi Amaoto <inochiama@outlook.com>
*/
#ifndef _CLK_SOPHGO_CV1800_IP_H_
#define _CLK_SOPHGO_CV1800_IP_H_
#include "clk-cv18xx-common.h"
struct cv1800_clk_gate {
struct cv1800_clk_common common;
struct cv1800_clk_regbit gate;
};
struct cv1800_clk_div_data {
u32 reg;
u32 mask;
u32 width;
u32 init;
u32 flags;
};
struct cv1800_clk_div {
struct cv1800_clk_common common;
struct cv1800_clk_regbit gate;
struct cv1800_clk_regfield div;
};
struct cv1800_clk_bypass_div {
struct cv1800_clk_div div;
struct cv1800_clk_regbit bypass;
};
struct cv1800_clk_mux {
struct cv1800_clk_common common;
struct cv1800_clk_regbit gate;
struct cv1800_clk_regfield div;
struct cv1800_clk_regfield mux;
};
struct cv1800_clk_bypass_mux {
struct cv1800_clk_mux mux;
struct cv1800_clk_regbit bypass;
};
struct cv1800_clk_mmux {
struct cv1800_clk_common common;
struct cv1800_clk_regbit gate;
struct cv1800_clk_regfield div[2];
struct cv1800_clk_regfield mux[2];
struct cv1800_clk_regbit bypass;
struct cv1800_clk_regbit clk_sel;
const s8 *parent2sel;
const u8 *sel2parent[2];
};
struct cv1800_clk_audio {
struct cv1800_clk_common common;
struct cv1800_clk_regbit src_en;
struct cv1800_clk_regbit output_en;
struct cv1800_clk_regbit div_en;
struct cv1800_clk_regbit div_up;
struct cv1800_clk_regfield m;
struct cv1800_clk_regfield n;
u32 target_rate;
};
#define CV1800_GATE(_name, _parent, _gate_reg, _gate_shift, _flags) \
struct cv1800_clk_gate _name = { \
.common = CV1800_CLK_COMMON(#_name, _parent, \
&cv1800_clk_gate_ops, \
_flags), \
.gate = CV1800_CLK_BIT(_gate_reg, _gate_shift), \
}
#define _CV1800_DIV(_name, _parent, _gate_reg, _gate_shift, \
_div_reg, _div_shift, _div_width, _div_init, \
_div_flag, _ops, _flags) \
{ \
.common = CV1800_CLK_COMMON(#_name, _parent, \
_ops, _flags), \
.gate = CV1800_CLK_BIT(_gate_reg, \
_gate_shift), \
.div = CV1800_CLK_REG(_div_reg, _div_shift, \
_div_width, _div_init, \
_div_flag), \
}
#define _CV1800_FIXED_DIV_FLAG \
(CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ROUND_CLOSEST)
#define _CV1800_FIXED_DIV(_name, _parent, _gate_reg, _gate_shift, \
_fix_div, _ops, _flags) \
{ \
.common = CV1800_CLK_COMMON(#_name, _parent, \
_ops, _flags), \
.gate = CV1800_CLK_BIT(_gate_reg, \
_gate_shift), \
.div = CV1800_CLK_REG(0, 0, 0, \
_fix_div, \
_CV1800_FIXED_DIV_FLAG),\
}
#define CV1800_DIV(_name, _parent, _gate_reg, _gate_shift, \
_div_reg, _div_shift, _div_width, _div_init, \
_div_flag, _flags) \
struct cv1800_clk_div _name = \
_CV1800_DIV(_name, _parent, _gate_reg, _gate_shift, \
_div_reg, _div_shift, _div_width, _div_init,\
_div_flag, &cv1800_clk_div_ops, _flags)
#define CV1800_BYPASS_DIV(_name, _parent, _gate_reg, _gate_shift, \
_div_reg, _div_shift, _div_width, _div_init, \
_div_flag, _bypass_reg, _bypass_shift, _flags)\
struct cv1800_clk_bypass_div _name = { \
.div = _CV1800_DIV(_name, _parent, \
_gate_reg, _gate_shift, \
_div_reg, _div_shift, \
_div_width, _div_init, _div_flag, \
&cv1800_clk_bypass_div_ops, \
_flags), \
.bypass = CV1800_CLK_BIT(_bypass_reg, _bypass_shift), \
}
#define CV1800_FIXED_DIV(_name, _parent, _gate_reg, _gate_shift, \
_fix_div, _flags) \
struct cv1800_clk_div _name = \
_CV1800_FIXED_DIV(_name, _parent, \
_gate_reg, _gate_shift, \
_fix_div, \
&cv1800_clk_div_ops, _flags) \
#define CV1800_BYPASS_FIXED_DIV(_name, _parent, _gate_reg, _gate_shift, \
_fix_div, _bypass_reg, _bypass_shift, \
_flags) \
struct cv1800_clk_bypass_div _name = { \
.div = _CV1800_FIXED_DIV(_name, _parent, \
_gate_reg, _gate_shift, \
_fix_div, \
&cv1800_clk_bypass_div_ops, \
_flags), \
.bypass = CV1800_CLK_BIT(_bypass_reg, _bypass_shift), \
}
#define _CV1800_MUX(_name, _parent, _gate_reg, _gate_shift, \
_div_reg, _div_shift, _div_width, _div_init, \
_div_flag, \
_mux_reg, _mux_shift, _mux_width, \
_ops, _flags) \
{ \
.common = CV1800_CLK_COMMON(#_name, _parent, \
_ops, _flags), \
.gate = CV1800_CLK_BIT(_gate_reg, \
_gate_shift), \
.div = CV1800_CLK_REG(_div_reg, _div_shift, \
_div_width, _div_init, \
_div_flag), \
.mux = CV1800_CLK_REG(_mux_reg, _mux_shift, \
_mux_width, 0, 0), \
}
#define CV1800_MUX(_name, _parent, _gate_reg, _gate_shift, \
_div_reg, _div_shift, _div_width, _div_init, \
_div_flag, \
_mux_reg, _mux_shift, _mux_width, _flags) \
struct cv1800_clk_mux _name = \
_CV1800_MUX(_name, _parent, _gate_reg, _gate_shift, \
_div_reg, _div_shift, _div_width, _div_init,\
_div_flag, _mux_reg, _mux_shift, _mux_width,\
&cv1800_clk_mux_ops, _flags)
#define CV1800_BYPASS_MUX(_name, _parent, _gate_reg, _gate_shift, \
_div_reg, _div_shift, _div_width, _div_init, \
_div_flag, \
_mux_reg, _mux_shift, _mux_width, \
_bypass_reg, _bypass_shift, _flags) \
struct cv1800_clk_bypass_mux _name = { \
.mux = _CV1800_MUX(_name, _parent, \
_gate_reg, _gate_shift, \
_div_reg, _div_shift, _div_width, \
_div_init, _div_flag, \
_mux_reg, _mux_shift, _mux_width, \
&cv1800_clk_bypass_mux_ops, \
_flags), \
.bypass = CV1800_CLK_BIT(_bypass_reg, _bypass_shift), \
}
#define CV1800_MMUX(_name, _parent, _gate_reg, _gate_shift, \
_div0_reg, _div0_shift, _div0_width, _div0_init, \
_div0_flag, \
_div1_reg, _div1_shift, _div1_width, _div1_init, \
_div1_flag, \
_mux0_reg, _mux0_shift, _mux0_width, \
_mux1_reg, _mux1_shift, _mux1_width, \
_bypass_reg, _bypass_shift, \
_clk_sel_reg, _clk_sel_shift, \
_parent2sel, _sel2parent0, _sel2parent1, _flags) \
struct cv1800_clk_mmux _name = { \
.common = CV1800_CLK_COMMON(#_name, _parent, \
&cv1800_clk_mmux_ops,\
_flags), \
.gate = CV1800_CLK_BIT(_gate_reg, _gate_shift),\
.div = { \
CV1800_CLK_REG(_div0_reg, _div0_shift, \
_div0_width, _div0_init, \
_div0_flag), \
CV1800_CLK_REG(_div1_reg, _div1_shift, \
_div1_width, _div1_init, \
_div1_flag), \
}, \
.mux = { \
CV1800_CLK_REG(_mux0_reg, _mux0_shift, \
_mux0_width, 0, 0), \
CV1800_CLK_REG(_mux1_reg, _mux1_shift, \
_mux1_width, 0, 0), \
}, \
.bypass = CV1800_CLK_BIT(_bypass_reg, \
_bypass_shift), \
.clk_sel = CV1800_CLK_BIT(_clk_sel_reg, \
_clk_sel_shift), \
.parent2sel = _parent2sel, \
.sel2parent = { _sel2parent0, _sel2parent1 }, \
}
#define CV1800_ACLK(_name, _parent, \
_src_en_reg, _src_en_reg_shift, \
_output_en_reg, _output_en_shift, \
_div_en_reg, _div_en_reg_shift, \
_div_up_reg, _div_up_reg_shift, \
_m_reg, _m_shift, _m_width, _m_flag, \
_n_reg, _n_shift, _n_width, _n_flag, \
_target_rate, _flags) \
struct cv1800_clk_audio _name = { \
.common = CV1800_CLK_COMMON(#_name, _parent, \
&cv1800_clk_audio_ops,\
_flags), \
.src_en = CV1800_CLK_BIT(_src_en_reg, \
_src_en_reg_shift), \
.output_en = CV1800_CLK_BIT(_output_en_reg, \
_output_en_shift), \
.div_en = CV1800_CLK_BIT(_div_en_reg, \
_div_en_reg_shift), \
.div_up = CV1800_CLK_BIT(_div_up_reg, \
_div_up_reg_shift), \
.m = CV1800_CLK_REG(_m_reg, _m_shift, \
_m_width, 0, _m_flag), \
.n = CV1800_CLK_REG(_n_reg, _n_shift, \
_n_width, 0, _n_flag), \
.target_rate = _target_rate, \
}
extern const struct clk_ops cv1800_clk_gate_ops;
extern const struct clk_ops cv1800_clk_div_ops;
extern const struct clk_ops cv1800_clk_bypass_div_ops;
extern const struct clk_ops cv1800_clk_mux_ops;
extern const struct clk_ops cv1800_clk_bypass_mux_ops;
extern const struct clk_ops cv1800_clk_mmux_ops;
extern const struct clk_ops cv1800_clk_audio_ops;
#endif // _CLK_SOPHGO_CV1800_IP_H_
@@ -0,0 +1,419 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2023 Inochi Amaoto <inochiama@outlook.com>
*/
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/limits.h>
#include <linux/spinlock.h>
#include "clk-cv18xx-pll.h"
static inline struct cv1800_clk_pll *hw_to_cv1800_clk_pll(struct clk_hw *hw)
{
struct cv1800_clk_common *common = hw_to_cv1800_clk_common(hw);
return container_of(common, struct cv1800_clk_pll, common);
}
static unsigned long ipll_calc_rate(unsigned long parent_rate,
unsigned long pre_div_sel,
unsigned long div_sel,
unsigned long post_div_sel)
{
uint64_t rate = parent_rate;
rate *= div_sel;
do_div(rate, pre_div_sel * post_div_sel);
return rate;
}
static unsigned long ipll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
u32 value;
value = readl(pll->common.base + pll->pll_reg);
return ipll_calc_rate(parent_rate,
PLL_GET_PRE_DIV_SEL(value),
PLL_GET_DIV_SEL(value),
PLL_GET_POST_DIV_SEL(value));
}
static int ipll_find_rate(const struct cv1800_clk_pll_limit *limit,
unsigned long prate, unsigned long *rate,
u32 *value)
{
unsigned long best_rate = 0;
unsigned long trate = *rate;
unsigned long pre_div_sel = 0, div_sel = 0, post_div_sel = 0;
unsigned long pre, div, post;
u32 detected = *value;
unsigned long tmp;
for_each_pll_limit_range(pre, &limit->pre_div) {
for_each_pll_limit_range(div, &limit->div) {
for_each_pll_limit_range(post, &limit->post_div) {
tmp = ipll_calc_rate(prate, pre, div, post);
if (tmp > trate)
continue;
if ((trate - tmp) < (trate - best_rate)) {
best_rate = tmp;
pre_div_sel = pre;
div_sel = div;
post_div_sel = post;
}
}
}
}
if (best_rate) {
detected = PLL_SET_PRE_DIV_SEL(detected, pre_div_sel);
detected = PLL_SET_POST_DIV_SEL(detected, post_div_sel);
detected = PLL_SET_DIV_SEL(detected, div_sel);
*value = detected;
*rate = best_rate;
return 0;
}
return -EINVAL;
}
static int ipll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
{
u32 val;
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
return ipll_find_rate(pll->pll_limit, req->best_parent_rate,
&req->rate, &val);
}
static void pll_get_mode_ctrl(unsigned long div_sel,
bool (*mode_ctrl_check)(unsigned long,
unsigned long,
unsigned long),
const struct cv1800_clk_pll_limit *limit,
u32 *value)
{
unsigned long ictrl = 0, mode = 0;
u32 detected = *value;
for_each_pll_limit_range(mode, &limit->mode) {
for_each_pll_limit_range(ictrl, &limit->ictrl) {
if (mode_ctrl_check(div_sel, ictrl, mode)) {
detected = PLL_SET_SEL_MODE(detected, mode);
detected = PLL_SET_ICTRL(detected, ictrl);
*value = detected;
return;
}
}
}
}
static bool ipll_check_mode_ctrl_restrict(unsigned long div_sel,
unsigned long ictrl,
unsigned long mode)
{
unsigned long left_rest = 20 * div_sel;
unsigned long right_rest = 35 * div_sel;
unsigned long test = 184 * (1 + mode) * (1 + ictrl) / 2;
return test > left_rest && test <= right_rest;
}
static int ipll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
u32 regval, detected = 0;
unsigned long flags;
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
ipll_find_rate(pll->pll_limit, parent_rate, &rate, &detected);
pll_get_mode_ctrl(PLL_GET_DIV_SEL(detected),
ipll_check_mode_ctrl_restrict,
pll->pll_limit, &detected);
spin_lock_irqsave(pll->common.lock, flags);
regval = readl(pll->common.base + pll->pll_reg);
regval = PLL_COPY_REG(regval, detected);
writel(regval, pll->common.base + pll->pll_reg);
spin_unlock_irqrestore(pll->common.lock, flags);
cv1800_clk_wait_for_lock(&pll->common, pll->pll_status.reg,
BIT(pll->pll_status.shift));
return 0;
}
static int pll_enable(struct clk_hw *hw)
{
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
return cv1800_clk_clearbit(&pll->common, &pll->pll_pwd);
}
static void pll_disable(struct clk_hw *hw)
{
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
cv1800_clk_setbit(&pll->common, &pll->pll_pwd);
}
static int pll_is_enable(struct clk_hw *hw)
{
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
return cv1800_clk_checkbit(&pll->common, &pll->pll_pwd) == 0;
}
const struct clk_ops cv1800_clk_ipll_ops = {
.disable = pll_disable,
.enable = pll_enable,
.is_enabled = pll_is_enable,
.recalc_rate = ipll_recalc_rate,
.determine_rate = ipll_determine_rate,
.set_rate = ipll_set_rate,
};
#define PLL_SYN_FACTOR_DOT_POS 26
#define PLL_SYN_FACTOR_MINIMUM ((4 << PLL_SYN_FACTOR_DOT_POS) + 1)
static bool fpll_is_factional_mode(struct cv1800_clk_pll *pll)
{
return cv1800_clk_checkbit(&pll->common, &pll->pll_syn->en);
}
static unsigned long fpll_calc_rate(unsigned long parent_rate,
unsigned long pre_div_sel,
unsigned long div_sel,
unsigned long post_div_sel,
unsigned long ssc_syn_set,
bool is_full_parent)
{
u64 dividend = parent_rate * div_sel;
u64 factor = ssc_syn_set * pre_div_sel * post_div_sel;
unsigned long rate;
dividend <<= PLL_SYN_FACTOR_DOT_POS - 1;
rate = div64_u64_rem(dividend, factor, &dividend);
if (is_full_parent) {
dividend <<= 1;
rate <<= 1;
}
rate += DIV64_U64_ROUND_CLOSEST(dividend, factor);
return rate;
}
static unsigned long fpll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
u32 value;
bool clk_full;
u32 syn_set;
if (!fpll_is_factional_mode(pll))
return ipll_recalc_rate(hw, parent_rate);
syn_set = readl(pll->common.base + pll->pll_syn->set);
if (syn_set == 0)
return 0;
clk_full = cv1800_clk_checkbit(&pll->common,
&pll->pll_syn->clk_half);
value = readl(pll->common.base + pll->pll_reg);
return fpll_calc_rate(parent_rate,
PLL_GET_PRE_DIV_SEL(value),
PLL_GET_DIV_SEL(value),
PLL_GET_POST_DIV_SEL(value),
syn_set, clk_full);
}
static unsigned long fpll_find_synthesizer(unsigned long parent,
unsigned long rate,
unsigned long pre_div,
unsigned long div,
unsigned long post_div,
bool is_full_parent,
u32 *ssc_syn_set)
{
u32 test_max = U32_MAX, test_min = PLL_SYN_FACTOR_MINIMUM;
unsigned long trate;
while (test_min < test_max) {
u32 tssc = (test_max + test_min) / 2;
trate = fpll_calc_rate(parent, pre_div, div, post_div,
tssc, is_full_parent);
if (trate == rate) {
test_min = tssc;
break;
}
if (trate > rate)
test_min = tssc + 1;
else
test_max = tssc - 1;
}
if (trate != 0)
*ssc_syn_set = test_min;
return trate;
}
static int fpll_find_rate(struct cv1800_clk_pll *pll,
const struct cv1800_clk_pll_limit *limit,
unsigned long prate,
unsigned long *rate,
u32 *value, u32 *ssc_syn_set)
{
unsigned long best_rate = 0;
unsigned long pre_div_sel = 0, div_sel = 0, post_div_sel = 0;
unsigned long pre, div, post;
unsigned long trate = *rate;
u32 detected = *value;
unsigned long tmp;
bool clk_full = cv1800_clk_checkbit(&pll->common,
&pll->pll_syn->clk_half);
for_each_pll_limit_range(pre, &limit->pre_div) {
for_each_pll_limit_range(post, &limit->post_div) {
for_each_pll_limit_range(div, &limit->div) {
tmp = fpll_find_synthesizer(prate, trate,
pre, div, post,
clk_full,
ssc_syn_set);
if ((trate - tmp) < (trate - best_rate)) {
best_rate = tmp;
pre_div_sel = pre;
div_sel = div;
post_div_sel = post;
}
}
}
}
if (best_rate) {
detected = PLL_SET_PRE_DIV_SEL(detected, pre_div_sel);
detected = PLL_SET_POST_DIV_SEL(detected, post_div_sel);
detected = PLL_SET_DIV_SEL(detected, div_sel);
*value = detected;
*rate = best_rate;
return 0;
}
return -EINVAL;
}
static int fpll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
{
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
u32 val, ssc_syn_set;
if (!fpll_is_factional_mode(pll))
return ipll_determine_rate(hw, req);
fpll_find_rate(pll, &pll->pll_limit[2], req->best_parent_rate,
&req->rate, &val, &ssc_syn_set);
return 0;
}
static bool fpll_check_mode_ctrl_restrict(unsigned long div_sel,
unsigned long ictrl,
unsigned long mode)
{
unsigned long left_rest = 10 * div_sel;
unsigned long right_rest = 24 * div_sel;
unsigned long test = 184 * (1 + mode) * (1 + ictrl) / 2;
return test > left_rest && test <= right_rest;
}
static int fpll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
u32 regval;
u32 detected = 0, detected_ssc = 0;
unsigned long flags;
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
if (!fpll_is_factional_mode(pll))
return ipll_set_rate(hw, rate, parent_rate);
fpll_find_rate(pll, &pll->pll_limit[2], parent_rate,
&rate, &detected, &detected_ssc);
pll_get_mode_ctrl(PLL_GET_DIV_SEL(detected),
fpll_check_mode_ctrl_restrict,
pll->pll_limit, &detected);
spin_lock_irqsave(pll->common.lock, flags);
writel(detected_ssc, pll->common.base + pll->pll_syn->set);
regval = readl(pll->common.base + pll->pll_reg);
regval = PLL_COPY_REG(regval, detected);
writel(regval, pll->common.base + pll->pll_reg);
spin_unlock_irqrestore(pll->common.lock, flags);
cv1800_clk_wait_for_lock(&pll->common, pll->pll_status.reg,
BIT(pll->pll_status.shift));
return 0;
}
static u8 fpll_get_parent(struct clk_hw *hw)
{
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
if (fpll_is_factional_mode(pll))
return 1;
return 0;
}
static int fpll_set_parent(struct clk_hw *hw, u8 index)
{
struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
if (index)
cv1800_clk_setbit(&pll->common, &pll->pll_syn->en);
else
cv1800_clk_clearbit(&pll->common, &pll->pll_syn->en);
return 0;
}
const struct clk_ops cv1800_clk_fpll_ops = {
.disable = pll_disable,
.enable = pll_enable,
.is_enabled = pll_is_enable,
.recalc_rate = fpll_recalc_rate,
.determine_rate = fpll_determine_rate,
.set_rate = fpll_set_rate,
.set_parent = fpll_set_parent,
.get_parent = fpll_get_parent,
};
@@ -0,0 +1,118 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Inochi Amaoto <inochiama@outlook.com>
*/
#ifndef _CLK_SOPHGO_CV1800_PLL_H_
#define _CLK_SOPHGO_CV1800_PLL_H_
#include "clk-cv18xx-common.h"
struct cv1800_clk_pll_limit {
struct {
u8 min;
u8 max;
} pre_div, div, post_div, ictrl, mode;
};
#define _CV1800_PLL_LIMIT(_min, _max) \
{ \
.min = _min, \
.max = _max, \
} \
#define for_each_pll_limit_range(_var, _restrict) \
for (_var = (_restrict)->min; _var <= (_restrict)->max; _var++)
struct cv1800_clk_pll_synthesizer {
struct cv1800_clk_regbit en;
struct cv1800_clk_regbit clk_half;
u32 ctrl;
u32 set;
};
#define _PLL_PRE_DIV_SEL_FIELD GENMASK(6, 0)
#define _PLL_POST_DIV_SEL_FIELD GENMASK(14, 8)
#define _PLL_SEL_MODE_FIELD GENMASK(16, 15)
#define _PLL_DIV_SEL_FIELD GENMASK(23, 17)
#define _PLL_ICTRL_FIELD GENMASK(26, 24)
#define _PLL_ALL_FIELD_MASK \
(_PLL_PRE_DIV_SEL_FIELD | \
_PLL_POST_DIV_SEL_FIELD | \
_PLL_SEL_MODE_FIELD | \
_PLL_DIV_SEL_FIELD | \
_PLL_ICTRL_FIELD)
#define PLL_COPY_REG(_dest, _src) \
(((_dest) & (~_PLL_ALL_FIELD_MASK)) | ((_src) & _PLL_ALL_FIELD_MASK))
#define PLL_GET_PRE_DIV_SEL(_reg) \
FIELD_GET(_PLL_PRE_DIV_SEL_FIELD, (_reg))
#define PLL_GET_POST_DIV_SEL(_reg) \
FIELD_GET(_PLL_POST_DIV_SEL_FIELD, (_reg))
#define PLL_GET_SEL_MODE(_reg) \
FIELD_GET(_PLL_SEL_MODE_FIELD, (_reg))
#define PLL_GET_DIV_SEL(_reg) \
FIELD_GET(_PLL_DIV_SEL_FIELD, (_reg))
#define PLL_GET_ICTRL(_reg) \
FIELD_GET(_PLL_ICTRL_FIELD, (_reg))
#define PLL_SET_PRE_DIV_SEL(_reg, _val) \
_CV1800_SET_FIELD((_reg), (_val), _PLL_PRE_DIV_SEL_FIELD)
#define PLL_SET_POST_DIV_SEL(_reg, _val) \
_CV1800_SET_FIELD((_reg), (_val), _PLL_POST_DIV_SEL_FIELD)
#define PLL_SET_SEL_MODE(_reg, _val) \
_CV1800_SET_FIELD((_reg), (_val), _PLL_SEL_MODE_FIELD)
#define PLL_SET_DIV_SEL(_reg, _val) \
_CV1800_SET_FIELD((_reg), (_val), _PLL_DIV_SEL_FIELD)
#define PLL_SET_ICTRL(_reg, _val) \
_CV1800_SET_FIELD((_reg), (_val), _PLL_ICTRL_FIELD)
struct cv1800_clk_pll {
struct cv1800_clk_common common;
u32 pll_reg;
struct cv1800_clk_regbit pll_pwd;
struct cv1800_clk_regbit pll_status;
const struct cv1800_clk_pll_limit *pll_limit;
struct cv1800_clk_pll_synthesizer *pll_syn;
};
#define CV1800_INTEGRAL_PLL(_name, _parent, _pll_reg, \
_pll_pwd_reg, _pll_pwd_shift, \
_pll_status_reg, _pll_status_shift, \
_pll_limit, _flags) \
struct cv1800_clk_pll _name = { \
.common = CV1800_CLK_COMMON(#_name, _parent, \
&cv1800_clk_ipll_ops,\
_flags), \
.pll_reg = _pll_reg, \
.pll_pwd = CV1800_CLK_BIT(_pll_pwd_reg, \
_pll_pwd_shift), \
.pll_status = CV1800_CLK_BIT(_pll_status_reg, \
_pll_status_shift), \
.pll_limit = _pll_limit, \
.pll_syn = NULL, \
}
#define CV1800_FACTIONAL_PLL(_name, _parent, _pll_reg, \
_pll_pwd_reg, _pll_pwd_shift, \
_pll_status_reg, _pll_status_shift, \
_pll_limit, _pll_syn, _flags) \
struct cv1800_clk_pll _name = { \
.common = CV1800_CLK_COMMON(#_name, _parent, \
&cv1800_clk_fpll_ops,\
_flags), \
.pll_reg = _pll_reg, \
.pll_pwd = CV1800_CLK_BIT(_pll_pwd_reg, \
_pll_pwd_shift), \
.pll_status = CV1800_CLK_BIT(_pll_status_reg, \
_pll_status_shift), \
.pll_limit = _pll_limit, \
.pll_syn = _pll_syn, \
}
extern const struct clk_ops cv1800_clk_ipll_ops;
extern const struct clk_ops cv1800_clk_fpll_ops;
#endif // _CLK_SOPHGO_CV1800_PLL_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,559 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sophgo SG2042 PLL clock Driver
*
* Copyright (C) 2024 Sophgo Technology Inc.
* Copyright (C) 2024 Chen Wang <unicorn_wang@outlook.com>
*/
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <asm/div64.h>
#include <dt-bindings/clock/sophgo,sg2042-pll.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)
/**
* struct sg2042_pll_clock - PLL clock
* @hw: clk_hw for initialization
* @id: used to map clk_onecell_data
* @base: used for readl/writel.
* **NOTE**: PLL registers are all in SYS_CTRL!
* @lock: spinlock to protect register access, modification
* of frequency can only be served one at the time.
* @offset_ctrl: offset of pll control registers
* @shift_status_lock: shift of XXX_LOCK in pll status register
* @shift_status_updating: shift of UPDATING_XXX in pll status register
* @shift_enable: shift of XXX_CLK_EN in pll enable register
*/
struct sg2042_pll_clock {
struct clk_hw hw;
unsigned int id;
void __iomem *base;
/* protect register access */
spinlock_t *lock;
u32 offset_ctrl;
u8 shift_status_lock;
u8 shift_status_updating;
u8 shift_enable;
};
#define to_sg2042_pll_clk(_hw) container_of(_hw, struct sg2042_pll_clock, hw)
#define KHZ 1000UL
#define MHZ (KHZ * KHZ)
#define REFDIV_MIN 1
#define REFDIV_MAX 63
#define FBDIV_MIN 16
#define FBDIV_MAX 320
#define PLL_FREF_SG2042 (25 * MHZ)
#define PLL_FOUTPOSTDIV_MIN (16 * MHZ)
#define PLL_FOUTPOSTDIV_MAX (3200 * MHZ)
#define PLL_FOUTVCO_MIN (800 * MHZ)
#define PLL_FOUTVCO_MAX (3200 * MHZ)
struct sg2042_pll_ctrl {
unsigned long freq;
unsigned int fbdiv;
unsigned int postdiv1;
unsigned int postdiv2;
unsigned int refdiv;
};
#define PLLCTRL_FBDIV_MASK GENMASK(27, 16)
#define PLLCTRL_POSTDIV2_MASK GENMASK(14, 12)
#define PLLCTRL_POSTDIV1_MASK GENMASK(10, 8)
#define PLLCTRL_REFDIV_MASK GENMASK(5, 0)
static inline u32 sg2042_pll_ctrl_encode(struct sg2042_pll_ctrl *ctrl)
{
return FIELD_PREP(PLLCTRL_FBDIV_MASK, ctrl->fbdiv) |
FIELD_PREP(PLLCTRL_POSTDIV2_MASK, ctrl->postdiv2) |
FIELD_PREP(PLLCTRL_POSTDIV1_MASK, ctrl->postdiv1) |
FIELD_PREP(PLLCTRL_REFDIV_MASK, ctrl->refdiv);
}
static inline void sg2042_pll_ctrl_decode(unsigned int reg_value,
struct sg2042_pll_ctrl *ctrl)
{
ctrl->fbdiv = FIELD_GET(PLLCTRL_FBDIV_MASK, reg_value);
ctrl->refdiv = FIELD_GET(PLLCTRL_REFDIV_MASK, reg_value);
ctrl->postdiv1 = FIELD_GET(PLLCTRL_POSTDIV1_MASK, reg_value);
ctrl->postdiv2 = FIELD_GET(PLLCTRL_POSTDIV2_MASK, reg_value);
}
static inline void sg2042_pll_enable(struct sg2042_pll_clock *pll, bool en)
{
u32 value;
if (en) {
/* wait pll lock */
if (readl_poll_timeout_atomic(pll->base + R_PLL_STAT,
value,
((value >> pll->shift_status_lock) & 0x1),
0,
100000))
pr_warn("%s not locked\n", pll->hw.init->name);
/* wait pll updating */
if (readl_poll_timeout_atomic(pll->base + R_PLL_STAT,
value,
!((value >> pll->shift_status_updating) & 0x1),
0,
100000))
pr_warn("%s still updating\n", pll->hw.init->name);
/* enable pll */
value = readl(pll->base + R_PLL_CLKEN_CONTROL);
writel(value | (1 << pll->shift_enable), pll->base + R_PLL_CLKEN_CONTROL);
} else {
/* disable pll */
value = readl(pll->base + R_PLL_CLKEN_CONTROL);
writel(value & (~(1 << pll->shift_enable)), pll->base + R_PLL_CLKEN_CONTROL);
}
}
/**
* sg2042_pll_recalc_rate() - Calculate rate for plls
* @reg_value: current register value
* @parent_rate: parent frequency
*
* This function is used to calculate below "rate" in equation
* rate = (parent_rate/REFDIV) x FBDIV/POSTDIV1/POSTDIV2
* = (parent_rate x FBDIV) / (REFDIV x POSTDIV1 x POSTDIV2)
*
* Return: The rate calculated.
*/
static unsigned long sg2042_pll_recalc_rate(unsigned int reg_value,
unsigned long parent_rate)
{
struct sg2042_pll_ctrl ctrl_table;
u64 numerator, denominator;
sg2042_pll_ctrl_decode(reg_value, &ctrl_table);
numerator = (u64)parent_rate * ctrl_table.fbdiv;
denominator = ctrl_table.refdiv * ctrl_table.postdiv1 * ctrl_table.postdiv2;
numerator = div64_u64(numerator, denominator);
return numerator;
}
/**
* sg2042_pll_get_postdiv_1_2() - Based on input rate/prate/fbdiv/refdiv,
* look up the postdiv1_2 table to get the closest postdiiv combination.
* @rate: FOUTPOSTDIV
* @prate: parent rate, i.e. FREF
* @fbdiv: FBDIV
* @refdiv: REFDIV
* @postdiv1: POSTDIV1, output
* @postdiv2: POSTDIV2, output
*
* postdiv1_2 contains all the possible combination lists of POSTDIV1 and POSTDIV2
* for example:
* postdiv1_2[0] = {2, 4, 8}, where div1 = 2, div2 = 4 , div1 * div2 = 8
*
* See TRM:
* FOUTPOSTDIV = FREF * FBDIV / REFDIV / (POSTDIV1 * POSTDIV2)
* So we get following formula to get POSTDIV1 and POSTDIV2:
* POSTDIV = (prate/REFDIV) x FBDIV/rate
* above POSTDIV = POSTDIV1*POSTDIV2
*
* Return:
* %0 - OK
* %-EINVAL - invalid argument, which means Failed to get the postdivs.
*/
static int sg2042_pll_get_postdiv_1_2(unsigned long rate,
unsigned long prate,
unsigned int fbdiv,
unsigned int refdiv,
unsigned int *postdiv1,
unsigned int *postdiv2)
{
int index;
u64 tmp0;
/* POSTDIV_RESULT_INDEX point to 3rd element in the array postdiv1_2 */
#define POSTDIV_RESULT_INDEX 2
static const int postdiv1_2[][3] = {
{2, 4, 8}, {3, 3, 9}, {2, 5, 10}, {2, 6, 12},
{2, 7, 14}, {3, 5, 15}, {4, 4, 16}, {3, 6, 18},
{4, 5, 20}, {3, 7, 21}, {4, 6, 24}, {5, 5, 25},
{4, 7, 28}, {5, 6, 30}, {5, 7, 35}, {6, 6, 36},
{6, 7, 42}, {7, 7, 49}
};
/* prate/REFDIV and result save to tmp0 */
tmp0 = prate;
do_div(tmp0, refdiv);
/* ((prate/REFDIV) x FBDIV) and result save to tmp0 */
tmp0 *= fbdiv;
/* ((prate/REFDIV) x FBDIV)/rate and result save to tmp0 */
tmp0 = div64_ul(tmp0, rate);
/* tmp0 is POSTDIV1*POSTDIV2, now we calculate div1 and div2 value */
if (tmp0 <= 7) {
/* (div1 * div2) <= 7, no need to use array search */
*postdiv1 = tmp0;
*postdiv2 = 1;
return 0;
}
/* (div1 * div2) > 7, use array search */
for (index = 0; index < ARRAY_SIZE(postdiv1_2); index++) {
if (tmp0 > postdiv1_2[index][POSTDIV_RESULT_INDEX]) {
continue;
} else {
/* found it */
*postdiv1 = postdiv1_2[index][1];
*postdiv2 = postdiv1_2[index][0];
return 0;
}
}
pr_warn("%s can not find in postdiv array!\n", __func__);
return -EINVAL;
}
/**
* sg2042_get_pll_ctl_setting() - Based on the given FOUTPISTDIV and the input
* FREF to calculate the REFDIV/FBDIV/PSTDIV1/POSTDIV2 combination for pllctrl
* register.
* @req_rate: expected output clock rate, i.e. FOUTPISTDIV
* @parent_rate: input parent clock rate, i.e. FREF
* @best: output to hold calculated combination of REFDIV/FBDIV/PSTDIV1/POSTDIV2
*
* Return:
* %0 - OK
* %-EINVAL - invalid argument
*/
static int sg2042_get_pll_ctl_setting(struct sg2042_pll_ctrl *best,
unsigned long req_rate,
unsigned long parent_rate)
{
unsigned int fbdiv, refdiv, postdiv1, postdiv2;
unsigned long foutpostdiv;
u64 foutvco;
int ret;
u64 tmp;
if (parent_rate != PLL_FREF_SG2042) {
pr_err("INVALID FREF: %ld\n", parent_rate);
return -EINVAL;
}
if (req_rate < PLL_FOUTPOSTDIV_MIN || req_rate > PLL_FOUTPOSTDIV_MAX) {
pr_alert("INVALID FOUTPOSTDIV: %ld\n", req_rate);
return -EINVAL;
}
memset(best, 0, sizeof(struct sg2042_pll_ctrl));
for (refdiv = REFDIV_MIN; refdiv < REFDIV_MAX + 1; refdiv++) {
/* required by hardware: FREF/REFDIV must > 10 */
tmp = parent_rate;
do_div(tmp, refdiv);
if (tmp <= 10)
continue;
for (fbdiv = FBDIV_MIN; fbdiv < FBDIV_MAX + 1; fbdiv++) {
/*
* FOUTVCO = FREF*FBDIV/REFDIV validation
* required by hardware, FOUTVCO must [800MHz, 3200MHz]
*/
foutvco = parent_rate * fbdiv;
do_div(foutvco, refdiv);
if (foutvco < PLL_FOUTVCO_MIN || foutvco > PLL_FOUTVCO_MAX)
continue;
ret = sg2042_pll_get_postdiv_1_2(req_rate, parent_rate,
fbdiv, refdiv,
&postdiv1, &postdiv2);
if (ret)
continue;
/*
* FOUTPOSTDIV = FREF*FBDIV/REFDIV/(POSTDIV1*POSTDIV2)
* = FOUTVCO/(POSTDIV1*POSTDIV2)
*/
tmp = foutvco;
do_div(tmp, (postdiv1 * postdiv2));
foutpostdiv = (unsigned long)tmp;
/* Iterative to approach the expected value */
if (abs_diff(foutpostdiv, req_rate) < abs_diff(best->freq, req_rate)) {
best->freq = foutpostdiv;
best->refdiv = refdiv;
best->fbdiv = fbdiv;
best->postdiv1 = postdiv1;
best->postdiv2 = postdiv2;
if (foutpostdiv == req_rate)
return 0;
}
continue;
}
}
if (best->freq == 0)
return -EINVAL;
else
return 0;
}
/**
* sg2042_clk_pll_recalc_rate() - recalc_rate callback for pll clks
* @hw: ccf use to hook get sg2042_pll_clock
* @parent_rate: parent rate
*
* The is function will be called through clk_get_rate
* and return current rate after decoding reg value
*
* Return: Current rate recalculated.
*/
static unsigned long sg2042_clk_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct sg2042_pll_clock *pll = to_sg2042_pll_clk(hw);
unsigned long rate;
u32 value;
value = readl(pll->base + pll->offset_ctrl);
rate = sg2042_pll_recalc_rate(value, parent_rate);
pr_debug("--> %s: pll_recalc_rate: val = %ld\n",
clk_hw_get_name(hw), rate);
return rate;
}
static int sg2042_clk_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct sg2042_pll_ctrl pctrl_table;
unsigned int value;
long proper_rate;
int ret;
ret = sg2042_get_pll_ctl_setting(&pctrl_table,
min(req->rate, req->max_rate),
req->best_parent_rate);
if (ret) {
proper_rate = 0;
goto out;
}
value = sg2042_pll_ctrl_encode(&pctrl_table);
proper_rate = (long)sg2042_pll_recalc_rate(value, req->best_parent_rate);
out:
pr_debug("--> %s: pll_determine_rate: val = %ld\n",
clk_hw_get_name(hw), proper_rate);
req->rate = proper_rate;
return 0;
}
static int sg2042_clk_pll_set_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate)
{
struct sg2042_pll_clock *pll = to_sg2042_pll_clk(hw);
struct sg2042_pll_ctrl pctrl_table;
unsigned long flags;
u32 value = 0;
int ret;
spin_lock_irqsave(pll->lock, flags);
sg2042_pll_enable(pll, 0);
ret = sg2042_get_pll_ctl_setting(&pctrl_table, rate, parent_rate);
if (ret) {
pr_warn("%s: Can't find a proper pll setting\n", pll->hw.init->name);
goto out;
}
value = sg2042_pll_ctrl_encode(&pctrl_table);
/* write the value to top register */
writel(value, pll->base + pll->offset_ctrl);
out:
sg2042_pll_enable(pll, 1);
spin_unlock_irqrestore(pll->lock, flags);
pr_debug("--> %s: pll_set_rate: val = 0x%x\n",
clk_hw_get_name(hw), value);
return ret;
}
static const struct clk_ops sg2042_clk_pll_ops = {
.recalc_rate = sg2042_clk_pll_recalc_rate,
.determine_rate = sg2042_clk_pll_determine_rate,
.set_rate = sg2042_clk_pll_set_rate,
};
static const struct clk_ops sg2042_clk_pll_ro_ops = {
.recalc_rate = sg2042_clk_pll_recalc_rate,
.determine_rate = sg2042_clk_pll_determine_rate,
};
/*
* Clock initialization macro naming rules:
* FW: use CLK_HW_INIT_FW_NAME
* RO: means Read-Only
*/
#define SG2042_PLL_FW(_id, _name, _parent, _r_ctrl, _shift) \
{ \
.id = _id, \
.hw.init = CLK_HW_INIT_FW_NAME( \
_name, \
_parent, \
&sg2042_clk_pll_ops, \
CLK_GET_RATE_NOCACHE | CLK_GET_ACCURACY_NOCACHE),\
.offset_ctrl = _r_ctrl, \
.shift_status_lock = 8 + (_shift), \
.shift_status_updating = _shift, \
.shift_enable = _shift, \
}
#define SG2042_PLL_FW_RO(_id, _name, _parent, _r_ctrl, _shift) \
{ \
.id = _id, \
.hw.init = CLK_HW_INIT_FW_NAME( \
_name, \
_parent, \
&sg2042_clk_pll_ro_ops, \
CLK_GET_RATE_NOCACHE | CLK_GET_ACCURACY_NOCACHE),\
.offset_ctrl = _r_ctrl, \
.shift_status_lock = 8 + (_shift), \
.shift_status_updating = _shift, \
.shift_enable = _shift, \
}
static struct sg2042_pll_clock sg2042_pll_clks[] = {
SG2042_PLL_FW(MPLL_CLK, "mpll_clock", "cgi_main", R_MPLL_CONTROL, 0),
SG2042_PLL_FW_RO(FPLL_CLK, "fpll_clock", "cgi_main", R_FPLL_CONTROL, 3),
SG2042_PLL_FW_RO(DPLL0_CLK, "dpll0_clock", "cgi_dpll0", R_DPLL0_CONTROL, 4),
SG2042_PLL_FW_RO(DPLL1_CLK, "dpll1_clock", "cgi_dpll1", R_DPLL1_CONTROL, 5),
};
static DEFINE_SPINLOCK(sg2042_clk_lock);
static int sg2042_clk_register_plls(struct device *dev,
struct sg2042_clk_data *clk_data,
struct sg2042_pll_clock pll_clks[],
int num_pll_clks)
{
struct sg2042_pll_clock *pll;
struct clk_hw *hw;
int i, ret = 0;
for (i = 0; i < num_pll_clks; i++) {
pll = &pll_clks[i];
/* assign these for ops usage during registration */
pll->base = clk_data->iobase;
pll->lock = &sg2042_clk_lock;
hw = &pll->hw;
ret = devm_clk_hw_register(dev, hw);
if (ret) {
pr_err("failed to register clock %s\n", pll->hw.init->name);
break;
}
clk_data->onecell_data.hws[pll->id] = hw;
}
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;
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_pll_probe(struct platform_device *pdev)
{
struct sg2042_clk_data *clk_data = NULL;
int num_clks;
int ret;
num_clks = ARRAY_SIZE(sg2042_pll_clks);
ret = sg2042_init_clkdata(pdev, num_clks, &clk_data);
if (ret)
goto error_out;
ret = sg2042_clk_register_plls(&pdev->dev, clk_data, sg2042_pll_clks,
num_clks);
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_pll_match[] = {
{ .compatible = "sophgo,sg2042-pll" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sg2042_pll_match);
static struct platform_driver sg2042_pll_driver = {
.probe = sg2042_pll_probe,
.driver = {
.name = "clk-sophgo-sg2042-pll",
.of_match_table = sg2042_pll_match,
.suppress_bind_attrs = true,
},
};
module_platform_driver(sg2042_pll_driver);
MODULE_AUTHOR("Chen Wang");
MODULE_DESCRIPTION("Sophgo SG2042 pll clock driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,291 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sophgo SG2042 RP clock Driver
*
* Copyright (C) 2024 Sophgo Technology Inc.
* Copyright (C) 2024 Chen Wang <unicorn_wang@outlook.com>
*/
#include <linux/array_size.h>
#include <linux/clk-provider.h>
#include <linux/platform_device.h>
#include <dt-bindings/clock/sophgo,sg2042-rpgate.h>
#include "clk-sg2042.h"
#define R_SYSGATE_BEGIN 0x0368
#define R_RP_RXU_CLK_ENABLE (0x0368 - R_SYSGATE_BEGIN)
#define R_MP0_STATUS_REG (0x0380 - R_SYSGATE_BEGIN)
#define R_MP0_CONTROL_REG (0x0384 - R_SYSGATE_BEGIN)
#define R_MP1_STATUS_REG (0x0388 - R_SYSGATE_BEGIN)
#define R_MP1_CONTROL_REG (0x038C - R_SYSGATE_BEGIN)
#define R_MP2_STATUS_REG (0x0390 - R_SYSGATE_BEGIN)
#define R_MP2_CONTROL_REG (0x0394 - R_SYSGATE_BEGIN)
#define R_MP3_STATUS_REG (0x0398 - R_SYSGATE_BEGIN)
#define R_MP3_CONTROL_REG (0x039C - R_SYSGATE_BEGIN)
#define R_MP4_STATUS_REG (0x03A0 - R_SYSGATE_BEGIN)
#define R_MP4_CONTROL_REG (0x03A4 - R_SYSGATE_BEGIN)
#define R_MP5_STATUS_REG (0x03A8 - R_SYSGATE_BEGIN)
#define R_MP5_CONTROL_REG (0x03AC - R_SYSGATE_BEGIN)
#define R_MP6_STATUS_REG (0x03B0 - R_SYSGATE_BEGIN)
#define R_MP6_CONTROL_REG (0x03B4 - R_SYSGATE_BEGIN)
#define R_MP7_STATUS_REG (0x03B8 - R_SYSGATE_BEGIN)
#define R_MP7_CONTROL_REG (0x03BC - R_SYSGATE_BEGIN)
#define R_MP8_STATUS_REG (0x03C0 - R_SYSGATE_BEGIN)
#define R_MP8_CONTROL_REG (0x03C4 - R_SYSGATE_BEGIN)
#define R_MP9_STATUS_REG (0x03C8 - R_SYSGATE_BEGIN)
#define R_MP9_CONTROL_REG (0x03CC - R_SYSGATE_BEGIN)
#define R_MP10_STATUS_REG (0x03D0 - R_SYSGATE_BEGIN)
#define R_MP10_CONTROL_REG (0x03D4 - R_SYSGATE_BEGIN)
#define R_MP11_STATUS_REG (0x03D8 - R_SYSGATE_BEGIN)
#define R_MP11_CONTROL_REG (0x03DC - R_SYSGATE_BEGIN)
#define R_MP12_STATUS_REG (0x03E0 - R_SYSGATE_BEGIN)
#define R_MP12_CONTROL_REG (0x03E4 - R_SYSGATE_BEGIN)
#define R_MP13_STATUS_REG (0x03E8 - R_SYSGATE_BEGIN)
#define R_MP13_CONTROL_REG (0x03EC - R_SYSGATE_BEGIN)
#define R_MP14_STATUS_REG (0x03F0 - R_SYSGATE_BEGIN)
#define R_MP14_CONTROL_REG (0x03F4 - R_SYSGATE_BEGIN)
#define R_MP15_STATUS_REG (0x03F8 - R_SYSGATE_BEGIN)
#define R_MP15_CONTROL_REG (0x03FC - R_SYSGATE_BEGIN)
/**
* struct sg2042_rpgate_clock - Gate clock for RP(riscv processors) subsystem
* @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_rpgate_clock {
struct clk_hw hw;
unsigned int id;
u32 offset_enable;
u8 bit_idx;
};
/*
* Clock initialization macro naming rules:
* FW: use CLK_HW_INIT_FW_NAME
*/
#define SG2042_GATE_FW(_id, _name, _parent, _flags, \
_r_enable, _bit_idx) { \
.hw.init = CLK_HW_INIT_FW_NAME( \
_name, \
_parent, \
NULL, \
_flags), \
.id = _id, \
.offset_enable = _r_enable, \
.bit_idx = _bit_idx, \
}
/*
* Gate clocks for RP subsystem (including the MP subsystem), which control
* registers are defined in SYS_CTRL.
*/
static const struct sg2042_rpgate_clock sg2042_gate_rp[] = {
/* downstream of clk_gate_rp_cpu_normal about rxu */
SG2042_GATE_FW(GATE_CLK_RXU0, "clk_gate_rxu0", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 0),
SG2042_GATE_FW(GATE_CLK_RXU1, "clk_gate_rxu1", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 1),
SG2042_GATE_FW(GATE_CLK_RXU2, "clk_gate_rxu2", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 2),
SG2042_GATE_FW(GATE_CLK_RXU3, "clk_gate_rxu3", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 3),
SG2042_GATE_FW(GATE_CLK_RXU4, "clk_gate_rxu4", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 4),
SG2042_GATE_FW(GATE_CLK_RXU5, "clk_gate_rxu5", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 5),
SG2042_GATE_FW(GATE_CLK_RXU6, "clk_gate_rxu6", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 6),
SG2042_GATE_FW(GATE_CLK_RXU7, "clk_gate_rxu7", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 7),
SG2042_GATE_FW(GATE_CLK_RXU8, "clk_gate_rxu8", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 8),
SG2042_GATE_FW(GATE_CLK_RXU9, "clk_gate_rxu9", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 9),
SG2042_GATE_FW(GATE_CLK_RXU10, "clk_gate_rxu10", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 10),
SG2042_GATE_FW(GATE_CLK_RXU11, "clk_gate_rxu11", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 11),
SG2042_GATE_FW(GATE_CLK_RXU12, "clk_gate_rxu12", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 12),
SG2042_GATE_FW(GATE_CLK_RXU13, "clk_gate_rxu13", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 13),
SG2042_GATE_FW(GATE_CLK_RXU14, "clk_gate_rxu14", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 14),
SG2042_GATE_FW(GATE_CLK_RXU15, "clk_gate_rxu15", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 15),
SG2042_GATE_FW(GATE_CLK_RXU16, "clk_gate_rxu16", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 16),
SG2042_GATE_FW(GATE_CLK_RXU17, "clk_gate_rxu17", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 17),
SG2042_GATE_FW(GATE_CLK_RXU18, "clk_gate_rxu18", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 18),
SG2042_GATE_FW(GATE_CLK_RXU19, "clk_gate_rxu19", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 19),
SG2042_GATE_FW(GATE_CLK_RXU20, "clk_gate_rxu20", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 20),
SG2042_GATE_FW(GATE_CLK_RXU21, "clk_gate_rxu21", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 21),
SG2042_GATE_FW(GATE_CLK_RXU22, "clk_gate_rxu22", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 22),
SG2042_GATE_FW(GATE_CLK_RXU23, "clk_gate_rxu23", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 23),
SG2042_GATE_FW(GATE_CLK_RXU24, "clk_gate_rxu24", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 24),
SG2042_GATE_FW(GATE_CLK_RXU25, "clk_gate_rxu25", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 25),
SG2042_GATE_FW(GATE_CLK_RXU26, "clk_gate_rxu26", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 26),
SG2042_GATE_FW(GATE_CLK_RXU27, "clk_gate_rxu27", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 27),
SG2042_GATE_FW(GATE_CLK_RXU28, "clk_gate_rxu28", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 28),
SG2042_GATE_FW(GATE_CLK_RXU29, "clk_gate_rxu29", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 29),
SG2042_GATE_FW(GATE_CLK_RXU30, "clk_gate_rxu30", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 30),
SG2042_GATE_FW(GATE_CLK_RXU31, "clk_gate_rxu31", "rpgate",
0, R_RP_RXU_CLK_ENABLE, 31),
/* downstream of clk_gate_rp_cpu_normal about mp */
SG2042_GATE_FW(GATE_CLK_MP0, "clk_gate_mp0", "rpgate",
CLK_IS_CRITICAL, R_MP0_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP1, "clk_gate_mp1", "rpgate",
CLK_IS_CRITICAL, R_MP1_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP2, "clk_gate_mp2", "rpgate",
CLK_IS_CRITICAL, R_MP2_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP3, "clk_gate_mp3", "rpgate",
CLK_IS_CRITICAL, R_MP3_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP4, "clk_gate_mp4", "rpgate",
CLK_IS_CRITICAL, R_MP4_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP5, "clk_gate_mp5", "rpgate",
CLK_IS_CRITICAL, R_MP5_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP6, "clk_gate_mp6", "rpgate",
CLK_IS_CRITICAL, R_MP6_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP7, "clk_gate_mp7", "rpgate",
CLK_IS_CRITICAL, R_MP7_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP8, "clk_gate_mp8", "rpgate",
CLK_IS_CRITICAL, R_MP8_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP9, "clk_gate_mp9", "rpgate",
CLK_IS_CRITICAL, R_MP9_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP10, "clk_gate_mp10", "rpgate",
CLK_IS_CRITICAL, R_MP10_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP11, "clk_gate_mp11", "rpgate",
CLK_IS_CRITICAL, R_MP11_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP12, "clk_gate_mp12", "rpgate",
CLK_IS_CRITICAL, R_MP12_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP13, "clk_gate_mp13", "rpgate",
CLK_IS_CRITICAL, R_MP13_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP14, "clk_gate_mp14", "rpgate",
CLK_IS_CRITICAL, R_MP14_CONTROL_REG, 0),
SG2042_GATE_FW(GATE_CLK_MP15, "clk_gate_mp15", "rpgate",
CLK_IS_CRITICAL, R_MP15_CONTROL_REG, 0),
};
static DEFINE_SPINLOCK(sg2042_clk_lock);
static int sg2042_clk_register_rpgates(struct device *dev,
struct sg2042_clk_data *clk_data,
const struct sg2042_rpgate_clock gate_clks[],
int num_gate_clks)
{
const struct sg2042_rpgate_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;
}
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;
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_rpgate_probe(struct platform_device *pdev)
{
struct sg2042_clk_data *clk_data = NULL;
int num_clks;
int ret;
num_clks = ARRAY_SIZE(sg2042_gate_rp);
ret = sg2042_init_clkdata(pdev, num_clks, &clk_data);
if (ret)
goto error_out;
ret = sg2042_clk_register_rpgates(&pdev->dev, clk_data, sg2042_gate_rp,
num_clks);
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_rpgate_match[] = {
{ .compatible = "sophgo,sg2042-rpgate" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sg2042_rpgate_match);
static struct platform_driver sg2042_rpgate_driver = {
.probe = sg2042_rpgate_probe,
.driver = {
.name = "clk-sophgo-sg2042-rpgate",
.of_match_table = sg2042_rpgate_match,
.suppress_bind_attrs = true,
},
};
module_platform_driver(sg2042_rpgate_driver);
MODULE_AUTHOR("Chen Wang");
MODULE_DESCRIPTION("Sophgo SG2042 rp subsystem clock driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _CLK_SOPHGO_SG2042_H_
#define _CLK_SOPHGO_SG2042_H_
#include <linux/io.h>
#include <linux/clk-provider.h>
/**
* struct sg2042_clk_data - Common data of clock-controller
* @iobase: base address of clock-controller
* @onecell_data: used for adding providers.
*/
struct sg2042_clk_data {
void __iomem *iobase;
struct clk_hw_onecell_data onecell_data;
};
#endif /* _CLK_SOPHGO_SG2042_H_ */
@@ -0,0 +1,628 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sophgo SG2044 PLL clock controller driver
*
* Copyright (C) 2025 Inochi Amaoto <inochiama@gmail.com>
*/
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/math64.h>
#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/spinlock.h>
#include <dt-bindings/clock/sophgo,sg2044-pll.h>
/* Low Control part */
#define PLL_VCOSEL_MASK GENMASK(17, 16)
/* High Control part */
#define PLL_FBDIV_MASK GENMASK(11, 0)
#define PLL_REFDIV_MASK GENMASK(17, 12)
#define PLL_POSTDIV1_MASK GENMASK(20, 18)
#define PLL_POSTDIV2_MASK GENMASK(23, 21)
#define PLL_CALIBRATE_EN BIT(24)
#define PLL_CALIBRATE_MASK GENMASK(29, 27)
#define PLL_CALIBRATE_DEFAULT FIELD_PREP(PLL_CALIBRATE_MASK, 2)
#define PLL_UPDATE_EN BIT(30)
#define PLL_HIGH_CTRL_MASK \
(PLL_FBDIV_MASK | PLL_REFDIV_MASK | \
PLL_POSTDIV1_MASK | PLL_POSTDIV2_MASK | \
PLL_CALIBRATE_EN | PLL_CALIBRATE_MASK | \
PLL_UPDATE_EN)
#define PLL_HIGH_CTRL_OFFSET 4
#define PLL_VCOSEL_1G6 0x2
#define PLL_VCOSEL_2G4 0x3
#define PLL_LIMIT_FOUTVCO 0
#define PLL_LIMIT_FOUT 1
#define PLL_LIMIT_REFDIV 2
#define PLL_LIMIT_FBDIV 3
#define PLL_LIMIT_POSTDIV1 4
#define PLL_LIMIT_POSTDIV2 5
#define for_each_pll_limit_range(_var, _limit) \
for (_var = (_limit)->min; _var <= (_limit)->max; _var++)
struct sg2044_pll_limit {
u64 min;
u64 max;
};
struct sg2044_pll_internal {
u32 ctrl_offset;
u32 status_offset;
u32 enable_offset;
u8 status_lock_bit;
u8 status_updating_bit;
u8 enable_bit;
const struct sg2044_pll_limit *limits;
};
struct sg2044_clk_common {
struct clk_hw hw;
struct regmap *regmap;
spinlock_t *lock;
unsigned int id;
};
struct sg2044_pll {
struct sg2044_clk_common common;
struct sg2044_pll_internal pll;
unsigned int syscon_offset;
};
struct sg2044_pll_desc_data {
struct sg2044_clk_common * const *pll;
u16 num_pll;
};
#define SG2044_SYSCON_PLL_OFFSET 0x98
struct sg2044_pll_ctrl {
spinlock_t lock;
struct clk_hw_onecell_data data;
};
#define hw_to_sg2044_clk_common(_hw) \
container_of((_hw), struct sg2044_clk_common, hw)
static inline bool sg2044_clk_fit_limit(u64 value,
const struct sg2044_pll_limit *limit)
{
return value >= limit->min && value <= limit->max;
}
static inline struct sg2044_pll *hw_to_sg2044_pll(struct clk_hw *hw)
{
return container_of(hw_to_sg2044_clk_common(hw),
struct sg2044_pll, common);
}
static unsigned long sg2044_pll_calc_vco_rate(unsigned long parent_rate,
unsigned long refdiv,
unsigned long fbdiv)
{
u64 numerator = parent_rate * fbdiv;
return div64_ul(numerator, refdiv);
}
static unsigned long sg2044_pll_calc_rate(unsigned long parent_rate,
unsigned long refdiv,
unsigned long fbdiv,
unsigned long postdiv1,
unsigned long postdiv2)
{
u64 numerator, denominator;
numerator = parent_rate * fbdiv;
denominator = refdiv * (postdiv1 + 1) * (postdiv2 + 1);
return div64_u64(numerator, denominator);
}
static unsigned long sg2044_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct sg2044_pll *pll = hw_to_sg2044_pll(hw);
u32 value;
int ret;
ret = regmap_read(pll->common.regmap,
pll->syscon_offset + pll->pll.ctrl_offset + PLL_HIGH_CTRL_OFFSET,
&value);
if (ret < 0)
return 0;
return sg2044_pll_calc_rate(parent_rate,
FIELD_GET(PLL_REFDIV_MASK, value),
FIELD_GET(PLL_FBDIV_MASK, value),
FIELD_GET(PLL_POSTDIV1_MASK, value),
FIELD_GET(PLL_POSTDIV2_MASK, value));
}
static bool pll_is_better_rate(unsigned long target, unsigned long now,
unsigned long best)
{
return abs_diff(target, now) < abs_diff(target, best);
}
static int sg2042_pll_compute_postdiv(const struct sg2044_pll_limit *limits,
unsigned long target,
unsigned long parent_rate,
unsigned int refdiv,
unsigned int fbdiv,
unsigned int *postdiv1,
unsigned int *postdiv2)
{
unsigned int div1, div2;
unsigned long tmp, best_rate = 0;
unsigned int best_div1 = 0, best_div2 = 0;
for_each_pll_limit_range(div2, &limits[PLL_LIMIT_POSTDIV2]) {
for_each_pll_limit_range(div1, &limits[PLL_LIMIT_POSTDIV1]) {
tmp = sg2044_pll_calc_rate(parent_rate,
refdiv, fbdiv,
div1, div2);
if (tmp > target)
continue;
if (pll_is_better_rate(target, tmp, best_rate)) {
best_div1 = div1;
best_div2 = div2;
best_rate = tmp;
if (tmp == target)
goto find;
}
}
}
find:
if (best_rate) {
*postdiv1 = best_div1;
*postdiv2 = best_div2;
return 0;
}
return -EINVAL;
}
static int sg2044_compute_pll_setting(const struct sg2044_pll_limit *limits,
unsigned long req_rate,
unsigned long parent_rate,
unsigned int *value)
{
unsigned int refdiv, fbdiv, postdiv1, postdiv2;
unsigned int best_refdiv, best_fbdiv, best_postdiv1, best_postdiv2;
unsigned long tmp, best_rate = 0;
int ret;
for_each_pll_limit_range(fbdiv, &limits[PLL_LIMIT_FBDIV]) {
for_each_pll_limit_range(refdiv, &limits[PLL_LIMIT_REFDIV]) {
u64 vco = sg2044_pll_calc_vco_rate(parent_rate,
refdiv, fbdiv);
if (!sg2044_clk_fit_limit(vco, &limits[PLL_LIMIT_FOUTVCO]))
continue;
ret = sg2042_pll_compute_postdiv(limits,
req_rate, parent_rate,
refdiv, fbdiv,
&postdiv1, &postdiv2);
if (ret)
continue;
tmp = sg2044_pll_calc_rate(parent_rate,
refdiv, fbdiv,
postdiv1, postdiv2);
if (pll_is_better_rate(req_rate, tmp, best_rate)) {
best_refdiv = refdiv;
best_fbdiv = fbdiv;
best_postdiv1 = postdiv1;
best_postdiv2 = postdiv2;
best_rate = tmp;
if (tmp == req_rate)
goto find;
}
}
}
find:
if (best_rate) {
*value = FIELD_PREP(PLL_REFDIV_MASK, best_refdiv) |
FIELD_PREP(PLL_FBDIV_MASK, best_fbdiv) |
FIELD_PREP(PLL_POSTDIV1_MASK, best_postdiv1) |
FIELD_PREP(PLL_POSTDIV2_MASK, best_postdiv2);
return 0;
}
return -EINVAL;
}
static int sg2044_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct sg2044_pll *pll = hw_to_sg2044_pll(hw);
unsigned int value;
u64 target;
int ret;
target = clamp(req->rate, pll->pll.limits[PLL_LIMIT_FOUT].min,
pll->pll.limits[PLL_LIMIT_FOUT].max);
ret = sg2044_compute_pll_setting(pll->pll.limits, target,
req->best_parent_rate, &value);
if (ret < 0)
return ret;
req->rate = sg2044_pll_calc_rate(req->best_parent_rate,
FIELD_GET(PLL_REFDIV_MASK, value),
FIELD_GET(PLL_FBDIV_MASK, value),
FIELD_GET(PLL_POSTDIV1_MASK, value),
FIELD_GET(PLL_POSTDIV2_MASK, value));
return 0;
}
static int sg2044_pll_poll_update(struct sg2044_pll *pll)
{
int ret;
unsigned int value;
ret = regmap_read_poll_timeout_atomic(pll->common.regmap,
pll->syscon_offset + pll->pll.status_offset,
value,
(value & BIT(pll->pll.status_lock_bit)),
1, 100000);
if (ret)
return ret;
return regmap_read_poll_timeout_atomic(pll->common.regmap,
pll->syscon_offset + pll->pll.status_offset,
value,
(!(value & BIT(pll->pll.status_updating_bit))),
1, 100000);
}
static int sg2044_pll_enable(struct sg2044_pll *pll, bool en)
{
if (en) {
if (sg2044_pll_poll_update(pll) < 0)
pr_warn("%s: fail to lock pll\n", clk_hw_get_name(&pll->common.hw));
return regmap_set_bits(pll->common.regmap,
pll->syscon_offset + pll->pll.enable_offset,
BIT(pll->pll.enable_bit));
}
return regmap_clear_bits(pll->common.regmap,
pll->syscon_offset + pll->pll.enable_offset,
BIT(pll->pll.enable_bit));
}
static int sg2044_pll_update_vcosel(struct sg2044_pll *pll, u64 rate)
{
unsigned int sel;
if (rate < U64_C(2400000000))
sel = PLL_VCOSEL_1G6;
else
sel = PLL_VCOSEL_2G4;
return regmap_write_bits(pll->common.regmap,
pll->syscon_offset + pll->pll.ctrl_offset,
PLL_VCOSEL_MASK,
FIELD_PREP(PLL_VCOSEL_MASK, sel));
}
static int sg2044_pll_set_rate(struct clk_hw *hw,
unsigned long rate, unsigned long parent_rate)
{
struct sg2044_pll *pll = hw_to_sg2044_pll(hw);
unsigned int value;
u64 vco;
int ret;
ret = sg2044_compute_pll_setting(pll->pll.limits, rate,
parent_rate, &value);
if (ret < 0)
return ret;
vco = sg2044_pll_calc_vco_rate(parent_rate,
FIELD_GET(PLL_REFDIV_MASK, value),
FIELD_GET(PLL_FBDIV_MASK, value));
value |= PLL_CALIBRATE_EN;
value |= PLL_CALIBRATE_DEFAULT;
value |= PLL_UPDATE_EN;
guard(spinlock_irqsave)(pll->common.lock);
ret = sg2044_pll_enable(pll, false);
if (ret)
return ret;
sg2044_pll_update_vcosel(pll, vco);
regmap_write_bits(pll->common.regmap,
pll->syscon_offset + pll->pll.ctrl_offset +
PLL_HIGH_CTRL_OFFSET,
PLL_HIGH_CTRL_MASK, value);
sg2044_pll_enable(pll, true);
return ret;
}
static const struct clk_ops sg2044_pll_ops = {
.recalc_rate = sg2044_pll_recalc_rate,
.determine_rate = sg2044_pll_determine_rate,
.set_rate = sg2044_pll_set_rate,
};
static const struct clk_ops sg2044_pll_ro_ops = {
.recalc_rate = sg2044_pll_recalc_rate,
};
#define SG2044_CLK_COMMON_PDATA(_id, _name, _parents, _op, _flags) \
{ \
.hw.init = CLK_HW_INIT_PARENTS_DATA(_name, _parents, \
_op, (_flags)), \
.id = (_id), \
}
#define DEFINE_SG2044_PLL(_id, _name, _parent, _flags, \
_ctrl_offset, \
_status_offset, _status_lock_bit, \
_status_updating_bit, \
_enable_offset, _enable_bit, \
_limits) \
struct sg2044_pll _name = { \
.common = SG2044_CLK_COMMON_PDATA(_id, #_name, _parent, \
&sg2044_pll_ops, \
(_flags)), \
.pll = { \
.ctrl_offset = (_ctrl_offset), \
.status_offset = (_status_offset), \
.enable_offset = (_enable_offset), \
.status_lock_bit = (_status_lock_bit), \
.status_updating_bit = (_status_updating_bit), \
.enable_bit = (_enable_bit), \
.limits = (_limits), \
}, \
}
#define DEFINE_SG2044_PLL_RO(_id, _name, _parent, _flags, \
_ctrl_offset, \
_status_offset, _status_lock_bit, \
_status_updating_bit, \
_enable_offset, _enable_bit, \
_limits) \
struct sg2044_pll _name = { \
.common = SG2044_CLK_COMMON_PDATA(_id, #_name, _parent, \
&sg2044_pll_ro_ops, \
(_flags)), \
.pll = { \
.ctrl_offset = (_ctrl_offset), \
.status_offset = (_status_offset), \
.enable_offset = (_enable_offset), \
.status_lock_bit = (_status_lock_bit), \
.status_updating_bit = (_status_updating_bit), \
.enable_bit = (_enable_bit), \
.limits = (_limits), \
}, \
}
static const struct clk_parent_data osc_parents[] = {
{ .index = 0 },
};
static const struct sg2044_pll_limit pll_limits[] = {
[PLL_LIMIT_FOUTVCO] = {
.min = U64_C(1600000000),
.max = U64_C(3200000000),
},
[PLL_LIMIT_FOUT] = {
.min = U64_C(25000),
.max = U64_C(3200000000),
},
[PLL_LIMIT_REFDIV] = {
.min = U64_C(1),
.max = U64_C(63),
},
[PLL_LIMIT_FBDIV] = {
.min = U64_C(8),
.max = U64_C(1066),
},
[PLL_LIMIT_POSTDIV1] = {
.min = U64_C(0),
.max = U64_C(7),
},
[PLL_LIMIT_POSTDIV2] = {
.min = U64_C(0),
.max = U64_C(7),
},
};
static DEFINE_SG2044_PLL_RO(CLK_FPLL0, clk_fpll0, osc_parents, CLK_IS_CRITICAL,
0x58, 0x00, 22, 6,
0x04, 6, pll_limits);
static DEFINE_SG2044_PLL_RO(CLK_FPLL1, clk_fpll1, osc_parents, CLK_IS_CRITICAL,
0x60, 0x00, 23, 7,
0x04, 7, pll_limits);
static DEFINE_SG2044_PLL_RO(CLK_FPLL2, clk_fpll2, osc_parents, CLK_IS_CRITICAL,
0x20, 0x08, 16, 0,
0x0c, 0, pll_limits);
static DEFINE_SG2044_PLL_RO(CLK_DPLL0, clk_dpll0, osc_parents, CLK_IS_CRITICAL,
0x68, 0x00, 24, 8,
0x04, 8, pll_limits);
static DEFINE_SG2044_PLL_RO(CLK_DPLL1, clk_dpll1, osc_parents, CLK_IS_CRITICAL,
0x70, 0x00, 25, 9,
0x04, 9, pll_limits);
static DEFINE_SG2044_PLL_RO(CLK_DPLL2, clk_dpll2, osc_parents, CLK_IS_CRITICAL,
0x78, 0x00, 26, 10,
0x04, 10, pll_limits);
static DEFINE_SG2044_PLL_RO(CLK_DPLL3, clk_dpll3, osc_parents, CLK_IS_CRITICAL,
0x80, 0x00, 27, 11,
0x04, 11, pll_limits);
static DEFINE_SG2044_PLL_RO(CLK_DPLL4, clk_dpll4, osc_parents, CLK_IS_CRITICAL,
0x88, 0x00, 28, 12,
0x04, 12, pll_limits);
static DEFINE_SG2044_PLL_RO(CLK_DPLL5, clk_dpll5, osc_parents, CLK_IS_CRITICAL,
0x90, 0x00, 29, 13,
0x04, 13, pll_limits);
static DEFINE_SG2044_PLL_RO(CLK_DPLL6, clk_dpll6, osc_parents, CLK_IS_CRITICAL,
0x98, 0x00, 30, 14,
0x04, 14, pll_limits);
static DEFINE_SG2044_PLL_RO(CLK_DPLL7, clk_dpll7, osc_parents, CLK_IS_CRITICAL,
0xa0, 0x00, 31, 15,
0x04, 15, pll_limits);
static DEFINE_SG2044_PLL(CLK_MPLL0, clk_mpll0, osc_parents, CLK_IS_CRITICAL,
0x28, 0x00, 16, 0,
0x04, 0, pll_limits);
static DEFINE_SG2044_PLL(CLK_MPLL1, clk_mpll1, osc_parents, CLK_IS_CRITICAL,
0x30, 0x00, 17, 1,
0x04, 1, pll_limits);
static DEFINE_SG2044_PLL(CLK_MPLL2, clk_mpll2, osc_parents, CLK_IS_CRITICAL,
0x38, 0x00, 18, 2,
0x04, 2, pll_limits);
static DEFINE_SG2044_PLL(CLK_MPLL3, clk_mpll3, osc_parents, CLK_IS_CRITICAL,
0x40, 0x00, 19, 3,
0x04, 3, pll_limits);
static DEFINE_SG2044_PLL(CLK_MPLL4, clk_mpll4, osc_parents, CLK_IS_CRITICAL,
0x48, 0x00, 20, 4,
0x04, 4, pll_limits);
static DEFINE_SG2044_PLL(CLK_MPLL5, clk_mpll5, osc_parents, CLK_IS_CRITICAL,
0x50, 0x00, 21, 5,
0x04, 5, pll_limits);
static struct sg2044_clk_common * const sg2044_pll_commons[] = {
&clk_fpll0.common,
&clk_fpll1.common,
&clk_fpll2.common,
&clk_dpll0.common,
&clk_dpll1.common,
&clk_dpll2.common,
&clk_dpll3.common,
&clk_dpll4.common,
&clk_dpll5.common,
&clk_dpll6.common,
&clk_dpll7.common,
&clk_mpll0.common,
&clk_mpll1.common,
&clk_mpll2.common,
&clk_mpll3.common,
&clk_mpll4.common,
&clk_mpll5.common,
};
static int sg2044_pll_init_ctrl(struct device *dev, struct regmap *regmap,
struct sg2044_pll_ctrl *ctrl,
const struct sg2044_pll_desc_data *desc)
{
int ret, i;
spin_lock_init(&ctrl->lock);
for (i = 0; i < desc->num_pll; i++) {
struct sg2044_clk_common *common = desc->pll[i];
struct sg2044_pll *pll = hw_to_sg2044_pll(&common->hw);
common->lock = &ctrl->lock;
common->regmap = regmap;
pll->syscon_offset = SG2044_SYSCON_PLL_OFFSET;
ret = devm_clk_hw_register(dev, &common->hw);
if (ret)
return ret;
ctrl->data.hws[common->id] = &common->hw;
}
return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
&ctrl->data);
}
static int sg2044_pll_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sg2044_pll_ctrl *ctrl;
const struct sg2044_pll_desc_data *desc;
struct regmap *regmap;
regmap = device_node_to_regmap(pdev->dev.parent->of_node);
if (IS_ERR(regmap))
return dev_err_probe(dev, PTR_ERR(regmap),
"fail to get the regmap for PLL\n");
desc = (const struct sg2044_pll_desc_data *)platform_get_device_id(pdev)->driver_data;
if (!desc)
return dev_err_probe(dev, -EINVAL, "no match data for platform\n");
ctrl = devm_kzalloc(dev, struct_size(ctrl, data.hws, desc->num_pll), GFP_KERNEL);
if (!ctrl)
return -ENOMEM;
ctrl->data.num = desc->num_pll;
return sg2044_pll_init_ctrl(dev, regmap, ctrl, desc);
}
static const struct sg2044_pll_desc_data sg2044_pll_desc_data = {
.pll = sg2044_pll_commons,
.num_pll = ARRAY_SIZE(sg2044_pll_commons),
};
static const struct platform_device_id sg2044_pll_match[] = {
{ .name = "sg2044-pll",
.driver_data = (unsigned long)&sg2044_pll_desc_data },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, sg2044_pll_match);
static struct platform_driver sg2044_clk_driver = {
.probe = sg2044_pll_probe,
.driver = {
.name = "sg2044-pll",
},
.id_table = sg2044_pll_match,
};
module_platform_driver(sg2044_clk_driver);
MODULE_AUTHOR("Inochi Amaoto <inochiama@gmail.com>");
MODULE_DESCRIPTION("Sophgo SG2044 pll clock driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff