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
+23
View File
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Clock support for SpacemiT platforms"
depends on ARCH_SPACEMIT || COMPILE_TEST
config SPACEMIT_CCU
tristate
select AUXILIARY_BUS
select MFD_SYSCON
config SPACEMIT_K1_CCU
tristate "Support for SpacemiT K1 SoC"
select SPACEMIT_CCU
help
Support for clock controller unit in SpacemiT K1 SoC.
config SPACEMIT_K3_CCU
tristate "Support for SpacemiT K3 SoC"
select SPACEMIT_CCU
help
Support for clock controller unit in SpacemiT K3 SoC.
endmenu
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_SPACEMIT_CCU) += spacemit-ccu.o
spacemit-ccu-y += ccu_common.o
spacemit-ccu-y += ccu_pll.o
spacemit-ccu-y += ccu_mix.o
spacemit-ccu-y += ccu_ddn.o
obj-$(CONFIG_SPACEMIT_K1_CCU) += spacemit-ccu-k1.o
spacemit-ccu-k1-y += ccu-k1.o
obj-$(CONFIG_SPACEMIT_K3_CCU) += spacemit-ccu-k3.o
spacemit-ccu-k3-y += ccu-k3.o
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,177 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/clk-provider.h>
#include <linux/device/devres.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <soc/spacemit/ccu.h>
#include "ccu_common.h"
static DEFINE_IDA(auxiliary_ids);
static int spacemit_ccu_register(struct device *dev,
struct regmap *regmap,
struct regmap *lock_regmap,
const struct spacemit_ccu_data *data)
{
struct clk_hw_onecell_data *clk_data;
int i, ret;
/* Nothing to do if the CCU does not implement any clocks */
if (!data->hws)
return 0;
clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, data->num),
GFP_KERNEL);
if (!clk_data)
return -ENOMEM;
clk_data->num = data->num;
for (i = 0; i < data->num; i++) {
struct clk_hw *hw = data->hws[i];
struct ccu_common *common;
const char *name;
if (!hw) {
clk_data->hws[i] = ERR_PTR(-ENOENT);
continue;
}
name = hw->init->name;
common = hw_to_ccu_common(hw);
common->regmap = regmap;
common->lock_regmap = lock_regmap;
ret = devm_clk_hw_register(dev, hw);
if (ret) {
dev_err(dev, "Cannot register clock %d - %s\n",
i, name);
return ret;
}
clk_data->hws[i] = hw;
}
ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
if (ret)
dev_err(dev, "failed to add clock hardware provider (%d)\n", ret);
return ret;
}
static void spacemit_cadev_release(struct device *dev)
{
struct auxiliary_device *adev = to_auxiliary_dev(dev);
ida_free(&auxiliary_ids, adev->id);
kfree(to_spacemit_ccu_adev(adev));
}
static void spacemit_adev_unregister(void *data)
{
struct auxiliary_device *adev = data;
auxiliary_device_delete(adev);
auxiliary_device_uninit(adev);
}
static int spacemit_ccu_reset_register(struct device *dev,
struct regmap *regmap,
const char *reset_name)
{
struct spacemit_ccu_adev *cadev;
struct auxiliary_device *adev;
int ret;
/* Nothing to do if the CCU does not implement a reset controller */
if (!reset_name)
return 0;
cadev = kzalloc_obj(*cadev);
if (!cadev)
return -ENOMEM;
cadev->regmap = regmap;
adev = &cadev->adev;
adev->name = reset_name;
adev->dev.parent = dev;
adev->dev.release = spacemit_cadev_release;
adev->dev.of_node = dev->of_node;
ret = ida_alloc(&auxiliary_ids, GFP_KERNEL);
if (ret < 0)
goto err_free_cadev;
adev->id = ret;
ret = auxiliary_device_init(adev);
if (ret)
goto err_free_aux_id;
ret = auxiliary_device_add(adev);
if (ret) {
auxiliary_device_uninit(adev);
return ret;
}
return devm_add_action_or_reset(dev, spacemit_adev_unregister, adev);
err_free_aux_id:
ida_free(&auxiliary_ids, adev->id);
err_free_cadev:
kfree(cadev);
return ret;
}
int spacemit_ccu_probe(struct platform_device *pdev, const char *compat)
{
struct regmap *base_regmap, *lock_regmap = NULL;
const struct spacemit_ccu_data *data;
struct device *dev = &pdev->dev;
int ret;
base_regmap = device_node_to_regmap(dev->of_node);
if (IS_ERR(base_regmap))
return dev_err_probe(dev, PTR_ERR(base_regmap),
"failed to get regmap\n");
/*
* The lock status of PLLs locate in MPMU region, while PLLs themselves
* are in APBS region. Reference to MPMU syscon is required to check PLL
* status.
*/
if (compat && of_device_is_compatible(dev->of_node, compat)) {
struct device_node *mpmu = of_parse_phandle(dev->of_node,
"spacemit,mpmu", 0);
if (!mpmu)
return dev_err_probe(dev, -ENODEV,
"Cannot parse MPMU region\n");
lock_regmap = device_node_to_regmap(mpmu);
of_node_put(mpmu);
if (IS_ERR(lock_regmap))
return dev_err_probe(dev, PTR_ERR(lock_regmap),
"failed to get lock regmap\n");
}
data = of_device_get_match_data(dev);
ret = spacemit_ccu_register(dev, base_regmap, lock_regmap, data);
if (ret)
return dev_err_probe(dev, ret, "failed to register clocks\n");
ret = spacemit_ccu_reset_register(dev, base_regmap, data->reset_name);
if (ret)
return dev_err_probe(dev, ret, "failed to register resets\n");
return 0;
}
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_probe, "CLK_SPACEMIT");
MODULE_DESCRIPTION("SpacemiT CCU common clock driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,59 @@
/* 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/clk-provider.h>
#include <linux/platform_device.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_swcr2;
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);
}
struct spacemit_ccu_data {
const char *reset_name;
struct clk_hw **hws;
size_t num;
};
#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)
int spacemit_ccu_probe(struct platform_device *pdev, const char *compat);
#endif /* _CCU_COMMON_H_ */
@@ -0,0 +1,87 @@
// 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, unsigned int pre_div)
{
return prate * den / pre_div / 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 / ddn->pre_div,
ddn->den_mask >> ddn->den_shift,
ddn->num_mask >> ddn->num_shift,
den, num);
return ccu_ddn_calc_rate(prate, *num, *den, ddn->pre_div);
}
static int ccu_ddn_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
unsigned long num, den;
req->rate = ccu_ddn_calc_best_rate(ddn, req->rate,
req->best_parent_rate, &num, &den);
return 0;
}
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, ddn->pre_div);
}
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,
.determine_rate = ccu_ddn_determine_rate,
.set_rate = ccu_ddn_set_rate,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_ddn_ops, "CLK_SPACEMIT");
@@ -0,0 +1,50 @@
/* 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;
unsigned int pre_div;
};
#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, _pre_div, _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, \
.pre_div = _pre_div, \
}
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
+283
View File
@@ -0,0 +1,283 @@
// 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);
struct ccu_gate_config *gate = &mix->gate;
u32 val = gate->inverted ? gate->mask : 0;
ccu_update(&mix->common, ctrl, gate->mask, val);
}
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;
u32 val = gate->inverted ? 0 : gate->mask;
ccu_update(&mix->common, ctrl, gate->mask, val);
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;
u32 tmp = ccu_read(&mix->common, ctrl) & gate->mask;
u32 val = gate->inverted ? 0 : gate->mask;
return !!(tmp == val);
}
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 int ccu_factor_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
req->rate = ccu_factor_recalc_rate(hw, req->best_parent_rate);
return 0;
}
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,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_gate_ops, "CLK_SPACEMIT");
const struct clk_ops spacemit_ccu_factor_ops = {
.determine_rate = ccu_factor_determine_rate,
.recalc_rate = ccu_factor_recalc_rate,
.set_rate = ccu_factor_set_rate,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_factor_ops, "CLK_SPACEMIT");
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,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_ops, "CLK_SPACEMIT");
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,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_div_ops, "CLK_SPACEMIT");
const struct clk_ops spacemit_ccu_factor_gate_ops = {
.disable = ccu_gate_disable,
.enable = ccu_gate_enable,
.is_enabled = ccu_gate_is_enabled,
.determine_rate = ccu_factor_determine_rate,
.recalc_rate = ccu_factor_recalc_rate,
.set_rate = ccu_factor_set_rate,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_factor_gate_ops, "CLK_SPACEMIT");
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,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_gate_ops, "CLK_SPACEMIT");
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,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_div_gate_ops, "CLK_SPACEMIT");
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,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_div_gate_ops, "CLK_SPACEMIT");
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,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_div_ops, "CLK_SPACEMIT");
+235
View File
@@ -0,0 +1,235 @@
/* 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.
* @inverted: Enable bit is inverted, 1 - disable clock, 0 - enable clock
*/
struct ccu_gate_config {
u32 mask;
bool inverted;
};
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_GATE_FLAGS_INIT(_mask, _inverted) { .mask = _mask, .inverted = _inverted }
#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_GATE_FLAGS_DEFINE(_name, _parent, _reg_ctrl, _mask_gate, _inverted, _flags) \
static struct ccu_mix _name = { \
.gate = CCU_GATE_FLAGS_INIT(_mask_gate, _inverted), \
.common = { \
.reg_ctrl = _reg_ctrl, \
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_gate_ops, _flags), \
} \
}
#define CCU_FACTOR_GATE_FLAGS_DEFINE(_name, _parent, _reg_ctrl, _mask_gate, _div, \
_mul, _flags) \
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, _flags) \
} \
}
#define CCU_FACTOR_GATE_DEFINE(_name, _parent, _reg_ctrl, _mask_gate, _div, \
_mul) \
CCU_FACTOR_GATE_FLAGS_DEFINE(_name, _parent, _reg_ctrl, _mask_gate, _div, \
_mul, 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_MIX_H_ */
+278
View File
@@ -0,0 +1,278 @@
// 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)
#define PLLA_SWCR2_EN ((u32)BIT(16))
#define PLLA_SWCR2_MASK GENMASK(15, 8)
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 : 0;
}
static int ccu_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct ccu_pll *pll = hw_to_ccu_pll(hw);
req->rate = ccu_pll_lookup_best_rate(pll, req->rate)->rate;
return 0;
}
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;
}
static const struct ccu_pll_rate_tbl *ccu_plla_lookup_matched_entry(struct ccu_pll *pll)
{
struct ccu_pll_config *config = &pll->config;
const struct ccu_pll_rate_tbl *entry;
u32 i, swcr1, swcr2, swcr3;
swcr1 = ccu_read(&pll->common, swcr1);
swcr2 = ccu_read(&pll->common, swcr2);
swcr2 &= PLLA_SWCR2_MASK;
swcr3 = ccu_read(&pll->common, swcr3);
for (i = 0; i < config->tbl_num; i++) {
entry = &config->rate_tbl[i];
if (swcr1 == entry->swcr1 &&
swcr2 == entry->swcr2 &&
swcr3 == entry->swcr3)
return entry;
}
return NULL;
}
static void ccu_plla_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);
regmap_write(common->regmap, common->reg_swcr3, entry->swcr3);
ccu_update(common, swcr2, PLLA_SWCR2_MASK, entry->swcr2);
}
static int ccu_plla_is_enabled(struct clk_hw *hw)
{
struct ccu_common *common = hw_to_ccu_common(hw);
return ccu_read(common, swcr2) & PLLA_SWCR2_EN;
}
static int ccu_plla_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, swcr2, PLLA_SWCR2_EN, PLLA_SWCR2_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_plla_disable(struct clk_hw *hw)
{
struct ccu_common *common = hw_to_ccu_common(hw);
ccu_update(common, swcr2, PLLA_SWCR2_EN, 0);
}
/*
* PLLAs must be gated before changing rate, which is ensured by
* flag CLK_SET_RATE_GATE.
*/
static int ccu_plla_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_plla_update_param(pll, entry);
return 0;
}
static unsigned long ccu_plla_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_plla_lookup_matched_entry(pll);
WARN_ON_ONCE(!entry);
return entry ? entry->rate : 0;
}
static int ccu_plla_init(struct clk_hw *hw)
{
struct ccu_pll *pll = hw_to_ccu_pll(hw);
if (ccu_plla_lookup_matched_entry(pll))
return 0;
ccu_plla_disable(hw);
ccu_plla_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,
.determine_rate = ccu_pll_determine_rate,
.is_enabled = ccu_pll_is_enabled,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_pll_ops, "CLK_SPACEMIT");
const struct clk_ops spacemit_ccu_plla_ops = {
.init = ccu_plla_init,
.enable = ccu_plla_enable,
.disable = ccu_plla_disable,
.set_rate = ccu_plla_set_rate,
.recalc_rate = ccu_plla_recalc_rate,
.determine_rate = ccu_pll_determine_rate,
.is_enabled = ccu_plla_is_enabled,
};
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_plla_ops, "CLK_SPACEMIT");
+123
View File
@@ -0,0 +1,123 @@
/* 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: Value of register PLLx_SW1_CTRL.
* @swcr2: Value of register PLLAx_SW2_CTRL.
* @swcr3: value of register PLLx_SW3_CTRL.
*
* See below tables for the register used in PPL/PPLA clocks
*
* Regular PLL type
* | Enable | swcr3 | PLLx_SW3_CTRL - BIT[31] |
* -----------------------------------------------
* | Config | swcr1 | PLLx_SW1_CTRL - BIT[31:0] |
* | | swcr2 | Not used |
* | | swcr3 | PLLx_SW3_CTRL - BIT[30:0] |
*
* Special PLL type A
* | Enable | swcr2 | PLLAx_SW2_CTRL - BIT[16] |
* -----------------------------------------------
* | Config | swcr1 | PLLAx_SW1_CTRL - BIT[31:0] |
* | | swcr2 | PLLAx_SW2_CTRL - BIT[15:8] |
* | | swcr3 | PLLAx_SW3_CTRL - BIT[31:0] |
*
*/
struct ccu_pll_rate_tbl {
unsigned long rate;
u32 swcr1;
u32 swcr2;
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, \
}
#define CCU_PLLA_RATE(_rate, _swcr1, _swcr2, _swcr3) \
{ \
.rate = _rate, \
.swcr1 = _swcr1, \
.swcr2 = _swcr2, \
.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_COMMON_HWINIT(_name, _ops, _flags) \
(&(struct clk_init_data) { \
.name = #_name, \
.ops = _ops, \
.parent_data = &(struct clk_parent_data) { .index = 0 }, \
.num_parents = 1, \
.flags = _flags, \
})
#define CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \
_reg_lock, _mask_lock, _ops, _flags) \
static struct ccu_pll _name = { \
.config = CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock), \
.common = { \
.reg_swcr1 = _reg_swcr1, \
.reg_swcr2 = _reg_swcr2, \
.reg_swcr3 = _reg_swcr3, \
.hw.init = CCU_PLL_COMMON_HWINIT(_name, _ops, _flags) \
} \
}
#define CCU_PLL_DEFINE(_name, _table, _reg_swcr1, _reg_swcr3, _reg_lock, \
_mask_lock, _flags) \
CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, 0, _reg_swcr3, \
_reg_lock, _mask_lock, &spacemit_ccu_pll_ops, _flags)
#define CCU_PLLA_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \
_reg_lock, _mask_lock, _flags) \
CCU_PLL_X_DEFINE(_name, _table, _reg_swcr1, _reg_swcr2, _reg_swcr3, \
_reg_lock, _mask_lock, &spacemit_ccu_plla_ops, _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;
extern const struct clk_ops spacemit_ccu_plla_ops;
#endif