linux/drivers/clk/sophgo/clk-sg2042-pll.c

568 lines
15 KiB
C
Raw Permalink Normal View History

// 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);
}
clk: sophgo: Avoid -Wsometimes-uninitialized in sg2042_clk_pll_set_rate() Clang warns (or errors with CONFIG_WERROR=y): drivers/clk/sophgo/clk-sg2042-pll.c:396:6: error: variable 'ret' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized] 396 | if (sg2042_pll_enable(pll, 0)) { | ^~~~~~~~~~~~~~~~~~~~~~~~~ drivers/clk/sophgo/clk-sg2042-pll.c:418:9: note: uninitialized use occurs here 418 | return ret; | ^~~ drivers/clk/sophgo/clk-sg2042-pll.c:396:2: note: remove the 'if' if its condition is always false 396 | if (sg2042_pll_enable(pll, 0)) { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 397 | pr_warn("Can't disable pll(%s), status error\n", pll->hw.init->name); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 398 | goto out; | ~~~~~~~~~ 399 | } | ~ drivers/clk/sophgo/clk-sg2042-pll.c:393:9: note: initialize the variable 'ret' to silence this warning 393 | int ret; | ^ | = 0 1 error generated. sg2042_pll_enable() only ever returns zero, so this situation cannot happen, but clang does not perform interprocedural analysis, so it cannot know this to avoid the warning. Make it clearer to the compiler by making sg2042_pll_enable() void and eliminate the error handling in sg2042_clk_pll_set_rate(), which clears up the warning, as ret will always be initialized. Fixes: 48cf7e01386e ("clk: sophgo: Add SG2042 clock driver") Signed-off-by: Nathan Chancellor <nathan@kernel.org> Link: https://lore.kernel.org/r/20240710-clk-sg2042-fix-sometimes-uninitialized-pll_set_rate-v1-1-538fa82dd539@kernel.org Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2024-07-10 10:07:52 -07:00
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 long sg2042_clk_pll_round_rate(struct clk_hw *hw,
unsigned long req_rate,
unsigned long *prate)
{
struct sg2042_pll_ctrl pctrl_table;
unsigned int value;
long proper_rate;
int ret;
ret = sg2042_get_pll_ctl_setting(&pctrl_table, req_rate, *prate);
if (ret) {
proper_rate = 0;
goto out;
}
value = sg2042_pll_ctrl_encode(&pctrl_table);
proper_rate = (long)sg2042_pll_recalc_rate(value, *prate);
out:
pr_debug("--> %s: pll_round_rate: val = %ld\n",
clk_hw_get_name(hw), proper_rate);
return proper_rate;
}
static int sg2042_clk_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
req->rate = sg2042_clk_pll_round_rate(hw, min(req->rate, req->max_rate),
&req->best_parent_rate);
pr_debug("--> %s: pll_determine_rate: val = %ld\n",
clk_hw_get_name(hw), req->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);
clk: sophgo: Avoid -Wsometimes-uninitialized in sg2042_clk_pll_set_rate() Clang warns (or errors with CONFIG_WERROR=y): drivers/clk/sophgo/clk-sg2042-pll.c:396:6: error: variable 'ret' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized] 396 | if (sg2042_pll_enable(pll, 0)) { | ^~~~~~~~~~~~~~~~~~~~~~~~~ drivers/clk/sophgo/clk-sg2042-pll.c:418:9: note: uninitialized use occurs here 418 | return ret; | ^~~ drivers/clk/sophgo/clk-sg2042-pll.c:396:2: note: remove the 'if' if its condition is always false 396 | if (sg2042_pll_enable(pll, 0)) { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 397 | pr_warn("Can't disable pll(%s), status error\n", pll->hw.init->name); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 398 | goto out; | ~~~~~~~~~ 399 | } | ~ drivers/clk/sophgo/clk-sg2042-pll.c:393:9: note: initialize the variable 'ret' to silence this warning 393 | int ret; | ^ | = 0 1 error generated. sg2042_pll_enable() only ever returns zero, so this situation cannot happen, but clang does not perform interprocedural analysis, so it cannot know this to avoid the warning. Make it clearer to the compiler by making sg2042_pll_enable() void and eliminate the error handling in sg2042_clk_pll_set_rate(), which clears up the warning, as ret will always be initialized. Fixes: 48cf7e01386e ("clk: sophgo: Add SG2042 clock driver") Signed-off-by: Nathan Chancellor <nathan@kernel.org> Link: https://lore.kernel.org/r/20240710-clk-sg2042-fix-sometimes-uninitialized-pll_set_rate-v1-1-538fa82dd539@kernel.org Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2024-07-10 10:07:52 -07:00
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);
clk: sophgo: Avoid -Wsometimes-uninitialized in sg2042_clk_pll_set_rate() Clang warns (or errors with CONFIG_WERROR=y): drivers/clk/sophgo/clk-sg2042-pll.c:396:6: error: variable 'ret' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized] 396 | if (sg2042_pll_enable(pll, 0)) { | ^~~~~~~~~~~~~~~~~~~~~~~~~ drivers/clk/sophgo/clk-sg2042-pll.c:418:9: note: uninitialized use occurs here 418 | return ret; | ^~~ drivers/clk/sophgo/clk-sg2042-pll.c:396:2: note: remove the 'if' if its condition is always false 396 | if (sg2042_pll_enable(pll, 0)) { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 397 | pr_warn("Can't disable pll(%s), status error\n", pll->hw.init->name); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 398 | goto out; | ~~~~~~~~~ 399 | } | ~ drivers/clk/sophgo/clk-sg2042-pll.c:393:9: note: initialize the variable 'ret' to silence this warning 393 | int ret; | ^ | = 0 1 error generated. sg2042_pll_enable() only ever returns zero, so this situation cannot happen, but clang does not perform interprocedural analysis, so it cannot know this to avoid the warning. Make it clearer to the compiler by making sg2042_pll_enable() void and eliminate the error handling in sg2042_clk_pll_set_rate(), which clears up the warning, as ret will always be initialized. Fixes: 48cf7e01386e ("clk: sophgo: Add SG2042 clock driver") Signed-off-by: Nathan Chancellor <nathan@kernel.org> Link: https://lore.kernel.org/r/20240710-clk-sg2042-fix-sometimes-uninitialized-pll_set_rate-v1-1-538fa82dd539@kernel.org Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2024-07-10 10:07:52 -07:00
goto out;
}
value = sg2042_pll_ctrl_encode(&pctrl_table);
/* write the value to top register */
writel(value, pll->base + pll->offset_ctrl);
out:
clk: sophgo: Avoid -Wsometimes-uninitialized in sg2042_clk_pll_set_rate() Clang warns (or errors with CONFIG_WERROR=y): drivers/clk/sophgo/clk-sg2042-pll.c:396:6: error: variable 'ret' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized] 396 | if (sg2042_pll_enable(pll, 0)) { | ^~~~~~~~~~~~~~~~~~~~~~~~~ drivers/clk/sophgo/clk-sg2042-pll.c:418:9: note: uninitialized use occurs here 418 | return ret; | ^~~ drivers/clk/sophgo/clk-sg2042-pll.c:396:2: note: remove the 'if' if its condition is always false 396 | if (sg2042_pll_enable(pll, 0)) { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 397 | pr_warn("Can't disable pll(%s), status error\n", pll->hw.init->name); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 398 | goto out; | ~~~~~~~~~ 399 | } | ~ drivers/clk/sophgo/clk-sg2042-pll.c:393:9: note: initialize the variable 'ret' to silence this warning 393 | int ret; | ^ | = 0 1 error generated. sg2042_pll_enable() only ever returns zero, so this situation cannot happen, but clang does not perform interprocedural analysis, so it cannot know this to avoid the warning. Make it clearer to the compiler by making sg2042_pll_enable() void and eliminate the error handling in sg2042_clk_pll_set_rate(), which clears up the warning, as ret will always be initialized. Fixes: 48cf7e01386e ("clk: sophgo: Add SG2042 clock driver") Signed-off-by: Nathan Chancellor <nathan@kernel.org> Link: https://lore.kernel.org/r/20240710-clk-sg2042-fix-sometimes-uninitialized-pll_set_rate-v1-1-538fa82dd539@kernel.org Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2024-07-10 10:07:52 -07:00
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,
.round_rate = sg2042_clk_pll_round_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,
.round_rate = sg2042_clk_pll_round_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");