Merge tag 'spacemit-clk-for-6.16-1' of https://github.com/spacemit-com/linux into clk-spacemit

Pull SpacemiT clk driver updates from Yixun Lan:

 - Add clock driver for SpacemiT K1 SoC
 - Add TWSI8 clock, workaround the read quirk

* tag 'spacemit-clk-for-6.16-1' of https://github.com/spacemit-com/linux:
  clk: spacemit: k1: Add TWSI8 bus and function clocks
  clk: spacemit: Add clock support for SpacemiT K1 SoC
  dt-bindings: clock: spacemit: Add spacemit,k1-pll
  dt-bindings: soc: spacemit: Add spacemit,k1-syscon
This commit is contained in:
Stephen Boyd
2025-05-06 10:56:52 -07:00
15 changed files with 2474 additions and 0 deletions
@@ -0,0 +1,50 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/clock/spacemit,k1-pll.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: SpacemiT K1 PLL
maintainers:
- Haylen Chu <heylenay@4d2.org>
properties:
compatible:
const: spacemit,k1-pll
reg:
maxItems: 1
clocks:
description: External 24MHz oscillator
spacemit,mpmu:
$ref: /schemas/types.yaml#/definitions/phandle
description:
Phandle to the "Main PMU (MPMU)" syscon. It is used to check PLL
lock status.
"#clock-cells":
const: 1
description:
See <dt-bindings/clock/spacemit,k1-syscon.h> for valid indices.
required:
- compatible
- reg
- clocks
- spacemit,mpmu
- "#clock-cells"
additionalProperties: false
examples:
- |
clock-controller@d4090000 {
compatible = "spacemit,k1-pll";
reg = <0xd4090000 0x1000>;
clocks = <&vctcxo_24m>;
spacemit,mpmu = <&sysctl_mpmu>;
#clock-cells = <1>;
};
@@ -0,0 +1,80 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/soc/spacemit/spacemit,k1-syscon.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: SpacemiT K1 SoC System Controller
maintainers:
- Haylen Chu <heylenay@4d2.org>
description:
System controllers found on SpacemiT K1 SoC, which are capable of
clock, reset and power-management functions.
properties:
compatible:
enum:
- spacemit,k1-syscon-apbc
- spacemit,k1-syscon-apmu
- spacemit,k1-syscon-mpmu
reg:
maxItems: 1
clocks:
maxItems: 4
clock-names:
items:
- const: osc
- const: vctcxo_1m
- const: vctcxo_3m
- const: vctcxo_24m
"#clock-cells":
const: 1
description:
See <dt-bindings/clock/spacemit,k1-syscon.h> for valid indices.
"#power-domain-cells":
const: 1
"#reset-cells":
const: 1
required:
- compatible
- reg
- clocks
- clock-names
- "#clock-cells"
- "#reset-cells"
allOf:
- if:
properties:
compatible:
contains:
const: spacemit,k1-syscon-apbc
then:
properties:
"#power-domain-cells": false
else:
required:
- "#power-domain-cells"
additionalProperties: false
examples:
- |
system-controller@d4050000 {
compatible = "spacemit,k1-syscon-mpmu";
reg = <0xd4050000 0x209c>;
clocks = <&osc>, <&vctcxo_1m>, <&vctcxo_3m>, <&vctcxo_24m>;
clock-names = "osc", "vctcxo_1m", "vctcxo_3m", "vctcxo_24m";
#clock-cells = <1>;
#power-domain-cells = <1>;
#reset-cells = <1>;
};
+1
View File
@@ -517,6 +517,7 @@ source "drivers/clk/samsung/Kconfig"
source "drivers/clk/sifive/Kconfig"
source "drivers/clk/socfpga/Kconfig"
source "drivers/clk/sophgo/Kconfig"
source "drivers/clk/spacemit/Kconfig"
source "drivers/clk/sprd/Kconfig"
source "drivers/clk/starfive/Kconfig"
source "drivers/clk/sunxi/Kconfig"
+1
View File
@@ -145,6 +145,7 @@ obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/
obj-$(CONFIG_CLK_SIFIVE) += sifive/
obj-y += socfpga/
obj-y += sophgo/
obj-y += spacemit/
obj-$(CONFIG_PLAT_SPEAR) += spear/
obj-y += sprd/
obj-$(CONFIG_ARCH_STI) += st/
+18
View File
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: GPL-2.0-only
config SPACEMIT_CCU
tristate "Clock support for SpacemiT SoCs"
depends on ARCH_SPACEMIT || COMPILE_TEST
select MFD_SYSCON
help
Say Y to enable clock controller unit support for SpacemiT SoCs.
if SPACEMIT_CCU
config SPACEMIT_K1_CCU
tristate "Support for SpacemiT K1 SoC"
depends on ARCH_SPACEMIT || COMPILE_TEST
help
Support for clock controller unit in SpacemiT K1 SoC.
endif
+5
View File
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_SPACEMIT_K1_CCU) = spacemit-ccu-k1.o
spacemit-ccu-k1-y = ccu_pll.o ccu_mix.o ccu_ddn.o
spacemit-ccu-k1-y += ccu-k1.o
File diff suppressed because it is too large Load Diff
+48
View File
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
*/
#ifndef _CCU_COMMON_H_
#define _CCU_COMMON_H_
#include <linux/regmap.h>
struct ccu_common {
struct regmap *regmap;
struct regmap *lock_regmap;
union {
/* For DDN and MIX */
struct {
u32 reg_ctrl;
u32 reg_fc;
u32 mask_fc;
};
/* For PLL */
struct {
u32 reg_swcr1;
u32 reg_swcr3;
};
};
struct clk_hw hw;
};
static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw)
{
return container_of(hw, struct ccu_common, hw);
}
#define ccu_read(c, reg) \
({ \
u32 tmp; \
regmap_read((c)->regmap, (c)->reg_##reg, &tmp); \
tmp; \
})
#define ccu_update(c, reg, mask, val) \
regmap_update_bits((c)->regmap, (c)->reg_##reg, mask, val)
#endif /* _CCU_COMMON_H_ */
+83
View File
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
*
* DDN stands for "Divider Denominator Numerator", it's M/N clock with a
* constant x2 factor. This clock hardware follows the equation below,
*
* numerator Fin
* 2 * ------------- = -------
* denominator Fout
*
* Thus, Fout could be calculated with,
*
* Fin denominator
* Fout = ----- * -------------
* 2 numerator
*/
#include <linux/clk-provider.h>
#include <linux/rational.h>
#include "ccu_ddn.h"
static unsigned long ccu_ddn_calc_rate(unsigned long prate,
unsigned long num, unsigned long den)
{
return prate * den / 2 / num;
}
static unsigned long ccu_ddn_calc_best_rate(struct ccu_ddn *ddn,
unsigned long rate, unsigned long prate,
unsigned long *num, unsigned long *den)
{
rational_best_approximation(rate, prate / 2,
ddn->den_mask >> ddn->den_shift,
ddn->num_mask >> ddn->num_shift,
den, num);
return ccu_ddn_calc_rate(prate, *num, *den);
}
static long ccu_ddn_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
unsigned long num, den;
return ccu_ddn_calc_best_rate(ddn, rate, *prate, &num, &den);
}
static unsigned long ccu_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate)
{
struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
unsigned int val, num, den;
val = ccu_read(&ddn->common, ctrl);
num = (val & ddn->num_mask) >> ddn->num_shift;
den = (val & ddn->den_mask) >> ddn->den_shift;
return ccu_ddn_calc_rate(prate, num, den);
}
static int ccu_ddn_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long prate)
{
struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
unsigned long num, den;
ccu_ddn_calc_best_rate(ddn, rate, prate, &num, &den);
ccu_update(&ddn->common, ctrl,
ddn->num_mask | ddn->den_mask,
(num << ddn->num_shift) | (den << ddn->den_shift));
return 0;
}
const struct clk_ops spacemit_ccu_ddn_ops = {
.recalc_rate = ccu_ddn_recalc_rate,
.round_rate = ccu_ddn_round_rate,
.set_rate = ccu_ddn_set_rate,
};
+48
View File
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
*/
#ifndef _CCU_DDN_H_
#define _CCU_DDN_H_
#include <linux/bitops.h>
#include <linux/clk-provider.h>
#include "ccu_common.h"
struct ccu_ddn {
struct ccu_common common;
unsigned int num_mask;
unsigned int num_shift;
unsigned int den_mask;
unsigned int den_shift;
};
#define CCU_DDN_INIT(_name, _parent, _flags) \
CLK_HW_INIT_HW(#_name, &_parent.common.hw, &spacemit_ccu_ddn_ops, _flags)
#define CCU_DDN_DEFINE(_name, _parent, _reg_ctrl, _num_shift, _num_width, \
_den_shift, _den_width, _flags) \
static struct ccu_ddn _name = { \
.common = { \
.reg_ctrl = _reg_ctrl, \
.hw.init = CCU_DDN_INIT(_name, _parent, _flags), \
}, \
.num_mask = GENMASK(_num_shift + _num_width - 1, _num_shift), \
.num_shift = _num_shift, \
.den_mask = GENMASK(_den_shift + _den_width - 1, _den_shift), \
.den_shift = _den_shift, \
}
static inline struct ccu_ddn *hw_to_ccu_ddn(struct clk_hw *hw)
{
struct ccu_common *common = hw_to_ccu_common(hw);
return container_of(common, struct ccu_ddn, common);
}
extern const struct clk_ops spacemit_ccu_ddn_ops;
#endif
+268
View File
@@ -0,0 +1,268 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
*
* MIX clock type is the combination of mux, factor or divider, and gate
*/
#include <linux/clk-provider.h>
#include "ccu_mix.h"
#define MIX_FC_TIMEOUT_US 10000
#define MIX_FC_DELAY_US 5
static void ccu_gate_disable(struct clk_hw *hw)
{
struct ccu_mix *mix = hw_to_ccu_mix(hw);
ccu_update(&mix->common, ctrl, mix->gate.mask, 0);
}
static int ccu_gate_enable(struct clk_hw *hw)
{
struct ccu_mix *mix = hw_to_ccu_mix(hw);
struct ccu_gate_config *gate = &mix->gate;
ccu_update(&mix->common, ctrl, gate->mask, gate->mask);
return 0;
}
static int ccu_gate_is_enabled(struct clk_hw *hw)
{
struct ccu_mix *mix = hw_to_ccu_mix(hw);
struct ccu_gate_config *gate = &mix->gate;
return (ccu_read(&mix->common, ctrl) & gate->mask) == gate->mask;
}
static unsigned long ccu_factor_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct ccu_mix *mix = hw_to_ccu_mix(hw);
return parent_rate * mix->factor.mul / mix->factor.div;
}
static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct ccu_mix *mix = hw_to_ccu_mix(hw);
struct ccu_div_config *div = &mix->div;
unsigned long val;
val = ccu_read(&mix->common, ctrl) >> div->shift;
val &= (1 << div->width) - 1;
return divider_recalc_rate(hw, parent_rate, val, NULL, 0, div->width);
}
/*
* Some clocks require a "FC" (frequency change) bit to be set after changing
* their rates or reparenting. This bit will be automatically cleared by
* hardware in MIX_FC_TIMEOUT_US, which indicates the operation is completed.
*/
static int ccu_mix_trigger_fc(struct clk_hw *hw)
{
struct ccu_common *common = hw_to_ccu_common(hw);
unsigned int val;
if (common->reg_fc)
return 0;
ccu_update(common, fc, common->mask_fc, common->mask_fc);
return regmap_read_poll_timeout_atomic(common->regmap, common->reg_fc,
val, !(val & common->mask_fc),
MIX_FC_DELAY_US,
MIX_FC_TIMEOUT_US);
}
static long ccu_factor_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
return ccu_factor_recalc_rate(hw, *prate);
}
static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
return 0;
}
static unsigned long
ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate,
struct clk_hw **best_parent,
unsigned long *best_parent_rate,
u32 *div_val)
{
struct ccu_mix *mix = hw_to_ccu_mix(hw);
unsigned int parent_num = clk_hw_get_num_parents(hw);
struct ccu_div_config *div = &mix->div;
u32 div_max = 1 << div->width;
unsigned long best_rate = 0;
for (int i = 0; i < parent_num; i++) {
struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
unsigned long parent_rate;
if (!parent)
continue;
parent_rate = clk_hw_get_rate(parent);
for (int j = 1; j <= div_max; j++) {
unsigned long tmp = DIV_ROUND_CLOSEST_ULL(parent_rate, j);
if (abs(tmp - rate) < abs(best_rate - rate)) {
best_rate = tmp;
if (div_val)
*div_val = j - 1;
if (best_parent) {
*best_parent = parent;
*best_parent_rate = parent_rate;
}
}
}
}
return best_rate;
}
static int ccu_mix_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
req->rate = ccu_mix_calc_best_rate(hw, req->rate,
&req->best_parent_hw,
&req->best_parent_rate,
NULL);
return 0;
}
static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct ccu_mix *mix = hw_to_ccu_mix(hw);
struct ccu_common *common = &mix->common;
struct ccu_div_config *div = &mix->div;
u32 current_div, target_div, mask;
ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div);
current_div = ccu_read(common, ctrl) >> div->shift;
current_div &= (1 << div->width) - 1;
if (current_div == target_div)
return 0;
mask = GENMASK(div->width + div->shift - 1, div->shift);
ccu_update(common, ctrl, mask, target_div << div->shift);
return ccu_mix_trigger_fc(hw);
}
static u8 ccu_mux_get_parent(struct clk_hw *hw)
{
struct ccu_mix *mix = hw_to_ccu_mix(hw);
struct ccu_mux_config *mux = &mix->mux;
u8 parent;
parent = ccu_read(&mix->common, ctrl) >> mux->shift;
parent &= (1 << mux->width) - 1;
return parent;
}
static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct ccu_mix *mix = hw_to_ccu_mix(hw);
struct ccu_mux_config *mux = &mix->mux;
u32 mask;
mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
ccu_update(&mix->common, ctrl, mask, index << mux->shift);
return ccu_mix_trigger_fc(hw);
}
const struct clk_ops spacemit_ccu_gate_ops = {
.disable = ccu_gate_disable,
.enable = ccu_gate_enable,
.is_enabled = ccu_gate_is_enabled,
};
const struct clk_ops spacemit_ccu_factor_ops = {
.round_rate = ccu_factor_round_rate,
.recalc_rate = ccu_factor_recalc_rate,
.set_rate = ccu_factor_set_rate,
};
const struct clk_ops spacemit_ccu_mux_ops = {
.determine_rate = ccu_mix_determine_rate,
.get_parent = ccu_mux_get_parent,
.set_parent = ccu_mux_set_parent,
};
const struct clk_ops spacemit_ccu_div_ops = {
.determine_rate = ccu_mix_determine_rate,
.recalc_rate = ccu_div_recalc_rate,
.set_rate = ccu_mix_set_rate,
};
const struct clk_ops spacemit_ccu_factor_gate_ops = {
.disable = ccu_gate_disable,
.enable = ccu_gate_enable,
.is_enabled = ccu_gate_is_enabled,
.round_rate = ccu_factor_round_rate,
.recalc_rate = ccu_factor_recalc_rate,
.set_rate = ccu_factor_set_rate,
};
const struct clk_ops spacemit_ccu_mux_gate_ops = {
.disable = ccu_gate_disable,
.enable = ccu_gate_enable,
.is_enabled = ccu_gate_is_enabled,
.determine_rate = ccu_mix_determine_rate,
.get_parent = ccu_mux_get_parent,
.set_parent = ccu_mux_set_parent,
};
const struct clk_ops spacemit_ccu_div_gate_ops = {
.disable = ccu_gate_disable,
.enable = ccu_gate_enable,
.is_enabled = ccu_gate_is_enabled,
.determine_rate = ccu_mix_determine_rate,
.recalc_rate = ccu_div_recalc_rate,
.set_rate = ccu_mix_set_rate,
};
const struct clk_ops spacemit_ccu_mux_div_gate_ops = {
.disable = ccu_gate_disable,
.enable = ccu_gate_enable,
.is_enabled = ccu_gate_is_enabled,
.get_parent = ccu_mux_get_parent,
.set_parent = ccu_mux_set_parent,
.determine_rate = ccu_mix_determine_rate,
.recalc_rate = ccu_div_recalc_rate,
.set_rate = ccu_mix_set_rate,
};
const struct clk_ops spacemit_ccu_mux_div_ops = {
.get_parent = ccu_mux_get_parent,
.set_parent = ccu_mux_set_parent,
.determine_rate = ccu_mix_determine_rate,
.recalc_rate = ccu_div_recalc_rate,
.set_rate = ccu_mix_set_rate,
};
+218
View File
@@ -0,0 +1,218 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
*/
#ifndef _CCU_MIX_H_
#define _CCU_MIX_H_
#include <linux/clk-provider.h>
#include "ccu_common.h"
/**
* struct ccu_gate_config - Gate configuration
*
* @mask: Mask to enable the gate. Some clocks may have more than one bit
* set in this field.
*/
struct ccu_gate_config {
u32 mask;
};
struct ccu_factor_config {
u32 div;
u32 mul;
};
struct ccu_mux_config {
u8 shift;
u8 width;
};
struct ccu_div_config {
u8 shift;
u8 width;
};
struct ccu_mix {
struct ccu_factor_config factor;
struct ccu_gate_config gate;
struct ccu_div_config div;
struct ccu_mux_config mux;
struct ccu_common common;
};
#define CCU_GATE_INIT(_mask) { .mask = _mask }
#define CCU_FACTOR_INIT(_div, _mul) { .div = _div, .mul = _mul }
#define CCU_MUX_INIT(_shift, _width) { .shift = _shift, .width = _width }
#define CCU_DIV_INIT(_shift, _width) { .shift = _shift, .width = _width }
#define CCU_PARENT_HW(_parent) { .hw = &_parent.common.hw }
#define CCU_PARENT_NAME(_name) { .fw_name = #_name }
#define CCU_MIX_INITHW(_name, _parent, _ops, _flags) \
.hw.init = &(struct clk_init_data) { \
.flags = _flags, \
.name = #_name, \
.parent_data = (const struct clk_parent_data[]) \
{ _parent }, \
.num_parents = 1, \
.ops = &_ops, \
}
#define CCU_MIX_INITHW_PARENTS(_name, _parents, _ops, _flags) \
.hw.init = CLK_HW_INIT_PARENTS_DATA(#_name, _parents, &_ops, _flags)
#define CCU_GATE_DEFINE(_name, _parent, _reg_ctrl, _mask_gate, _flags) \
static struct ccu_mix _name = { \
.gate = CCU_GATE_INIT(_mask_gate), \
.common = { \
.reg_ctrl = _reg_ctrl, \
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_gate_ops, _flags), \
} \
}
#define CCU_FACTOR_DEFINE(_name, _parent, _div, _mul) \
static struct ccu_mix _name = { \
.factor = CCU_FACTOR_INIT(_div, _mul), \
.common = { \
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_factor_ops, 0), \
} \
}
#define CCU_MUX_DEFINE(_name, _parents, _reg_ctrl, _shift, _width, _flags) \
static struct ccu_mix _name = { \
.mux = CCU_MUX_INIT(_shift, _width), \
.common = { \
.reg_ctrl = _reg_ctrl, \
CCU_MIX_INITHW_PARENTS(_name, _parents, spacemit_ccu_mux_ops, \
_flags), \
} \
}
#define CCU_DIV_DEFINE(_name, _parent, _reg_ctrl, _shift, _width, _flags) \
static struct ccu_mix _name = { \
.div = CCU_DIV_INIT(_shift, _width), \
.common = { \
.reg_ctrl = _reg_ctrl, \
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_div_ops, _flags) \
} \
}
#define CCU_FACTOR_GATE_DEFINE(_name, _parent, _reg_ctrl, _mask_gate, _div, \
_mul) \
static struct ccu_mix _name = { \
.gate = CCU_GATE_INIT(_mask_gate), \
.factor = CCU_FACTOR_INIT(_div, _mul), \
.common = { \
.reg_ctrl = _reg_ctrl, \
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_factor_gate_ops, 0) \
} \
}
#define CCU_MUX_GATE_DEFINE(_name, _parents, _reg_ctrl, _shift, _width, \
_mask_gate, _flags) \
static struct ccu_mix _name = { \
.gate = CCU_GATE_INIT(_mask_gate), \
.mux = CCU_MUX_INIT(_shift, _width), \
.common = { \
.reg_ctrl = _reg_ctrl, \
CCU_MIX_INITHW_PARENTS(_name, _parents, \
spacemit_ccu_mux_gate_ops, _flags), \
} \
}
#define CCU_DIV_GATE_DEFINE(_name, _parent, _reg_ctrl, _shift, _width, \
_mask_gate, _flags) \
static struct ccu_mix _name = { \
.gate = CCU_GATE_INIT(_mask_gate), \
.div = CCU_DIV_INIT(_shift, _width), \
.common = { \
.reg_ctrl = _reg_ctrl, \
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_div_gate_ops, \
_flags), \
} \
}
#define CCU_MUX_DIV_GATE_DEFINE(_name, _parents, _reg_ctrl, _mshift, _mwidth, \
_muxshift, _muxwidth, _mask_gate, _flags) \
static struct ccu_mix _name = { \
.gate = CCU_GATE_INIT(_mask_gate), \
.div = CCU_DIV_INIT(_mshift, _mwidth), \
.mux = CCU_MUX_INIT(_muxshift, _muxwidth), \
.common = { \
.reg_ctrl = _reg_ctrl, \
CCU_MIX_INITHW_PARENTS(_name, _parents, \
spacemit_ccu_mux_div_gate_ops, _flags), \
}, \
}
#define CCU_MUX_DIV_GATE_SPLIT_FC_DEFINE(_name, _parents, _reg_ctrl, _reg_fc, \
_mshift, _mwidth, _mask_fc, _muxshift, \
_muxwidth, _mask_gate, _flags) \
static struct ccu_mix _name = { \
.gate = CCU_GATE_INIT(_mask_gate), \
.div = CCU_DIV_INIT(_mshift, _mwidth), \
.mux = CCU_MUX_INIT(_muxshift, _muxwidth), \
.common = { \
.reg_ctrl = _reg_ctrl, \
.reg_fc = _reg_fc, \
.mask_fc = _mask_fc, \
CCU_MIX_INITHW_PARENTS(_name, _parents, \
spacemit_ccu_mux_div_gate_ops, _flags), \
}, \
}
#define CCU_MUX_DIV_GATE_FC_DEFINE(_name, _parents, _reg_ctrl, _mshift, _mwidth,\
_mask_fc, _muxshift, _muxwidth, _mask_gate, \
_flags) \
CCU_MUX_DIV_GATE_SPLIT_FC_DEFINE(_name, _parents, _reg_ctrl, _reg_ctrl, _mshift,\
_mwidth, _mask_fc, _muxshift, _muxwidth, \
_mask_gate, _flags)
#define CCU_MUX_DIV_FC_DEFINE(_name, _parents, _reg_ctrl, _mshift, _mwidth, \
_mask_fc, _muxshift, _muxwidth, _flags) \
static struct ccu_mix _name = { \
.div = CCU_DIV_INIT(_mshift, _mwidth), \
.mux = CCU_MUX_INIT(_muxshift, _muxwidth), \
.common = { \
.reg_ctrl = _reg_ctrl, \
.reg_fc = _reg_ctrl, \
.mask_fc = _mask_fc, \
CCU_MIX_INITHW_PARENTS(_name, _parents, \
spacemit_ccu_mux_div_ops, _flags), \
}, \
}
#define CCU_MUX_FC_DEFINE(_name, _parents, _reg_ctrl, _mask_fc, _muxshift, \
_muxwidth, _flags) \
static struct ccu_mix _name = { \
.mux = CCU_MUX_INIT(_muxshift, _muxwidth), \
.common = { \
.reg_ctrl = _reg_ctrl, \
.reg_fc = _reg_ctrl, \
.mask_fc = _mask_fc, \
CCU_MIX_INITHW_PARENTS(_name, _parents, spacemit_ccu_mux_ops, \
_flags) \
}, \
}
static inline struct ccu_mix *hw_to_ccu_mix(struct clk_hw *hw)
{
struct ccu_common *common = hw_to_ccu_common(hw);
return container_of(common, struct ccu_mix, common);
}
extern const struct clk_ops spacemit_ccu_gate_ops;
extern const struct clk_ops spacemit_ccu_factor_ops;
extern const struct clk_ops spacemit_ccu_mux_ops;
extern const struct clk_ops spacemit_ccu_div_ops;
extern const struct clk_ops spacemit_ccu_factor_gate_ops;
extern const struct clk_ops spacemit_ccu_div_gate_ops;
extern const struct clk_ops spacemit_ccu_mux_gate_ops;
extern const struct clk_ops spacemit_ccu_mux_div_ops;
extern const struct clk_ops spacemit_ccu_mux_div_gate_ops;
#endif /* _CCU_DIV_H_ */
+157
View File
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
*/
#include <linux/clk-provider.h>
#include <linux/math.h>
#include <linux/regmap.h>
#include "ccu_common.h"
#include "ccu_pll.h"
#define PLL_TIMEOUT_US 3000
#define PLL_DELAY_US 5
#define PLL_SWCR3_EN ((u32)BIT(31))
#define PLL_SWCR3_MASK GENMASK(30, 0)
static const struct ccu_pll_rate_tbl *ccu_pll_lookup_best_rate(struct ccu_pll *pll,
unsigned long rate)
{
struct ccu_pll_config *config = &pll->config;
const struct ccu_pll_rate_tbl *best_entry;
unsigned long best_delta = ULONG_MAX;
int i;
for (i = 0; i < config->tbl_num; i++) {
const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
unsigned long delta = abs_diff(entry->rate, rate);
if (delta < best_delta) {
best_delta = delta;
best_entry = entry;
}
}
return best_entry;
}
static const struct ccu_pll_rate_tbl *ccu_pll_lookup_matched_entry(struct ccu_pll *pll)
{
struct ccu_pll_config *config = &pll->config;
u32 swcr1, swcr3;
int i;
swcr1 = ccu_read(&pll->common, swcr1);
swcr3 = ccu_read(&pll->common, swcr3);
swcr3 &= PLL_SWCR3_MASK;
for (i = 0; i < config->tbl_num; i++) {
const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
if (swcr1 == entry->swcr1 && swcr3 == entry->swcr3)
return entry;
}
return NULL;
}
static void ccu_pll_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry)
{
struct ccu_common *common = &pll->common;
regmap_write(common->regmap, common->reg_swcr1, entry->swcr1);
ccu_update(common, swcr3, PLL_SWCR3_MASK, entry->swcr3);
}
static int ccu_pll_is_enabled(struct clk_hw *hw)
{
struct ccu_common *common = hw_to_ccu_common(hw);
return ccu_read(common, swcr3) & PLL_SWCR3_EN;
}
static int ccu_pll_enable(struct clk_hw *hw)
{
struct ccu_pll *pll = hw_to_ccu_pll(hw);
struct ccu_common *common = &pll->common;
unsigned int tmp;
ccu_update(common, swcr3, PLL_SWCR3_EN, PLL_SWCR3_EN);
/* check lock status */
return regmap_read_poll_timeout_atomic(common->lock_regmap,
pll->config.reg_lock,
tmp,
tmp & pll->config.mask_lock,
PLL_DELAY_US, PLL_TIMEOUT_US);
}
static void ccu_pll_disable(struct clk_hw *hw)
{
struct ccu_common *common = hw_to_ccu_common(hw);
ccu_update(common, swcr3, PLL_SWCR3_EN, 0);
}
/*
* PLLs must be gated before changing rate, which is ensured by
* flag CLK_SET_RATE_GATE.
*/
static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct ccu_pll *pll = hw_to_ccu_pll(hw);
const struct ccu_pll_rate_tbl *entry;
entry = ccu_pll_lookup_best_rate(pll, rate);
ccu_pll_update_param(pll, entry);
return 0;
}
static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct ccu_pll *pll = hw_to_ccu_pll(hw);
const struct ccu_pll_rate_tbl *entry;
entry = ccu_pll_lookup_matched_entry(pll);
WARN_ON_ONCE(!entry);
return entry ? entry->rate : -EINVAL;
}
static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct ccu_pll *pll = hw_to_ccu_pll(hw);
return ccu_pll_lookup_best_rate(pll, rate)->rate;
}
static int ccu_pll_init(struct clk_hw *hw)
{
struct ccu_pll *pll = hw_to_ccu_pll(hw);
if (ccu_pll_lookup_matched_entry(pll))
return 0;
ccu_pll_disable(hw);
ccu_pll_update_param(pll, &pll->config.rate_tbl[0]);
return 0;
}
const struct clk_ops spacemit_ccu_pll_ops = {
.init = ccu_pll_init,
.enable = ccu_pll_enable,
.disable = ccu_pll_disable,
.set_rate = ccu_pll_set_rate,
.recalc_rate = ccu_pll_recalc_rate,
.round_rate = ccu_pll_round_rate,
.is_enabled = ccu_pll_is_enabled,
};
+86
View File
@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
*/
#ifndef _CCU_PLL_H_
#define _CCU_PLL_H_
#include <linux/clk-provider.h>
#include "ccu_common.h"
/**
* struct ccu_pll_rate_tbl - Structure mapping between PLL rate and register
* configuration.
*
* @rate: PLL rate
* @swcr1: Register value of PLLX_SW1_CTRL (PLLx_SWCR1).
* @swcr3: Register value of the PLLx_SW3_CTRL's lowest 31 bits of
* PLLx_SW3_CTRL (PLLx_SWCR3). This highest bit is for enabling
* the PLL and not contained in this field.
*/
struct ccu_pll_rate_tbl {
unsigned long rate;
u32 swcr1;
u32 swcr3;
};
struct ccu_pll_config {
const struct ccu_pll_rate_tbl *rate_tbl;
u32 tbl_num;
u32 reg_lock;
u32 mask_lock;
};
#define CCU_PLL_RATE(_rate, _swcr1, _swcr3) \
{ \
.rate = _rate, \
.swcr1 = _swcr1, \
.swcr3 = _swcr3, \
}
struct ccu_pll {
struct ccu_common common;
struct ccu_pll_config config;
};
#define CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock) \
{ \
.rate_tbl = _table, \
.tbl_num = ARRAY_SIZE(_table), \
.reg_lock = (_reg_lock), \
.mask_lock = (_mask_lock), \
}
#define CCU_PLL_HWINIT(_name, _flags) \
(&(struct clk_init_data) { \
.name = #_name, \
.ops = &spacemit_ccu_pll_ops, \
.parent_data = &(struct clk_parent_data) { .index = 0 }, \
.num_parents = 1, \
.flags = _flags, \
})
#define CCU_PLL_DEFINE(_name, _table, _reg_swcr1, _reg_swcr3, _reg_lock, \
_mask_lock, _flags) \
static struct ccu_pll _name = { \
.config = CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock), \
.common = { \
.reg_swcr1 = _reg_swcr1, \
.reg_swcr3 = _reg_swcr3, \
.hw.init = CCU_PLL_HWINIT(_name, _flags) \
} \
}
static inline struct ccu_pll *hw_to_ccu_pll(struct clk_hw *hw)
{
struct ccu_common *common = hw_to_ccu_common(hw);
return container_of(common, struct ccu_pll, common);
}
extern const struct clk_ops spacemit_ccu_pll_ops;
#endif
@@ -0,0 +1,247 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
/*
* Copyright (C) 2024-2025 Haylen Chu <heylenay@outlook.com>
*/
#ifndef _DT_BINDINGS_SPACEMIT_CCU_H_
#define _DT_BINDINGS_SPACEMIT_CCU_H_
/* APBS (PLL) clocks */
#define CLK_PLL1 0
#define CLK_PLL2 1
#define CLK_PLL3 2
#define CLK_PLL1_D2 3
#define CLK_PLL1_D3 4
#define CLK_PLL1_D4 5
#define CLK_PLL1_D5 6
#define CLK_PLL1_D6 7
#define CLK_PLL1_D7 8
#define CLK_PLL1_D8 9
#define CLK_PLL1_D11 10
#define CLK_PLL1_D13 11
#define CLK_PLL1_D23 12
#define CLK_PLL1_D64 13
#define CLK_PLL1_D10_AUD 14
#define CLK_PLL1_D100_AUD 15
#define CLK_PLL2_D1 16
#define CLK_PLL2_D2 17
#define CLK_PLL2_D3 18
#define CLK_PLL2_D4 19
#define CLK_PLL2_D5 20
#define CLK_PLL2_D6 21
#define CLK_PLL2_D7 22
#define CLK_PLL2_D8 23
#define CLK_PLL3_D1 24
#define CLK_PLL3_D2 25
#define CLK_PLL3_D3 26
#define CLK_PLL3_D4 27
#define CLK_PLL3_D5 28
#define CLK_PLL3_D6 29
#define CLK_PLL3_D7 30
#define CLK_PLL3_D8 31
#define CLK_PLL3_80 32
#define CLK_PLL3_40 33
#define CLK_PLL3_20 34
/* MPMU clocks */
#define CLK_PLL1_307P2 0
#define CLK_PLL1_76P8 1
#define CLK_PLL1_61P44 2
#define CLK_PLL1_153P6 3
#define CLK_PLL1_102P4 4
#define CLK_PLL1_51P2 5
#define CLK_PLL1_51P2_AP 6
#define CLK_PLL1_57P6 7
#define CLK_PLL1_25P6 8
#define CLK_PLL1_12P8 9
#define CLK_PLL1_12P8_WDT 10
#define CLK_PLL1_6P4 11
#define CLK_PLL1_3P2 12
#define CLK_PLL1_1P6 13
#define CLK_PLL1_0P8 14
#define CLK_PLL1_409P6 15
#define CLK_PLL1_204P8 16
#define CLK_PLL1_491 17
#define CLK_PLL1_245P76 18
#define CLK_PLL1_614 19
#define CLK_PLL1_47P26 20
#define CLK_PLL1_31P5 21
#define CLK_PLL1_819 22
#define CLK_PLL1_1228 23
#define CLK_SLOW_UART 24
#define CLK_SLOW_UART1 25
#define CLK_SLOW_UART2 26
#define CLK_WDT 27
#define CLK_RIPC 28
#define CLK_I2S_SYSCLK 29
#define CLK_I2S_BCLK 30
#define CLK_APB 31
#define CLK_WDT_BUS 32
/* APBC clocks */
#define CLK_UART0 0
#define CLK_UART2 1
#define CLK_UART3 2
#define CLK_UART4 3
#define CLK_UART5 4
#define CLK_UART6 5
#define CLK_UART7 6
#define CLK_UART8 7
#define CLK_UART9 8
#define CLK_GPIO 9
#define CLK_PWM0 10
#define CLK_PWM1 11
#define CLK_PWM2 12
#define CLK_PWM3 13
#define CLK_PWM4 14
#define CLK_PWM5 15
#define CLK_PWM6 16
#define CLK_PWM7 17
#define CLK_PWM8 18
#define CLK_PWM9 19
#define CLK_PWM10 20
#define CLK_PWM11 21
#define CLK_PWM12 22
#define CLK_PWM13 23
#define CLK_PWM14 24
#define CLK_PWM15 25
#define CLK_PWM16 26
#define CLK_PWM17 27
#define CLK_PWM18 28
#define CLK_PWM19 29
#define CLK_SSP3 30
#define CLK_RTC 31
#define CLK_TWSI0 32
#define CLK_TWSI1 33
#define CLK_TWSI2 34
#define CLK_TWSI4 35
#define CLK_TWSI5 36
#define CLK_TWSI6 37
#define CLK_TWSI7 38
#define CLK_TWSI8 39
#define CLK_TIMERS1 40
#define CLK_TIMERS2 41
#define CLK_AIB 42
#define CLK_ONEWIRE 43
#define CLK_SSPA0 44
#define CLK_SSPA1 45
#define CLK_DRO 46
#define CLK_IR 47
#define CLK_TSEN 48
#define CLK_IPC_AP2AUD 49
#define CLK_CAN0 50
#define CLK_CAN0_BUS 51
#define CLK_UART0_BUS 52
#define CLK_UART2_BUS 53
#define CLK_UART3_BUS 54
#define CLK_UART4_BUS 55
#define CLK_UART5_BUS 56
#define CLK_UART6_BUS 57
#define CLK_UART7_BUS 58
#define CLK_UART8_BUS 59
#define CLK_UART9_BUS 60
#define CLK_GPIO_BUS 61
#define CLK_PWM0_BUS 62
#define CLK_PWM1_BUS 63
#define CLK_PWM2_BUS 64
#define CLK_PWM3_BUS 65
#define CLK_PWM4_BUS 66
#define CLK_PWM5_BUS 67
#define CLK_PWM6_BUS 68
#define CLK_PWM7_BUS 69
#define CLK_PWM8_BUS 70
#define CLK_PWM9_BUS 71
#define CLK_PWM10_BUS 72
#define CLK_PWM11_BUS 73
#define CLK_PWM12_BUS 74
#define CLK_PWM13_BUS 75
#define CLK_PWM14_BUS 76
#define CLK_PWM15_BUS 77
#define CLK_PWM16_BUS 78
#define CLK_PWM17_BUS 79
#define CLK_PWM18_BUS 80
#define CLK_PWM19_BUS 81
#define CLK_SSP3_BUS 82
#define CLK_RTC_BUS 83
#define CLK_TWSI0_BUS 84
#define CLK_TWSI1_BUS 85
#define CLK_TWSI2_BUS 86
#define CLK_TWSI4_BUS 87
#define CLK_TWSI5_BUS 88
#define CLK_TWSI6_BUS 89
#define CLK_TWSI7_BUS 90
#define CLK_TWSI8_BUS 91
#define CLK_TIMERS1_BUS 92
#define CLK_TIMERS2_BUS 93
#define CLK_AIB_BUS 94
#define CLK_ONEWIRE_BUS 95
#define CLK_SSPA0_BUS 96
#define CLK_SSPA1_BUS 97
#define CLK_TSEN_BUS 98
#define CLK_IPC_AP2AUD_BUS 99
/* APMU clocks */
#define CLK_CCI550 0
#define CLK_CPU_C0_HI 1
#define CLK_CPU_C0_CORE 2
#define CLK_CPU_C0_ACE 3
#define CLK_CPU_C0_TCM 4
#define CLK_CPU_C1_HI 5
#define CLK_CPU_C1_CORE 6
#define CLK_CPU_C1_ACE 7
#define CLK_CCIC_4X 8
#define CLK_CCIC1PHY 9
#define CLK_SDH_AXI 10
#define CLK_SDH0 11
#define CLK_SDH1 12
#define CLK_SDH2 13
#define CLK_USB_P1 14
#define CLK_USB_AXI 15
#define CLK_USB30 16
#define CLK_QSPI 17
#define CLK_QSPI_BUS 18
#define CLK_DMA 19
#define CLK_AES 20
#define CLK_VPU 21
#define CLK_GPU 22
#define CLK_EMMC 23
#define CLK_EMMC_X 24
#define CLK_AUDIO 25
#define CLK_HDMI 26
#define CLK_PMUA_ACLK 27
#define CLK_PCIE0_MASTER 28
#define CLK_PCIE0_SLAVE 29
#define CLK_PCIE0_DBI 30
#define CLK_PCIE1_MASTER 31
#define CLK_PCIE1_SLAVE 32
#define CLK_PCIE1_DBI 33
#define CLK_PCIE2_MASTER 34
#define CLK_PCIE2_SLAVE 35
#define CLK_PCIE2_DBI 36
#define CLK_EMAC0_BUS 37
#define CLK_EMAC0_PTP 38
#define CLK_EMAC1_BUS 39
#define CLK_EMAC1_PTP 40
#define CLK_JPG 41
#define CLK_CCIC2PHY 42
#define CLK_CCIC3PHY 43
#define CLK_CSI 44
#define CLK_CAMM0 45
#define CLK_CAMM1 46
#define CLK_CAMM2 47
#define CLK_ISP_CPP 48
#define CLK_ISP_BUS 49
#define CLK_ISP 50
#define CLK_DPU_MCLK 51
#define CLK_DPU_ESC 52
#define CLK_DPU_BIT 53
#define CLK_DPU_PXCLK 54
#define CLK_DPU_HCLK 55
#define CLK_DPU_SPI 56
#define CLK_DPU_SPI_HBUS 57
#define CLK_DPU_SPIBUS 58
#define CLK_DPU_SPI_ACLK 59
#define CLK_V2D 60
#define CLK_EMMC_BUS 61
#endif /* _DT_BINDINGS_SPACEMIT_CCU_H_ */