- pll fixes for GXBB, GXL and AXG

- use regmap in clock controllers for GXBB, GXL and AXG
  - general clock updates for Meson8, GXBB, GXL and AXG
 Based on the clk-helpers topic branch as a dependency.
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJap5YzAAoJEHfc29rIyEnRtxgQAJ+KgOifsPex8pjqSm50akLx
 4eQJA1xteu5TEipC66eSM0/EyHyVHhShaqYQcNZmNA0ckI7wZkii4+EXPvrCxlyR
 ZvEs98BpPZKsZQ58yrKQ44AHdFREDnMF+9vDzklRhKzp676s9o9+IxowfAxCIWlz
 e4mpMOTwqRYMPVN1WabHCjVsmwkMR0d1+1JpoV1uZHJXYBBc2Rrd0BhoL9Ma9uiC
 Rr9osS97O8/xsk1bn6o1CLHhvLylvDw9rY6jSh2FltM7S6kkgg4SDdIvXc3unx2i
 ZpxQP9+f/TslAvXRh5ejOq7xPvOtXXqvCDbU5Vs226n3rwpTEhHrjSFCsmPB+C/d
 1+B2bEELTapDHiq0s3JY3GP9Ks58RWwJzsaOwvh/3TVr2hvsJeQaiVUDqsyJ7nLu
 QEaaIhGtImw1WFyxghoc6IU9JH7Y/w41GioGg8rQEAhs+iKI6RQ9ZnO5TFW7aelQ
 VUYnWWCY/amJYoPdoMqNZVsDXqCJO7JmhB79t2Ssp9pOkipSHspzTemuT/+aqhU4
 GGS3iKl2SE42KYbu5EK+YdVynWPd72swtRAu985D7K+4UToyhbRSXFm6XBubt1fw
 99BdC5yGcyr1ShPmHAjTlL8+GttN74c4af7mvZgPom15g6bZw22e3X2LRmT1mL7z
 OQbsseAj9SZzxMtm83rK
 =TpHi
 -----END PGP SIGNATURE-----

Merge tag 'clk-for-v4.17-1' of https://github.com/BayLibre/clk-meson into clk-amlogic

Pull amlogic clk driver updates from Neil Armstrong:

 - pll fixes for GXBB, GXL and AXG
 - use regmap in clock controllers for GXBB, GXL and AXG
 - general clock updates for Meson8, GXBB, GXL and AXG

(Based on the clk-helpers topic branch as a dependency)

* tag 'clk-for-v4.17-1' of https://github.com/BayLibre/clk-meson: (49 commits)
  clk: meson: clean-up clk81 clocks
  clk: meson: add fdiv clock gates
  clk: meson: add mpll pre-divider
  clk: meson: axg: add hifi pll clock
  clk: meson: axg: add hifi clock bindings
  clk: meson: add ROUND_CLOSEST to the pll driver
  clk: meson: add gp0 frac parameter for axg and gxl
  clk: meson: improve pll driver results with frac
  clk: meson: remove special gp0 lock loop
  clk: meson: poke pll CNTL last
  clk: meson: add fractional part of meson8b fixed_pll
  clk: meson: use hhi syscon if available
  clk: meson: remove obsolete cpu_clk
  clk: meson: rework meson8b cpu clock
  clk: meson: split divider and gate part of mpll
  clk: meson: migrate plls clocks to clk_regmap
  clk: meson: migrate the audio divider clock to clk_regmap
  clk: meson: migrate mplls clocks to clk_regmap
  clk: meson: add regmap helpers for parm
  clk: meson: migrate muxes to clk_regmap
  ...
This commit is contained in:
Stephen Boyd 2018-03-14 15:16:23 -07:00
commit 186dcd4ab2
24 changed files with 2726 additions and 2196 deletions

View File

@ -28,12 +28,10 @@
* parent - fixed parent. No clk_set_parent support
*/
#define div_mask(width) ((1 << (width)) - 1)
static unsigned int _get_table_maxdiv(const struct clk_div_table *table,
u8 width)
{
unsigned int maxdiv = 0, mask = div_mask(width);
unsigned int maxdiv = 0, mask = clk_div_mask(width);
const struct clk_div_table *clkt;
for (clkt = table; clkt->div; clkt++)
@ -57,12 +55,12 @@ static unsigned int _get_maxdiv(const struct clk_div_table *table, u8 width,
unsigned long flags)
{
if (flags & CLK_DIVIDER_ONE_BASED)
return div_mask(width);
return clk_div_mask(width);
if (flags & CLK_DIVIDER_POWER_OF_TWO)
return 1 << div_mask(width);
return 1 << clk_div_mask(width);
if (table)
return _get_table_maxdiv(table, width);
return div_mask(width) + 1;
return clk_div_mask(width) + 1;
}
static unsigned int _get_table_div(const struct clk_div_table *table,
@ -84,7 +82,7 @@ static unsigned int _get_div(const struct clk_div_table *table,
if (flags & CLK_DIVIDER_POWER_OF_TWO)
return 1 << val;
if (flags & CLK_DIVIDER_MAX_AT_ZERO)
return val ? val : div_mask(width) + 1;
return val ? val : clk_div_mask(width) + 1;
if (table)
return _get_table_div(table, val);
return val + 1;
@ -109,7 +107,7 @@ static unsigned int _get_val(const struct clk_div_table *table,
if (flags & CLK_DIVIDER_POWER_OF_TWO)
return __ffs(div);
if (flags & CLK_DIVIDER_MAX_AT_ZERO)
return (div == div_mask(width) + 1) ? 0 : div;
return (div == clk_div_mask(width) + 1) ? 0 : div;
if (table)
return _get_table_val(table, div);
return div - 1;
@ -141,7 +139,7 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
unsigned int val;
val = clk_readl(divider->reg) >> divider->shift;
val &= div_mask(divider->width);
val &= clk_div_mask(divider->width);
return divider_recalc_rate(hw, parent_rate, val, divider->table,
divider->flags, divider->width);
@ -344,19 +342,43 @@ long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
}
EXPORT_SYMBOL_GPL(divider_round_rate_parent);
long divider_ro_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
unsigned long rate, unsigned long *prate,
const struct clk_div_table *table, u8 width,
unsigned long flags, unsigned int val)
{
int div;
div = _get_div(table, val, flags, width);
/* Even a read-only clock can propagate a rate change */
if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
if (!parent)
return -EINVAL;
*prate = clk_hw_round_rate(parent, rate * div);
}
return DIV_ROUND_UP_ULL((u64)*prate, div);
}
EXPORT_SYMBOL_GPL(divider_ro_round_rate_parent);
static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct clk_divider *divider = to_clk_divider(hw);
int bestdiv;
/* if read only, just return current value */
if (divider->flags & CLK_DIVIDER_READ_ONLY) {
bestdiv = clk_readl(divider->reg) >> divider->shift;
bestdiv &= div_mask(divider->width);
bestdiv = _get_div(divider->table, bestdiv, divider->flags,
divider->width);
return DIV_ROUND_UP_ULL((u64)*prate, bestdiv);
u32 val;
val = clk_readl(divider->reg) >> divider->shift;
val &= clk_div_mask(divider->width);
return divider_ro_round_rate(hw, rate, prate, divider->table,
divider->width, divider->flags,
val);
}
return divider_round_rate(hw, rate, prate, divider->table,
@ -376,7 +398,7 @@ int divider_get_val(unsigned long rate, unsigned long parent_rate,
value = _get_val(table, div, flags, width);
return min_t(unsigned int, value, div_mask(width));
return min_t(unsigned int, value, clk_div_mask(width));
}
EXPORT_SYMBOL_GPL(divider_get_val);
@ -399,10 +421,10 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
__acquire(divider->lock);
if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
val = div_mask(divider->width) << (divider->shift + 16);
val = clk_div_mask(divider->width) << (divider->shift + 16);
} else {
val = clk_readl(divider->reg);
val &= ~(div_mask(divider->width) << divider->shift);
val &= ~(clk_div_mask(divider->width) << divider->shift);
}
val |= (u32)value << divider->shift;
clk_writel(val, divider->reg);

View File

@ -26,35 +26,24 @@
* parent - parent is adjustable through clk_set_parent
*/
static u8 clk_mux_get_parent(struct clk_hw *hw)
int clk_mux_val_to_index(struct clk_hw *hw, u32 *table, unsigned int flags,
unsigned int val)
{
struct clk_mux *mux = to_clk_mux(hw);
int num_parents = clk_hw_get_num_parents(hw);
u32 val;
/*
* FIXME need a mux-specific flag to determine if val is bitwise or numeric
* e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1
* to 0x7 (index starts at one)
* OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so
* val = 0x4 really means "bit 2, index starts at bit 0"
*/
val = clk_readl(mux->reg) >> mux->shift;
val &= mux->mask;
if (mux->table) {
if (table) {
int i;
for (i = 0; i < num_parents; i++)
if (mux->table[i] == val)
if (table[i] == val)
return i;
return -EINVAL;
}
if (val && (mux->flags & CLK_MUX_INDEX_BIT))
if (val && (flags & CLK_MUX_INDEX_BIT))
val = ffs(val) - 1;
if (val && (mux->flags & CLK_MUX_INDEX_ONE))
if (val && (flags & CLK_MUX_INDEX_ONE))
val--;
if (val >= num_parents)
@ -62,22 +51,43 @@ static u8 clk_mux_get_parent(struct clk_hw *hw)
return val;
}
EXPORT_SYMBOL_GPL(clk_mux_val_to_index);
unsigned int clk_mux_index_to_val(u32 *table, unsigned int flags, u8 index)
{
unsigned int val = index;
if (table) {
val = table[index];
} else {
if (flags & CLK_MUX_INDEX_BIT)
val = 1 << index;
if (flags & CLK_MUX_INDEX_ONE)
val++;
}
return val;
}
EXPORT_SYMBOL_GPL(clk_mux_index_to_val);
static u8 clk_mux_get_parent(struct clk_hw *hw)
{
struct clk_mux *mux = to_clk_mux(hw);
u32 val;
val = clk_readl(mux->reg) >> mux->shift;
val &= mux->mask;
return clk_mux_val_to_index(hw, mux->table, mux->flags, val);
}
static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_mux *mux = to_clk_mux(hw);
u32 val;
u32 val = clk_mux_index_to_val(mux->table, mux->flags, index);
unsigned long flags = 0;
if (mux->table) {
index = mux->table[index];
} else {
if (mux->flags & CLK_MUX_INDEX_BIT)
index = 1 << index;
if (mux->flags & CLK_MUX_INDEX_ONE)
index++;
}
u32 reg;
if (mux->lock)
spin_lock_irqsave(mux->lock, flags);
@ -85,13 +95,14 @@ static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
__acquire(mux->lock);
if (mux->flags & CLK_MUX_HIWORD_MASK) {
val = mux->mask << (mux->shift + 16);
reg = mux->mask << (mux->shift + 16);
} else {
val = clk_readl(mux->reg);
val &= ~(mux->mask << mux->shift);
reg = clk_readl(mux->reg);
reg &= ~(mux->mask << mux->shift);
}
val |= index << mux->shift;
clk_writel(val, mux->reg);
val = val << mux->shift;
reg |= val;
clk_writel(reg, mux->reg);
if (mux->lock)
spin_unlock_irqrestore(mux->lock, flags);

View File

@ -1125,8 +1125,10 @@ static int clk_core_round_rate_nolock(struct clk_core *core,
{
lockdep_assert_held(&prepare_lock);
if (!core)
if (!core) {
req->rate = 0;
return 0;
}
clk_core_init_rate_req(core, req);
@ -2927,6 +2929,17 @@ static int __clk_core_init(struct clk_core *core)
core->orphan = true;
}
/*
* optional platform-specific magic
*
* The .init callback is not used by any of the basic clock types, but
* exists for weird hardware that must perform initialization magic.
* Please consider other ways of solving initialization problems before
* using this callback, as its use is discouraged.
*/
if (core->ops->init)
core->ops->init(core->hw);
/*
* Set clk's accuracy. The preferred method is to use
* .recalc_accuracy. For simple clocks and lazy developers the default
@ -2968,38 +2981,10 @@ static int __clk_core_init(struct clk_core *core)
core->rate = core->req_rate = rate;
/*
* walk the list of orphan clocks and reparent any that newly finds a
* parent.
* Enable CLK_IS_CRITICAL clocks so newly added critical clocks
* don't get accidentally disabled when walking the orphan tree and
* reparenting clocks
*/
hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
struct clk_core *parent = __clk_init_parent(orphan);
unsigned long flags;
/*
* we could call __clk_set_parent, but that would result in a
* redundant call to the .set_rate op, if it exists
*/
if (parent) {
/* update the clk tree topology */
flags = clk_enable_lock();
clk_reparent(orphan, parent);
clk_enable_unlock(flags);
__clk_recalc_accuracies(orphan);
__clk_recalc_rates(orphan, 0);
}
}
/*
* optional platform-specific magic
*
* The .init callback is not used by any of the basic clock types, but
* exists for weird hardware that must perform initialization magic.
* Please consider other ways of solving initialization problems before
* using this callback, as its use is discouraged.
*/
if (core->ops->init)
core->ops->init(core->hw);
if (core->flags & CLK_IS_CRITICAL) {
unsigned long flags;
@ -3010,6 +2995,28 @@ static int __clk_core_init(struct clk_core *core)
clk_enable_unlock(flags);
}
/*
* walk the list of orphan clocks and reparent any that newly finds a
* parent.
*/
hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
struct clk_core *parent = __clk_init_parent(orphan);
/*
* We need to use __clk_set_parent_before() and _after() to
* to properly migrate any prepare/enable count of the orphan
* clock. This is important for CLK_IS_CRITICAL clocks, which
* are enabled during init but might not have a parent yet.
*/
if (parent) {
/* update the clk tree topology */
__clk_set_parent_before(orphan, parent);
__clk_set_parent_after(orphan, parent, NULL);
__clk_recalc_accuracies(orphan);
__clk_recalc_rates(orphan, 0);
}
}
kref_init(&core->ref);
out:
clk_pm_runtime_put(core);

View File

@ -3,10 +3,15 @@ config COMMON_CLK_AMLOGIC
depends on OF
depends on ARCH_MESON || COMPILE_TEST
config COMMON_CLK_REGMAP_MESON
bool
select REGMAP
config COMMON_CLK_MESON8B
bool
depends on COMMON_CLK_AMLOGIC
select RESET_CONTROLLER
select COMMON_CLK_REGMAP_MESON
help
Support for the clock controller on AmLogic S802 (Meson8),
S805 (Meson8b) and S812 (Meson8m2) devices. Say Y if you
@ -16,6 +21,8 @@ config COMMON_CLK_GXBB
bool
depends on COMMON_CLK_AMLOGIC
select RESET_CONTROLLER
select COMMON_CLK_REGMAP_MESON
select MFD_SYSCON
help
Support for the clock controller on AmLogic S905 devices, aka gxbb.
Say Y if you want peripherals and CPU frequency scaling to work.
@ -24,6 +31,8 @@ config COMMON_CLK_AXG
bool
depends on COMMON_CLK_AMLOGIC
select RESET_CONTROLLER
select COMMON_CLK_REGMAP_MESON
select MFD_SYSCON
help
Support for the clock controller on AmLogic A113D devices, aka axg.
Say Y if you want peripherals and CPU frequency scaling to work.

View File

@ -2,7 +2,8 @@
# Makefile for Meson specific clk
#
obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-cpu.o clk-mpll.o clk-audio-divider.o
obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-mpll.o clk-audio-divider.o
obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o
obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o gxbb-aoclk-regmap.o gxbb-aoclk-32k.o
obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o gxbb-aoclk-32k.o
obj-$(CONFIG_COMMON_CLK_AXG) += axg.o
obj-$(CONFIG_COMMON_CLK_REGMAP_MESON) += clk-regmap.o

File diff suppressed because it is too large Load Diff

View File

@ -117,8 +117,18 @@
#define CLKID_SD_EMMC_B_CLK0_DIV 62
#define CLKID_SD_EMMC_C_CLK0_SEL 63
#define CLKID_SD_EMMC_C_CLK0_DIV 64
#define CLKID_MPLL0_DIV 65
#define CLKID_MPLL1_DIV 66
#define CLKID_MPLL2_DIV 67
#define CLKID_MPLL3_DIV 68
#define CLKID_MPLL_PREDIV 70
#define CLKID_FCLK_DIV2_DIV 71
#define CLKID_FCLK_DIV3_DIV 72
#define CLKID_FCLK_DIV4_DIV 73
#define CLKID_FCLK_DIV5_DIV 74
#define CLKID_FCLK_DIV7_DIV 75
#define NR_CLKS 65
#define NR_CLKS 76
/* include the CLKIDs that have been made part of the DT binding */
#include <dt-bindings/clock/axg-clkc.h>

View File

@ -28,8 +28,11 @@
#include <linux/clk-provider.h>
#include "clkc.h"
#define to_meson_clk_audio_divider(_hw) container_of(_hw, \
struct meson_clk_audio_divider, hw)
static inline struct meson_clk_audio_div_data *
meson_clk_audio_div_data(struct clk_regmap *clk)
{
return (struct meson_clk_audio_div_data *)clk->data;
}
static int _div_round(unsigned long parent_rate, unsigned long rate,
unsigned long flags)
@ -45,15 +48,9 @@ static int _get_val(unsigned long parent_rate, unsigned long rate)
return DIV_ROUND_UP_ULL((u64)parent_rate, rate) - 1;
}
static int _valid_divider(struct clk_hw *hw, int divider)
static int _valid_divider(unsigned int width, int divider)
{
struct meson_clk_audio_divider *adiv =
to_meson_clk_audio_divider(hw);
int max_divider;
u8 width;
width = adiv->div.width;
max_divider = 1 << width;
int max_divider = 1 << width;
return clamp(divider, 1, max_divider);
}
@ -61,14 +58,11 @@ static int _valid_divider(struct clk_hw *hw, int divider)
static unsigned long audio_divider_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct meson_clk_audio_divider *adiv =
to_meson_clk_audio_divider(hw);
struct parm *p;
unsigned long reg, divider;
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_audio_div_data *adiv = meson_clk_audio_div_data(clk);
unsigned long divider;
p = &adiv->div;
reg = readl(adiv->base + p->reg_off);
divider = PARM_GET(p->width, p->shift, reg) + 1;
divider = meson_parm_read(clk->map, &adiv->div);
return DIV_ROUND_UP_ULL((u64)parent_rate, divider);
}
@ -77,14 +71,14 @@ static long audio_divider_round_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long *parent_rate)
{
struct meson_clk_audio_divider *adiv =
to_meson_clk_audio_divider(hw);
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_audio_div_data *adiv = meson_clk_audio_div_data(clk);
unsigned long max_prate;
int divider;
if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) {
divider = _div_round(*parent_rate, rate, adiv->flags);
divider = _valid_divider(hw, divider);
divider = _valid_divider(adiv->div.width, divider);
return DIV_ROUND_UP_ULL((u64)*parent_rate, divider);
}
@ -93,7 +87,7 @@ static long audio_divider_round_rate(struct clk_hw *hw,
/* Get the corresponding rounded down divider */
divider = max_prate / rate;
divider = _valid_divider(hw, divider);
divider = _valid_divider(adiv->div.width, divider);
/* Get actual rate of the parent */
*parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
@ -106,28 +100,11 @@ static int audio_divider_set_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate)
{
struct meson_clk_audio_divider *adiv =
to_meson_clk_audio_divider(hw);
struct parm *p;
unsigned long reg, flags = 0;
int val;
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_audio_div_data *adiv = meson_clk_audio_div_data(clk);
int val = _get_val(parent_rate, rate);
val = _get_val(parent_rate, rate);
if (adiv->lock)
spin_lock_irqsave(adiv->lock, flags);
else
__acquire(adiv->lock);
p = &adiv->div;
reg = readl(adiv->base + p->reg_off);
reg = PARM_SET(p->width, p->shift, reg, val);
writel(reg, adiv->base + p->reg_off);
if (adiv->lock)
spin_unlock_irqrestore(adiv->lock, flags);
else
__release(adiv->lock);
meson_parm_write(clk->map, &adiv->div, val);
return 0;
}

View File

@ -1,178 +0,0 @@
/*
* Copyright (c) 2015 Endless Mobile, Inc.
* Author: Carlo Caione <carlo@endlessm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* CPU clock path:
*
* +-[/N]-----|3|
* MUX2 +--[/3]-+----------|2| MUX1
* [sys_pll]---|1| |--[/2]------------|1|-|1|
* | |---+------------------|0| | |----- [a5_clk]
* +--|0| | |
* [xtal]---+-------------------------------|0|
*
*
*
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#define MESON_CPU_CLK_CNTL1 0x00
#define MESON_CPU_CLK_CNTL 0x40
#define MESON_CPU_CLK_MUX1 BIT(7)
#define MESON_CPU_CLK_MUX2 BIT(0)
#define MESON_N_WIDTH 9
#define MESON_N_SHIFT 20
#define MESON_SEL_WIDTH 2
#define MESON_SEL_SHIFT 2
#include "clkc.h"
#define to_meson_clk_cpu_hw(_hw) container_of(_hw, struct meson_clk_cpu, hw)
#define to_meson_clk_cpu_nb(_nb) container_of(_nb, struct meson_clk_cpu, clk_nb)
static long meson_clk_cpu_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_hw(hw);
return divider_round_rate(hw, rate, prate, clk_cpu->div_table,
MESON_N_WIDTH, CLK_DIVIDER_ROUND_CLOSEST);
}
static int meson_clk_cpu_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_hw(hw);
unsigned int div, sel, N = 0;
u32 reg;
div = DIV_ROUND_UP(parent_rate, rate);
if (div <= 3) {
sel = div - 1;
} else {
sel = 3;
N = div / 2;
}
reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL1);
reg = PARM_SET(MESON_N_WIDTH, MESON_N_SHIFT, reg, N);
writel(reg, clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL1);
reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL);
reg = PARM_SET(MESON_SEL_WIDTH, MESON_SEL_SHIFT, reg, sel);
writel(reg, clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL);
return 0;
}
static unsigned long meson_clk_cpu_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_hw(hw);
unsigned int N, sel;
unsigned int div = 1;
u32 reg;
reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL1);
N = PARM_GET(MESON_N_WIDTH, MESON_N_SHIFT, reg);
reg = readl(clk_cpu->base + clk_cpu->reg_off + MESON_CPU_CLK_CNTL);
sel = PARM_GET(MESON_SEL_WIDTH, MESON_SEL_SHIFT, reg);
if (sel < 3)
div = sel + 1;
else
div = 2 * N;
return parent_rate / div;
}
/* FIXME MUX1 & MUX2 should be struct clk_hw objects */
static int meson_clk_cpu_pre_rate_change(struct meson_clk_cpu *clk_cpu,
struct clk_notifier_data *ndata)
{
u32 cpu_clk_cntl;
/* switch MUX1 to xtal */
cpu_clk_cntl = readl(clk_cpu->base + clk_cpu->reg_off
+ MESON_CPU_CLK_CNTL);
cpu_clk_cntl &= ~MESON_CPU_CLK_MUX1;
writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off
+ MESON_CPU_CLK_CNTL);
udelay(100);
/* switch MUX2 to sys-pll */
cpu_clk_cntl |= MESON_CPU_CLK_MUX2;
writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off
+ MESON_CPU_CLK_CNTL);
return 0;
}
/* FIXME MUX1 & MUX2 should be struct clk_hw objects */
static int meson_clk_cpu_post_rate_change(struct meson_clk_cpu *clk_cpu,
struct clk_notifier_data *ndata)
{
u32 cpu_clk_cntl;
/* switch MUX1 to divisors' output */
cpu_clk_cntl = readl(clk_cpu->base + clk_cpu->reg_off
+ MESON_CPU_CLK_CNTL);
cpu_clk_cntl |= MESON_CPU_CLK_MUX1;
writel(cpu_clk_cntl, clk_cpu->base + clk_cpu->reg_off
+ MESON_CPU_CLK_CNTL);
udelay(100);
return 0;
}
/*
* This clock notifier is called when the frequency of the of the parent
* PLL clock is to be changed. We use the xtal input as temporary parent
* while the PLL frequency is stabilized.
*/
int meson_clk_cpu_notifier_cb(struct notifier_block *nb,
unsigned long event, void *data)
{
struct clk_notifier_data *ndata = data;
struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_nb(nb);
int ret = 0;
if (event == PRE_RATE_CHANGE)
ret = meson_clk_cpu_pre_rate_change(clk_cpu, ndata);
else if (event == POST_RATE_CHANGE)
ret = meson_clk_cpu_post_rate_change(clk_cpu, ndata);
return notifier_from_errno(ret);
}
const struct clk_ops meson_clk_cpu_ops = {
.recalc_rate = meson_clk_cpu_recalc_rate,
.round_rate = meson_clk_cpu_round_rate,
.set_rate = meson_clk_cpu_set_rate,
};

View File

@ -68,11 +68,15 @@
#define N2_MIN 4
#define N2_MAX 511
#define to_meson_clk_mpll(_hw) container_of(_hw, struct meson_clk_mpll, hw)
static inline struct meson_clk_mpll_data *
meson_clk_mpll_data(struct clk_regmap *clk)
{
return (struct meson_clk_mpll_data *)clk->data;
}
static long rate_from_params(unsigned long parent_rate,
unsigned long sdm,
unsigned long n2)
unsigned int sdm,
unsigned int n2)
{
unsigned long divisor = (SDM_DEN * n2) + sdm;
@ -84,8 +88,8 @@ static long rate_from_params(unsigned long parent_rate,
static void params_from_rate(unsigned long requested_rate,
unsigned long parent_rate,
unsigned long *sdm,
unsigned long *n2)
unsigned int *sdm,
unsigned int *n2)
{
uint64_t div = parent_rate;
unsigned long rem = do_div(div, requested_rate);
@ -105,31 +109,23 @@ static void params_from_rate(unsigned long requested_rate,
static unsigned long mpll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct meson_clk_mpll *mpll = to_meson_clk_mpll(hw);
struct parm *p;
unsigned long reg, sdm, n2;
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_mpll_data *mpll = meson_clk_mpll_data(clk);
unsigned int sdm, n2;
long rate;
p = &mpll->sdm;
reg = readl(mpll->base + p->reg_off);
sdm = PARM_GET(p->width, p->shift, reg);
p = &mpll->n2;
reg = readl(mpll->base + p->reg_off);
n2 = PARM_GET(p->width, p->shift, reg);
sdm = meson_parm_read(clk->map, &mpll->sdm);
n2 = meson_parm_read(clk->map, &mpll->n2);
rate = rate_from_params(parent_rate, sdm, n2);
if (rate < 0)
return 0;
return rate;
return rate < 0 ? 0 : rate;
}
static long mpll_round_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long *parent_rate)
{
unsigned long sdm, n2;
unsigned int sdm, n2;
params_from_rate(rate, *parent_rate, &sdm, &n2);
return rate_from_params(*parent_rate, sdm, n2);
@ -139,9 +135,9 @@ static int mpll_set_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate)
{
struct meson_clk_mpll *mpll = to_meson_clk_mpll(hw);
struct parm *p;
unsigned long reg, sdm, n2;
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_mpll_data *mpll = meson_clk_mpll_data(clk);
unsigned int sdm, n2;
unsigned long flags = 0;
params_from_rate(rate, parent_rate, &sdm, &n2);
@ -151,27 +147,20 @@ static int mpll_set_rate(struct clk_hw *hw,
else
__acquire(mpll->lock);
p = &mpll->sdm;
reg = readl(mpll->base + p->reg_off);
reg = PARM_SET(p->width, p->shift, reg, sdm);
writel(reg, mpll->base + p->reg_off);
/* Enable and set the fractional part */
meson_parm_write(clk->map, &mpll->sdm, sdm);
meson_parm_write(clk->map, &mpll->sdm_en, 1);
p = &mpll->sdm_en;
reg = readl(mpll->base + p->reg_off);
reg = PARM_SET(p->width, p->shift, reg, 1);
writel(reg, mpll->base + p->reg_off);
/* Set additional fractional part enable if required */
if (MESON_PARM_APPLICABLE(&mpll->ssen))
meson_parm_write(clk->map, &mpll->ssen, 1);
p = &mpll->ssen;
if (p->width != 0) {
reg = readl(mpll->base + p->reg_off);
reg = PARM_SET(p->width, p->shift, reg, 1);
writel(reg, mpll->base + p->reg_off);
}
/* Set the integer divider part */
meson_parm_write(clk->map, &mpll->n2, n2);
p = &mpll->n2;
reg = readl(mpll->base + p->reg_off);
reg = PARM_SET(p->width, p->shift, reg, n2);
writel(reg, mpll->base + p->reg_off);
/* Set the magic misc bit if required */
if (MESON_PARM_APPLICABLE(&mpll->misc))
meson_parm_write(clk->map, &mpll->misc, 1);
if (mpll->lock)
spin_unlock_irqrestore(mpll->lock, flags);
@ -181,67 +170,13 @@ static int mpll_set_rate(struct clk_hw *hw,
return 0;
}
static void mpll_enable_core(struct clk_hw *hw, int enable)
{
struct meson_clk_mpll *mpll = to_meson_clk_mpll(hw);
struct parm *p;
unsigned long reg;
unsigned long flags = 0;
if (mpll->lock)
spin_lock_irqsave(mpll->lock, flags);
else
__acquire(mpll->lock);
p = &mpll->en;
reg = readl(mpll->base + p->reg_off);
reg = PARM_SET(p->width, p->shift, reg, enable ? 1 : 0);
writel(reg, mpll->base + p->reg_off);
if (mpll->lock)
spin_unlock_irqrestore(mpll->lock, flags);
else
__release(mpll->lock);
}
static int mpll_enable(struct clk_hw *hw)
{
mpll_enable_core(hw, 1);
return 0;
}
static void mpll_disable(struct clk_hw *hw)
{
mpll_enable_core(hw, 0);
}
static int mpll_is_enabled(struct clk_hw *hw)
{
struct meson_clk_mpll *mpll = to_meson_clk_mpll(hw);
struct parm *p;
unsigned long reg;
int en;
p = &mpll->en;
reg = readl(mpll->base + p->reg_off);
en = PARM_GET(p->width, p->shift, reg);
return en;
}
const struct clk_ops meson_clk_mpll_ro_ops = {
.recalc_rate = mpll_recalc_rate,
.round_rate = mpll_round_rate,
.is_enabled = mpll_is_enabled,
};
const struct clk_ops meson_clk_mpll_ops = {
.recalc_rate = mpll_recalc_rate,
.round_rate = mpll_round_rate,
.set_rate = mpll_set_rate,
.enable = mpll_enable,
.disable = mpll_disable,
.is_enabled = mpll_is_enabled,
};

View File

@ -2,6 +2,9 @@
* Copyright (c) 2015 Endless Mobile, Inc.
* Author: Carlo Caione <carlo@endlessm.com>
*
* Copyright (c) 2018 Baylibre, SAS.
* Author: Jerome Brunet <jbrunet@baylibre.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
@ -27,13 +30,14 @@
* | |
* FREF VCO
*
* out = (in * M / N) >> OD
* out = in * (m + frac / frac_max) / (n << sum(ods))
*/
#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 <linux/of_address.h>
#include <linux/slab.h>
@ -41,209 +45,213 @@
#include "clkc.h"
#define MESON_PLL_RESET BIT(29)
#define MESON_PLL_LOCK BIT(31)
static inline struct meson_clk_pll_data *
meson_clk_pll_data(struct clk_regmap *clk)
{
return (struct meson_clk_pll_data *)clk->data;
}
#define to_meson_clk_pll(_hw) container_of(_hw, struct meson_clk_pll, hw)
static unsigned long __pll_params_to_rate(unsigned long parent_rate,
const struct pll_rate_table *pllt,
u16 frac,
struct meson_clk_pll_data *pll)
{
u64 rate = (u64)parent_rate * pllt->m;
unsigned int od = pllt->od + pllt->od2 + pllt->od3;
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, pllt->n << od);
}
static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct meson_clk_pll *pll = to_meson_clk_pll(hw);
struct parm *p;
unsigned long parent_rate_mhz = parent_rate / 1000000;
unsigned long rate_mhz;
u16 n, m, frac = 0, od, od2 = 0;
u32 reg;
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
struct pll_rate_table pllt;
u16 frac;
p = &pll->n;
reg = readl(pll->base + p->reg_off);
n = PARM_GET(p->width, p->shift, reg);
pllt.n = meson_parm_read(clk->map, &pll->n);
pllt.m = meson_parm_read(clk->map, &pll->m);
pllt.od = meson_parm_read(clk->map, &pll->od);
p = &pll->m;
reg = readl(pll->base + p->reg_off);
m = PARM_GET(p->width, p->shift, reg);
pllt.od2 = MESON_PARM_APPLICABLE(&pll->od2) ?
meson_parm_read(clk->map, &pll->od2) :
0;
p = &pll->od;
reg = readl(pll->base + p->reg_off);
od = PARM_GET(p->width, p->shift, reg);
pllt.od3 = MESON_PARM_APPLICABLE(&pll->od3) ?
meson_parm_read(clk->map, &pll->od3) :
0;
p = &pll->od2;
if (p->width) {
reg = readl(pll->base + p->reg_off);
od2 = PARM_GET(p->width, p->shift, reg);
frac = MESON_PARM_APPLICABLE(&pll->frac) ?
meson_parm_read(clk->map, &pll->frac) :
0;
return __pll_params_to_rate(parent_rate, &pllt, frac, pll);
}
static u16 __pll_params_with_frac(unsigned long rate,
unsigned long parent_rate,
const struct pll_rate_table *pllt,
struct meson_clk_pll_data *pll)
{
u16 frac_max = (1 << pll->frac.width);
u64 val = (u64)rate * pllt->n;
val <<= pllt->od + pllt->od2 + pllt->od3;
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 -= pllt->m * frac_max;
return min((u16)val, (u16)(frac_max - 1));
}
static const struct pll_rate_table *
meson_clk_get_pll_settings(unsigned long rate,
struct meson_clk_pll_data *pll)
{
const struct pll_rate_table *table = pll->table;
unsigned int i = 0;
if (!table)
return NULL;
/* Find the first table element exceeding rate */
while (table[i].rate && table[i].rate <= rate)
i++;
if (i != 0) {
if (MESON_PARM_APPLICABLE(&pll->frac) ||
!(pll->flags & CLK_MESON_PLL_ROUND_CLOSEST) ||
(abs(rate - table[i - 1].rate) <
abs(rate - table[i].rate)))
i--;
}
p = &pll->frac;
if (p->width) {
reg = readl(pll->base + p->reg_off);
frac = PARM_GET(p->width, p->shift, reg);
rate_mhz = (parent_rate_mhz * m + \
(parent_rate_mhz * frac >> 12)) * 2 / n;
rate_mhz = rate_mhz >> od >> od2;
} else
rate_mhz = (parent_rate_mhz * m / n) >> od >> od2;
return rate_mhz * 1000000;
return (struct pll_rate_table *)&table[i];
}
static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct meson_clk_pll *pll = to_meson_clk_pll(hw);
const struct pll_rate_table *rate_table = pll->rate_table;
int i;
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
const struct pll_rate_table *pllt =
meson_clk_get_pll_settings(rate, pll);
u16 frac;
for (i = 0; i < pll->rate_count; i++) {
if (rate <= rate_table[i].rate)
return rate_table[i].rate;
}
if (!pllt)
return meson_clk_pll_recalc_rate(hw, *parent_rate);
/* else return the smallest value */
return rate_table[0].rate;
if (!MESON_PARM_APPLICABLE(&pll->frac)
|| rate == pllt->rate)
return pllt->rate;
/*
* 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(rate, *parent_rate, pllt, pll);
return __pll_params_to_rate(*parent_rate, pllt, frac, pll);
}
static const struct pll_rate_table *meson_clk_get_pll_settings(struct meson_clk_pll *pll,
unsigned long rate)
{
const struct pll_rate_table *rate_table = pll->rate_table;
int i;
for (i = 0; i < pll->rate_count; i++) {
if (rate == rate_table[i].rate)
return &rate_table[i];
}
return NULL;
}
/* Specific wait loop for GXL/GXM GP0 PLL */
static int meson_clk_pll_wait_lock_reset(struct meson_clk_pll *pll,
struct parm *p_n)
{
int delay = 100;
u32 reg;
while (delay > 0) {
reg = readl(pll->base + p_n->reg_off);
writel(reg | MESON_PLL_RESET, pll->base + p_n->reg_off);
udelay(10);
writel(reg & ~MESON_PLL_RESET, pll->base + p_n->reg_off);
/* This delay comes from AMLogic tree clk-gp0-gxl driver */
mdelay(1);
reg = readl(pll->base + p_n->reg_off);
if (reg & MESON_PLL_LOCK)
return 0;
delay--;
}
return -ETIMEDOUT;
}
static int meson_clk_pll_wait_lock(struct meson_clk_pll *pll,
struct parm *p_n)
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 = 24000000;
u32 reg;
while (delay > 0) {
reg = readl(pll->base + p_n->reg_off);
if (reg & MESON_PLL_LOCK)
do {
/* Is the clock locked now ? */
if (meson_parm_read(clk->map, &pll->l))
return 0;
delay--;
}
} while (delay > 0);
return -ETIMEDOUT;
}
static void meson_clk_pll_init_params(struct meson_clk_pll *pll)
static void meson_clk_pll_init(struct clk_hw *hw)
{
int i;
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
for (i = 0 ; i < pll->params.params_count ; ++i)
writel(pll->params.params_table[i].value,
pll->base + pll->params.params_table[i].reg_off);
if (pll->init_count) {
meson_parm_write(clk->map, &pll->rst, 1);
regmap_multi_reg_write(clk->map, pll->init_regs,
pll->init_count);
meson_parm_write(clk->map, &pll->rst, 0);
}
}
static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct meson_clk_pll *pll = to_meson_clk_pll(hw);
struct parm *p;
const struct pll_rate_table *rate_set;
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
const struct pll_rate_table *pllt;
unsigned long old_rate;
int ret = 0;
u32 reg;
u16 frac = 0;
if (parent_rate == 0 || rate == 0)
return -EINVAL;
old_rate = rate;
rate_set = meson_clk_get_pll_settings(pll, rate);
if (!rate_set)
pllt = meson_clk_get_pll_settings(rate, pll);
if (!pllt)
return -EINVAL;
/* Initialize the PLL in a clean state if specified */
if (pll->params.params_count)
meson_clk_pll_init_params(pll);
/* Put the pll in reset to write the params */
meson_parm_write(clk->map, &pll->rst, 1);
/* PLL reset */
p = &pll->n;
reg = readl(pll->base + p->reg_off);
/* If no_init_reset is provided, avoid resetting at this point */
if (!pll->params.no_init_reset)
writel(reg | MESON_PLL_RESET, pll->base + p->reg_off);
meson_parm_write(clk->map, &pll->n, pllt->n);
meson_parm_write(clk->map, &pll->m, pllt->m);
meson_parm_write(clk->map, &pll->od, pllt->od);
reg = PARM_SET(p->width, p->shift, reg, rate_set->n);
writel(reg, pll->base + p->reg_off);
if (MESON_PARM_APPLICABLE(&pll->od2))
meson_parm_write(clk->map, &pll->od2, pllt->od2);
p = &pll->m;
reg = readl(pll->base + p->reg_off);
reg = PARM_SET(p->width, p->shift, reg, rate_set->m);
writel(reg, pll->base + p->reg_off);
if (MESON_PARM_APPLICABLE(&pll->od3))
meson_parm_write(clk->map, &pll->od3, pllt->od3);
p = &pll->od;
reg = readl(pll->base + p->reg_off);
reg = PARM_SET(p->width, p->shift, reg, rate_set->od);
writel(reg, pll->base + p->reg_off);
p = &pll->od2;
if (p->width) {
reg = readl(pll->base + p->reg_off);
reg = PARM_SET(p->width, p->shift, reg, rate_set->od2);
writel(reg, pll->base + p->reg_off);
if (MESON_PARM_APPLICABLE(&pll->frac)) {
frac = __pll_params_with_frac(rate, parent_rate, pllt, pll);
meson_parm_write(clk->map, &pll->frac, frac);
}
p = &pll->frac;
if (p->width) {
reg = readl(pll->base + p->reg_off);
reg = PARM_SET(p->width, p->shift, reg, rate_set->frac);
writel(reg, pll->base + p->reg_off);
}
/* make sure the reset is cleared at this point */
meson_parm_write(clk->map, &pll->rst, 0);
p = &pll->n;
/* If clear_reset_for_lock is provided, remove the reset bit here */
if (pll->params.clear_reset_for_lock) {
reg = readl(pll->base + p->reg_off);
writel(reg & ~MESON_PLL_RESET, pll->base + p->reg_off);
}
/* If reset_lock_loop, use a special loop including resetting */
if (pll->params.reset_lock_loop)
ret = meson_clk_pll_wait_lock_reset(pll, p);
else
ret = meson_clk_pll_wait_lock(pll, p);
if (ret) {
if (meson_clk_pll_wait_lock(hw)) {
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;
return 0;
}
const struct clk_ops meson_clk_pll_ops = {
.init = meson_clk_pll_init,
.recalc_rate = meson_clk_pll_recalc_rate,
.round_rate = meson_clk_pll_round_rate,
.set_rate = meson_clk_pll_set_rate,

View File

@ -0,0 +1,166 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#include "clk-regmap.h"
static int clk_regmap_gate_endisable(struct clk_hw *hw, int enable)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_gate_data *gate = clk_get_regmap_gate_data(clk);
int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0;
set ^= enable;
return regmap_update_bits(clk->map, gate->offset, BIT(gate->bit_idx),
set ? BIT(gate->bit_idx) : 0);
}
static int clk_regmap_gate_enable(struct clk_hw *hw)
{
return clk_regmap_gate_endisable(hw, 1);
}
static void clk_regmap_gate_disable(struct clk_hw *hw)
{
clk_regmap_gate_endisable(hw, 0);
}
static int clk_regmap_gate_is_enabled(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_gate_data *gate = clk_get_regmap_gate_data(clk);
unsigned int val;
regmap_read(clk->map, gate->offset, &val);
if (gate->flags & CLK_GATE_SET_TO_DISABLE)
val ^= BIT(gate->bit_idx);
val &= BIT(gate->bit_idx);
return val ? 1 : 0;
}
const struct clk_ops clk_regmap_gate_ops = {
.enable = clk_regmap_gate_enable,
.disable = clk_regmap_gate_disable,
.is_enabled = clk_regmap_gate_is_enabled,
};
EXPORT_SYMBOL_GPL(clk_regmap_gate_ops);
static unsigned long clk_regmap_div_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
unsigned int val;
int ret;
ret = regmap_read(clk->map, div->offset, &val);
if (ret)
/* Gives a hint that something is wrong */
return 0;
val >>= div->shift;
val &= clk_div_mask(div->width);
return divider_recalc_rate(hw, prate, val, div->table, div->flags,
div->width);
}
static long clk_regmap_div_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
unsigned int val;
int ret;
/* if read only, just return current value */
if (div->flags & CLK_DIVIDER_READ_ONLY) {
ret = regmap_read(clk->map, div->offset, &val);
if (ret)
/* Gives a hint that something is wrong */
return 0;
val >>= div->shift;
val &= clk_div_mask(div->width);
return divider_ro_round_rate(hw, rate, prate, div->table,
div->width, div->flags, val);
}
return divider_round_rate(hw, rate, prate, div->table, div->width,
div->flags);
}
static int clk_regmap_div_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_div_data *div = clk_get_regmap_div_data(clk);
unsigned int val;
int ret;
ret = divider_get_val(rate, parent_rate, div->table, div->width,
div->flags);
if (ret < 0)
return ret;
val = (unsigned int)ret << div->shift;
return regmap_update_bits(clk->map, div->offset,
clk_div_mask(div->width) << div->shift, val);
};
/* Would prefer clk_regmap_div_ro_ops but clashes with qcom */
const struct clk_ops clk_regmap_divider_ops = {
.recalc_rate = clk_regmap_div_recalc_rate,
.round_rate = clk_regmap_div_round_rate,
.set_rate = clk_regmap_div_set_rate,
};
EXPORT_SYMBOL_GPL(clk_regmap_divider_ops);
const struct clk_ops clk_regmap_divider_ro_ops = {
.recalc_rate = clk_regmap_div_recalc_rate,
.round_rate = clk_regmap_div_round_rate,
};
EXPORT_SYMBOL_GPL(clk_regmap_divider_ro_ops);
static u8 clk_regmap_mux_get_parent(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_mux_data *mux = clk_get_regmap_mux_data(clk);
unsigned int val;
int ret;
ret = regmap_read(clk->map, mux->offset, &val);
if (ret)
return ret;
val >>= mux->shift;
val &= mux->mask;
return clk_mux_val_to_index(hw, mux->table, mux->flags, val);
}
static int clk_regmap_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct clk_regmap_mux_data *mux = clk_get_regmap_mux_data(clk);
unsigned int val = clk_mux_index_to_val(mux->table, mux->flags, index);
return regmap_update_bits(clk->map, mux->offset,
mux->mask << mux->shift,
val << mux->shift);
}
const struct clk_ops clk_regmap_mux_ops = {
.get_parent = clk_regmap_mux_get_parent,
.set_parent = clk_regmap_mux_set_parent,
.determine_rate = __clk_mux_determine_rate,
};
EXPORT_SYMBOL_GPL(clk_regmap_mux_ops);
const struct clk_ops clk_regmap_mux_ro_ops = {
.get_parent = clk_regmap_mux_get_parent,
};
EXPORT_SYMBOL_GPL(clk_regmap_mux_ro_ops);

View File

@ -0,0 +1,111 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 BayLibre, SAS.
// Author: Jerome Brunet <jbrunet@baylibre.com>
#ifndef __CLK_REGMAP_H
#define __CLK_REGMAP_H
#include <linux/clk-provider.h>
#include <linux/regmap.h>
/**
* struct clk_regmap - regmap backed clock
*
* @hw: handle between common and hardware-specific interfaces
* @map: pointer to the regmap structure controlling the clock
* @data: data specific to the clock type
*
* Clock which is controlled by regmap backed registers. The actual type of
* of the clock is controlled by the clock_ops and data.
*/
struct clk_regmap {
struct clk_hw hw;
struct regmap *map;
void *data;
};
#define to_clk_regmap(_hw) container_of(_hw, struct clk_regmap, hw)
/**
* struct clk_regmap_gate_data - regmap backed gate specific data
*
* @offset: offset of the register controlling gate
* @bit_idx: single bit controlling gate
* @flags: hardware-specific flags
*
* Flags:
* Same as clk_gate except CLK_GATE_HIWORD_MASK which is ignored
*/
struct clk_regmap_gate_data {
unsigned int offset;
u8 bit_idx;
u8 flags;
};
static inline struct clk_regmap_gate_data *
clk_get_regmap_gate_data(struct clk_regmap *clk)
{
return (struct clk_regmap_gate_data *)clk->data;
}
extern const struct clk_ops clk_regmap_gate_ops;
/**
* struct clk_regmap_div_data - regmap backed adjustable divider specific data
*
* @offset: offset of the register controlling the divider
* @shift: shift to the divider bit field
* @width: width of the divider bit field
* @table: array of value/divider pairs, last entry should have div = 0
*
* Flags:
* Same as clk_divider except CLK_DIVIDER_HIWORD_MASK which is ignored
*/
struct clk_regmap_div_data {
unsigned int offset;
u8 shift;
u8 width;
u8 flags;
const struct clk_div_table *table;
};
static inline struct clk_regmap_div_data *
clk_get_regmap_div_data(struct clk_regmap *clk)
{
return (struct clk_regmap_div_data *)clk->data;
}
extern const struct clk_ops clk_regmap_divider_ops;
extern const struct clk_ops clk_regmap_divider_ro_ops;
/**
* struct clk_regmap_mux_data - regmap backed multiplexer clock specific data
*
* @hw: handle between common and hardware-specific interfaces
* @offset: offset of theregister controlling multiplexer
* @table: array of parent indexed register values
* @shift: shift to multiplexer bit field
* @mask: mask of mutliplexer bit field
* @flags: hardware-specific flags
*
* Flags:
* Same as clk_divider except CLK_MUX_HIWORD_MASK which is ignored
*/
struct clk_regmap_mux_data {
unsigned int offset;
u32 *table;
u32 mask;
u8 shift;
u8 flags;
};
static inline struct clk_regmap_mux_data *
clk_get_regmap_mux_data(struct clk_regmap *clk)
{
return (struct clk_regmap_mux_data *)clk->data;
}
extern const struct clk_ops clk_regmap_mux_ops;
extern const struct clk_ops clk_regmap_mux_ro_ops;
#endif /* __CLK_REGMAP_H */

View File

@ -18,6 +18,9 @@
#ifndef __CLKC_H
#define __CLKC_H
#include <linux/clk-provider.h>
#include "clk-regmap.h"
#define PMASK(width) GENMASK(width - 1, 0)
#define SETPMASK(width, shift) GENMASK(shift + width - 1, shift)
#define CLRPMASK(width, shift) (~SETPMASK(width, shift))
@ -35,13 +38,29 @@ struct parm {
u8 width;
};
static inline unsigned int meson_parm_read(struct regmap *map, struct parm *p)
{
unsigned int val;
regmap_read(map, p->reg_off, &val);
return PARM_GET(p->width, p->shift, val);
}
static inline void meson_parm_write(struct regmap *map, struct parm *p,
unsigned int val)
{
regmap_update_bits(map, p->reg_off, SETPMASK(p->width, p->shift),
val << p->shift);
}
struct pll_rate_table {
unsigned long rate;
u16 m;
u16 n;
u16 od;
u16 od2;
u16 frac;
u16 od3;
};
#define PLL_RATE(_r, _m, _n, _od) \
@ -50,97 +69,53 @@ struct pll_rate_table {
.m = (_m), \
.n = (_n), \
.od = (_od), \
} \
#define PLL_FRAC_RATE(_r, _m, _n, _od, _od2, _frac) \
{ \
.rate = (_r), \
.m = (_m), \
.n = (_n), \
.od = (_od), \
.od2 = (_od2), \
.frac = (_frac), \
} \
struct pll_params_table {
unsigned int reg_off;
unsigned int value;
};
#define PLL_PARAM(_reg, _val) \
{ \
.reg_off = (_reg), \
.value = (_val), \
}
struct pll_setup_params {
struct pll_params_table *params_table;
unsigned int params_count;
/* Workaround for GP0, do not reset before configuring */
bool no_init_reset;
/* Workaround for GP0, unreset right before checking for lock */
bool clear_reset_for_lock;
/* Workaround for GXL GP0, reset in the lock checking loop */
bool reset_lock_loop;
};
#define CLK_MESON_PLL_ROUND_CLOSEST BIT(0)
struct meson_clk_pll {
struct clk_hw hw;
void __iomem *base;
struct meson_clk_pll_data {
struct parm m;
struct parm n;
struct parm frac;
struct parm od;
struct parm od2;
const struct pll_setup_params params;
const struct pll_rate_table *rate_table;
unsigned int rate_count;
spinlock_t *lock;
struct parm od3;
struct parm l;
struct parm rst;
const struct reg_sequence *init_regs;
unsigned int init_count;
const struct pll_rate_table *table;
u8 flags;
};
#define to_meson_clk_pll(_hw) container_of(_hw, struct meson_clk_pll, hw)
struct meson_clk_cpu {
struct clk_hw hw;
void __iomem *base;
u16 reg_off;
struct notifier_block clk_nb;
const struct clk_div_table *div_table;
};
int meson_clk_cpu_notifier_cb(struct notifier_block *nb, unsigned long event,
void *data);
struct meson_clk_mpll {
struct clk_hw hw;
void __iomem *base;
struct meson_clk_mpll_data {
struct parm sdm;
struct parm sdm_en;
struct parm n2;
struct parm en;
struct parm ssen;
struct parm misc;
spinlock_t *lock;
};
struct meson_clk_audio_divider {
struct clk_hw hw;
void __iomem *base;
struct meson_clk_audio_div_data {
struct parm div;
u8 flags;
spinlock_t *lock;
};
#define MESON_GATE(_name, _reg, _bit) \
struct clk_gate _name = { \
.reg = (void __iomem *) _reg, \
.bit_idx = (_bit), \
.lock = &meson_clk_lock, \
.hw.init = &(struct clk_init_data) { \
.name = #_name, \
.ops = &clk_gate_ops, \
struct clk_regmap _name = { \
.data = &(struct clk_regmap_gate_data){ \
.offset = (_reg), \
.bit_idx = (_bit), \
}, \
.hw.init = &(struct clk_init_data) { \
.name = #_name, \
.ops = &clk_regmap_gate_ops, \
.parent_names = (const char *[]){ "clk81" }, \
.num_parents = 1, \
.flags = (CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED), \
.flags = (CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED), \
}, \
};

View File

@ -1,46 +0,0 @@
/*
* Copyright (c) 2017 BayLibre, SAS.
* Author: Neil Armstrong <narmstrong@baylibre.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <linux/clk-provider.h>
#include <linux/bitfield.h>
#include <linux/regmap.h>
#include "gxbb-aoclk.h"
static int aoclk_gate_regmap_enable(struct clk_hw *hw)
{
struct aoclk_gate_regmap *gate = to_aoclk_gate_regmap(hw);
return regmap_update_bits(gate->regmap, AO_RTI_GEN_CNTL_REG0,
BIT(gate->bit_idx), BIT(gate->bit_idx));
}
static void aoclk_gate_regmap_disable(struct clk_hw *hw)
{
struct aoclk_gate_regmap *gate = to_aoclk_gate_regmap(hw);
regmap_update_bits(gate->regmap, AO_RTI_GEN_CNTL_REG0,
BIT(gate->bit_idx), 0);
}
static int aoclk_gate_regmap_is_enabled(struct clk_hw *hw)
{
struct aoclk_gate_regmap *gate = to_aoclk_gate_regmap(hw);
unsigned int val;
int ret;
ret = regmap_read(gate->regmap, AO_RTI_GEN_CNTL_REG0, &val);
if (ret)
return ret;
return (val & BIT(gate->bit_idx)) != 0;
}
const struct clk_ops meson_aoclk_gate_regmap_ops = {
.enable = aoclk_gate_regmap_enable,
.disable = aoclk_gate_regmap_disable,
.is_enabled = aoclk_gate_regmap_is_enabled,
};

View File

@ -62,10 +62,9 @@
#include <linux/delay.h>
#include <dt-bindings/clock/gxbb-aoclkc.h>
#include <dt-bindings/reset/gxbb-aoclkc.h>
#include "clk-regmap.h"
#include "gxbb-aoclk.h"
static DEFINE_SPINLOCK(gxbb_aoclk_lock);
struct gxbb_aoclk_reset_controller {
struct reset_controller_dev reset;
unsigned int *data;
@ -87,12 +86,14 @@ static const struct reset_control_ops gxbb_aoclk_reset_ops = {
};
#define GXBB_AO_GATE(_name, _bit) \
static struct aoclk_gate_regmap _name##_ao = { \
.bit_idx = (_bit), \
.lock = &gxbb_aoclk_lock, \
static struct clk_regmap _name##_ao = { \
.data = &(struct clk_regmap_gate_data) { \
.offset = AO_RTI_GEN_CNTL_REG0, \
.bit_idx = (_bit), \
}, \
.hw.init = &(struct clk_init_data) { \
.name = #_name "_ao", \
.ops = &meson_aoclk_gate_regmap_ops, \
.ops = &clk_regmap_gate_ops, \
.parent_names = (const char *[]){ "clk81" }, \
.num_parents = 1, \
.flags = (CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED), \
@ -107,7 +108,6 @@ GXBB_AO_GATE(uart2, 5);
GXBB_AO_GATE(ir_blaster, 6);
static struct aoclk_cec_32k cec_32k_ao = {
.lock = &gxbb_aoclk_lock,
.hw.init = &(struct clk_init_data) {
.name = "cec_32k_ao",
.ops = &meson_aoclk_cec_32k_ops,
@ -126,7 +126,7 @@ static unsigned int gxbb_aoclk_reset[] = {
[RESET_AO_IR_BLASTER] = 23,
};
static struct aoclk_gate_regmap *gxbb_aoclk_gate[] = {
static struct clk_regmap *gxbb_aoclk_gate[] = {
[CLKID_AO_REMOTE] = &remote_ao,
[CLKID_AO_I2C_MASTER] = &i2c_master_ao,
[CLKID_AO_I2C_SLAVE] = &i2c_slave_ao,
@ -177,10 +177,10 @@ static int gxbb_aoclkc_probe(struct platform_device *pdev)
* Populate regmap and register all clks
*/
for (clkid = 0; clkid < ARRAY_SIZE(gxbb_aoclk_gate); clkid++) {
gxbb_aoclk_gate[clkid]->regmap = regmap;
gxbb_aoclk_gate[clkid]->map = regmap;
ret = devm_clk_hw_register(dev,
gxbb_aoclk_onecell_data.hws[clkid]);
gxbb_aoclk_onecell_data.hws[clkid]);
if (ret)
return ret;
}

View File

@ -17,22 +17,11 @@
#define AO_RTC_ALT_CLK_CNTL0 0x94
#define AO_RTC_ALT_CLK_CNTL1 0x98
struct aoclk_gate_regmap {
struct clk_hw hw;
unsigned bit_idx;
struct regmap *regmap;
spinlock_t *lock;
};
#define to_aoclk_gate_regmap(_hw) \
container_of(_hw, struct aoclk_gate_regmap, hw)
extern const struct clk_ops meson_aoclk_gate_regmap_ops;
struct aoclk_cec_32k {
struct clk_hw hw;
struct regmap *regmap;
spinlock_t *lock;
};
#define to_aoclk_cec_32k(_hw) container_of(_hw, struct aoclk_cec_32k, hw)

File diff suppressed because it is too large Load Diff

View File

@ -194,8 +194,18 @@
#define CLKID_VPU_1_DIV 130
#define CLKID_VAPB_0_DIV 134
#define CLKID_VAPB_1_DIV 137
#define CLKID_HDMI_PLL_PRE_MULT 141
#define CLKID_MPLL0_DIV 142
#define CLKID_MPLL1_DIV 143
#define CLKID_MPLL2_DIV 144
#define CLKID_MPLL_PREDIV 145
#define CLKID_FCLK_DIV2_DIV 146
#define CLKID_FCLK_DIV3_DIV 147
#define CLKID_FCLK_DIV4_DIV 148
#define CLKID_FCLK_DIV5_DIV 149
#define CLKID_FCLK_DIV7_DIV 150
#define NR_CLKS 141
#define NR_CLKS 151
/* include the CLKIDs that have been made part of the DT binding */
#include <dt-bindings/clock/gxbb-clkc.h>

File diff suppressed because it is too large Load Diff

View File

@ -69,7 +69,22 @@
* will remain defined here.
*/
#define CLK_NR_CLKS 96
#define CLKID_MPLL0_DIV 96
#define CLKID_MPLL1_DIV 97
#define CLKID_MPLL2_DIV 98
#define CLKID_CPU_IN_SEL 99
#define CLKID_CPU_DIV2 100
#define CLKID_CPU_DIV3 101
#define CLKID_CPU_SCALE_DIV 102
#define CLKID_CPU_SCALE_OUT_SEL 103
#define CLKID_MPLL_PREDIV 104
#define CLKID_FCLK_DIV2_DIV 105
#define CLKID_FCLK_DIV3_DIV 106
#define CLKID_FCLK_DIV4_DIV 107
#define CLKID_FCLK_DIV5_DIV 108
#define CLKID_FCLK_DIV7_DIV 109
#define CLK_NR_CLKS 110
/*
* include the CLKID and RESETID that have

View File

@ -28,22 +28,14 @@ static long div_round_ro_rate(struct clk_hw *hw, unsigned long rate,
{
struct clk_regmap_div *divider = to_clk_regmap_div(hw);
struct clk_regmap *clkr = &divider->clkr;
u32 div;
struct clk_hw *hw_parent = clk_hw_get_parent(hw);
u32 val;
regmap_read(clkr->regmap, divider->reg, &div);
div >>= divider->shift;
div &= BIT(divider->width) - 1;
div += 1;
regmap_read(clkr->regmap, divider->reg, &val);
val >>= divider->shift;
val &= BIT(divider->width) - 1;
if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
if (!hw_parent)
return -EINVAL;
*prate = clk_hw_round_rate(hw_parent, rate * div);
}
return DIV_ROUND_UP_ULL((u64)*prate, div);
return divider_ro_round_rate(hw, rate, prate, NULL, divider->width,
CLK_DIVIDER_ROUND_CLOSEST, val);
}
static long div_round_rate(struct clk_hw *hw, unsigned long rate,

View File

@ -67,5 +67,6 @@
#define CLKID_AO_I2C 58
#define CLKID_SD_EMMC_B_CLK0 59
#define CLKID_SD_EMMC_C_CLK0 60
#define CLKID_HIFI_PLL 69
#endif /* __AXG_CLKC_H */

View File

@ -399,6 +399,7 @@ struct clk_divider {
spinlock_t *lock;
};
#define clk_div_mask(width) ((1 << (width)) - 1)
#define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw)
#define CLK_DIVIDER_ONE_BASED BIT(0)
@ -419,6 +420,10 @@ long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
unsigned long rate, unsigned long *prate,
const struct clk_div_table *table,
u8 width, unsigned long flags);
long divider_ro_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
unsigned long rate, unsigned long *prate,
const struct clk_div_table *table, u8 width,
unsigned long flags, unsigned int val);
int divider_get_val(unsigned long rate, unsigned long parent_rate,
const struct clk_div_table *table, u8 width,
unsigned long flags);
@ -449,8 +454,9 @@ void clk_hw_unregister_divider(struct clk_hw *hw);
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register controlling multiplexer
* @table: array of register values corresponding to the parent index
* @shift: shift to multiplexer bit field
* @width: width of mutliplexer bit field
* @mask: mask of mutliplexer bit field
* @flags: hardware-specific flags
* @lock: register lock
*
@ -510,6 +516,10 @@ struct clk_hw *clk_hw_register_mux_table(struct device *dev, const char *name,
void __iomem *reg, u8 shift, u32 mask,
u8 clk_mux_flags, u32 *table, spinlock_t *lock);
int clk_mux_val_to_index(struct clk_hw *hw, u32 *table, unsigned int flags,
unsigned int val);
unsigned int clk_mux_index_to_val(u32 *table, unsigned int flags, u8 index);
void clk_unregister_mux(struct clk *clk);
void clk_hw_unregister_mux(struct clk_hw *hw);
@ -774,6 +784,17 @@ static inline long divider_round_rate(struct clk_hw *hw, unsigned long rate,
rate, prate, table, width, flags);
}
static inline long divider_ro_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate,
const struct clk_div_table *table,
u8 width, unsigned long flags,
unsigned int val)
{
return divider_ro_round_rate_parent(hw, clk_hw_get_parent(hw),
rate, prate, table, width, flags,
val);
}
/*
* FIXME clock api without lock protection
*/