linux/drivers/clk/meson/clk-pll.c
Dmitry Rokosov 6e2acbfe59 clk: meson: change usleep_range() to udelay() for atomic context
The function meson_clk_pll_enable() can be invoked under the enable_lock
spinlock from the clk core logic, which risks a kernel panic during the
usleep_range() call:

   BUG: scheduling while atomic: kworker/u4:2/36/0x00000002
   Modules linked in: g_ffs usb_f_fs libcomposite
   CPU: 1 PID: 36 Comm: kworker/u4:2 Not tainted 6.4.0-rc5 #273
   Workqueue: events_unbound async_run_entry_fn
   Call trace:
    dump_backtrace+0x9c/0x128
    show_stack+0x20/0x38
    dump_stack_lvl+0x48/0x60
    dump_stack+0x18/0x28
    __schedule_bug+0x58/0x78
    __schedule+0x828/0xa88
    schedule+0x64/0xd8
    schedule_hrtimeout_range_clock+0xd0/0x208
    schedule_hrtimeout_range+0x1c/0x30
    usleep_range_state+0x6c/0xa8
    meson_clk_pll_enable+0x1f4/0x310
    clk_core_enable+0x78/0x200
    clk_core_enable+0x58/0x200
    clk_core_enable+0x58/0x200
    clk_core_enable+0x58/0x200
    clk_enable+0x34/0x60

So it is required to use the udelay() function instead of usleep_range()
for the atomic context safety.

Fixes: b6ec400aa1 ("clk: meson: introduce new pll power-on sequence for A1 SoC family")
Reported-by: Jan Dakinevich <yvdakinevich@sberdevices.ru>
Signed-off-by: Dmitry Rokosov <ddrokosov@sberdevices.ru>
Link: https://lore.kernel.org/r/20230704215404.11533-1-ddrokosov@sberdevices.ru
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
2023-07-11 11:35:42 +02:00

490 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015 Endless Mobile, Inc.
* Author: Carlo Caione <carlo@endlessm.com>
*
* Copyright (c) 2018 Baylibre, SAS.
* Author: Jerome Brunet <jbrunet@baylibre.com>
*/
/*
* In the most basic form, a Meson PLL is composed as follows:
*
* PLL
* +--------------------------------+
* | |
* | +--+ |
* in >>-----[ /N ]--->| | +-----+ |
* | | |------| DCO |---->> out
* | +--------->| | +--v--+ |
* | | +--+ | |
* | | | |
* | +--[ *(M + (F/Fmax) ]<--+ |
* | |
* +--------------------------------+
*
* out = in * (m + frac / frac_max) / n
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/math64.h>
#include <linux/module.h>
#include "clk-regmap.h"
#include "clk-pll.h"
static inline struct meson_clk_pll_data *
meson_clk_pll_data(struct clk_regmap *clk)
{
return (struct meson_clk_pll_data *)clk->data;
}
static int __pll_round_closest_mult(struct meson_clk_pll_data *pll)
{
if ((pll->flags & CLK_MESON_PLL_ROUND_CLOSEST) &&
!MESON_PARM_APPLICABLE(&pll->frac))
return 1;
return 0;
}
static unsigned long __pll_params_to_rate(unsigned long parent_rate,
unsigned int m, unsigned int n,
unsigned int frac,
struct meson_clk_pll_data *pll)
{
u64 rate = (u64)parent_rate * m;
if (frac && MESON_PARM_APPLICABLE(&pll->frac)) {
u64 frac_rate = (u64)parent_rate * frac;
rate += DIV_ROUND_UP_ULL(frac_rate,
(1 << pll->frac.width));
}
return DIV_ROUND_UP_ULL(rate, n);
}
static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
unsigned int m, n, frac;
n = meson_parm_read(clk->map, &pll->n);
/*
* On some HW, N is set to zero on init. This value is invalid as
* it would result in a division by zero. The rate can't be
* calculated in this case
*/
if (n == 0)
return 0;
m = meson_parm_read(clk->map, &pll->m);
frac = MESON_PARM_APPLICABLE(&pll->frac) ?
meson_parm_read(clk->map, &pll->frac) :
0;
return __pll_params_to_rate(parent_rate, m, n, frac, pll);
}
static unsigned int __pll_params_with_frac(unsigned long rate,
unsigned long parent_rate,
unsigned int m,
unsigned int n,
struct meson_clk_pll_data *pll)
{
unsigned int frac_max = (1 << pll->frac.width);
u64 val = (u64)rate * n;
/* Bail out if we are already over the requested rate */
if (rate < parent_rate * m / n)
return 0;
if (pll->flags & CLK_MESON_PLL_ROUND_CLOSEST)
val = DIV_ROUND_CLOSEST_ULL(val * frac_max, parent_rate);
else
val = div_u64(val * frac_max, parent_rate);
val -= m * frac_max;
return min((unsigned int)val, (frac_max - 1));
}
static bool meson_clk_pll_is_better(unsigned long rate,
unsigned long best,
unsigned long now,
struct meson_clk_pll_data *pll)
{
if (__pll_round_closest_mult(pll)) {
/* Round Closest */
if (abs(now - rate) < abs(best - rate))
return true;
} else {
/* Round down */
if (now <= rate && best < now)
return true;
}
return false;
}
static int meson_clk_get_pll_table_index(unsigned int index,
unsigned int *m,
unsigned int *n,
struct meson_clk_pll_data *pll)
{
if (!pll->table[index].n)
return -EINVAL;
*m = pll->table[index].m;
*n = pll->table[index].n;
return 0;
}
static unsigned int meson_clk_get_pll_range_m(unsigned long rate,
unsigned long parent_rate,
unsigned int n,
struct meson_clk_pll_data *pll)
{
u64 val = (u64)rate * n;
if (__pll_round_closest_mult(pll))
return DIV_ROUND_CLOSEST_ULL(val, parent_rate);
return div_u64(val, parent_rate);
}
static int meson_clk_get_pll_range_index(unsigned long rate,
unsigned long parent_rate,
unsigned int index,
unsigned int *m,
unsigned int *n,
struct meson_clk_pll_data *pll)
{
*n = index + 1;
/* Check the predivider range */
if (*n >= (1 << pll->n.width))
return -EINVAL;
if (*n == 1) {
/* Get the boundaries out the way */
if (rate <= pll->range->min * parent_rate) {
*m = pll->range->min;
return -ENODATA;
} else if (rate >= pll->range->max * parent_rate) {
*m = pll->range->max;
return -ENODATA;
}
}
*m = meson_clk_get_pll_range_m(rate, parent_rate, *n, pll);
/* the pre-divider gives a multiplier too big - stop */
if (*m >= (1 << pll->m.width))
return -EINVAL;
return 0;
}
static int meson_clk_get_pll_get_index(unsigned long rate,
unsigned long parent_rate,
unsigned int index,
unsigned int *m,
unsigned int *n,
struct meson_clk_pll_data *pll)
{
if (pll->range)
return meson_clk_get_pll_range_index(rate, parent_rate,
index, m, n, pll);
else if (pll->table)
return meson_clk_get_pll_table_index(index, m, n, pll);
return -EINVAL;
}
static int meson_clk_get_pll_settings(unsigned long rate,
unsigned long parent_rate,
unsigned int *best_m,
unsigned int *best_n,
struct meson_clk_pll_data *pll)
{
unsigned long best = 0, now = 0;
unsigned int i, m, n;
int ret;
for (i = 0, ret = 0; !ret; i++) {
ret = meson_clk_get_pll_get_index(rate, parent_rate,
i, &m, &n, pll);
if (ret == -EINVAL)
break;
now = __pll_params_to_rate(parent_rate, m, n, 0, pll);
if (meson_clk_pll_is_better(rate, best, now, pll)) {
best = now;
*best_m = m;
*best_n = n;
if (now == rate)
break;
}
}
return best ? 0 : -EINVAL;
}
static int meson_clk_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
unsigned int m, n, frac;
unsigned long round;
int ret;
ret = meson_clk_get_pll_settings(req->rate, req->best_parent_rate,
&m, &n, pll);
if (ret)
return ret;
round = __pll_params_to_rate(req->best_parent_rate, m, n, 0, pll);
if (!MESON_PARM_APPLICABLE(&pll->frac) || req->rate == round) {
req->rate = round;
return 0;
}
/*
* The rate provided by the setting is not an exact match, let's
* try to improve the result using the fractional parameter
*/
frac = __pll_params_with_frac(req->rate, req->best_parent_rate, m, n, pll);
req->rate = __pll_params_to_rate(req->best_parent_rate, m, n, frac, pll);
return 0;
}
static int meson_clk_pll_wait_lock(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
int delay = 5000;
do {
/* Is the clock locked now ? Time out after 100ms. */
if (meson_parm_read(clk->map, &pll->l))
return 0;
udelay(20);
} while (--delay);
return -ETIMEDOUT;
}
static int meson_clk_pll_init(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
if (pll->init_count) {
if (MESON_PARM_APPLICABLE(&pll->rst))
meson_parm_write(clk->map, &pll->rst, 1);
regmap_multi_reg_write(clk->map, pll->init_regs,
pll->init_count);
if (MESON_PARM_APPLICABLE(&pll->rst))
meson_parm_write(clk->map, &pll->rst, 0);
}
return 0;
}
static int meson_clk_pll_is_enabled(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
if (MESON_PARM_APPLICABLE(&pll->rst) &&
meson_parm_read(clk->map, &pll->rst))
return 0;
if (!meson_parm_read(clk->map, &pll->en) ||
!meson_parm_read(clk->map, &pll->l))
return 0;
return 1;
}
static int meson_clk_pcie_pll_enable(struct clk_hw *hw)
{
int retries = 10;
do {
meson_clk_pll_init(hw);
if (!meson_clk_pll_wait_lock(hw))
return 0;
pr_info("Retry enabling PCIe PLL clock\n");
} while (--retries);
return -EIO;
}
static int meson_clk_pll_enable(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
/* do nothing if the PLL is already enabled */
if (clk_hw_is_enabled(hw))
return 0;
/* Make sure the pll is in reset */
if (MESON_PARM_APPLICABLE(&pll->rst))
meson_parm_write(clk->map, &pll->rst, 1);
/* Enable the pll */
meson_parm_write(clk->map, &pll->en, 1);
/* Take the pll out reset */
if (MESON_PARM_APPLICABLE(&pll->rst))
meson_parm_write(clk->map, &pll->rst, 0);
/*
* Compared with the previous SoCs, self-adaption current module
* is newly added for A1, keep the new power-on sequence to enable the
* PLL. The sequence is:
* 1. enable the pll, delay for 10us
* 2. enable the pll self-adaption current module, delay for 40us
* 3. enable the lock detect module
*/
if (MESON_PARM_APPLICABLE(&pll->current_en)) {
udelay(10);
meson_parm_write(clk->map, &pll->current_en, 1);
udelay(40);
}
if (MESON_PARM_APPLICABLE(&pll->l_detect)) {
meson_parm_write(clk->map, &pll->l_detect, 1);
meson_parm_write(clk->map, &pll->l_detect, 0);
}
if (meson_clk_pll_wait_lock(hw))
return -EIO;
return 0;
}
static void meson_clk_pll_disable(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
/* Put the pll is in reset */
if (MESON_PARM_APPLICABLE(&pll->rst))
meson_parm_write(clk->map, &pll->rst, 1);
/* Disable the pll */
meson_parm_write(clk->map, &pll->en, 0);
/* Disable PLL internal self-adaption current module */
if (MESON_PARM_APPLICABLE(&pll->current_en))
meson_parm_write(clk->map, &pll->current_en, 0);
}
static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
unsigned int enabled, m, n, frac = 0;
unsigned long old_rate;
int ret;
if (parent_rate == 0 || rate == 0)
return -EINVAL;
old_rate = clk_hw_get_rate(hw);
ret = meson_clk_get_pll_settings(rate, parent_rate, &m, &n, pll);
if (ret)
return ret;
enabled = meson_parm_read(clk->map, &pll->en);
if (enabled)
meson_clk_pll_disable(hw);
meson_parm_write(clk->map, &pll->n, n);
meson_parm_write(clk->map, &pll->m, m);
if (MESON_PARM_APPLICABLE(&pll->frac)) {
frac = __pll_params_with_frac(rate, parent_rate, m, n, pll);
meson_parm_write(clk->map, &pll->frac, frac);
}
/* If the pll is stopped, bail out now */
if (!enabled)
return 0;
ret = meson_clk_pll_enable(hw);
if (ret) {
pr_warn("%s: pll did not lock, trying to restore old rate %lu\n",
__func__, old_rate);
/*
* FIXME: Do we really need/want this HACK ?
* It looks unsafe. what happens if the clock gets into a
* broken state and we can't lock back on the old_rate ? Looks
* like an infinite recursion is possible
*/
meson_clk_pll_set_rate(hw, old_rate, parent_rate);
}
return ret;
}
/*
* The Meson G12A PCIE PLL is fined tuned to deliver a very precise
* 100MHz reference clock for the PCIe Analog PHY, and thus requires
* a strict register sequence to enable the PLL.
* To simplify, re-use the _init() op to enable the PLL and keep
* the other ops except set_rate since the rate is fixed.
*/
const struct clk_ops meson_clk_pcie_pll_ops = {
.recalc_rate = meson_clk_pll_recalc_rate,
.determine_rate = meson_clk_pll_determine_rate,
.is_enabled = meson_clk_pll_is_enabled,
.enable = meson_clk_pcie_pll_enable,
.disable = meson_clk_pll_disable
};
EXPORT_SYMBOL_GPL(meson_clk_pcie_pll_ops);
const struct clk_ops meson_clk_pll_ops = {
.init = meson_clk_pll_init,
.recalc_rate = meson_clk_pll_recalc_rate,
.determine_rate = meson_clk_pll_determine_rate,
.set_rate = meson_clk_pll_set_rate,
.is_enabled = meson_clk_pll_is_enabled,
.enable = meson_clk_pll_enable,
.disable = meson_clk_pll_disable
};
EXPORT_SYMBOL_GPL(meson_clk_pll_ops);
const struct clk_ops meson_clk_pll_ro_ops = {
.recalc_rate = meson_clk_pll_recalc_rate,
.is_enabled = meson_clk_pll_is_enabled,
};
EXPORT_SYMBOL_GPL(meson_clk_pll_ro_ops);
MODULE_DESCRIPTION("Amlogic PLL driver");
MODULE_AUTHOR("Carlo Caione <carlo@endlessm.com>");
MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
MODULE_LICENSE("GPL v2");