mirror of
https://github.com/torvalds/linux.git
synced 2024-12-26 04:42:12 +00:00
3fec0eaaf0
of various clk driver updates. The biggest driver updates in terms of lines of code is the Allwinner driver, closely followed by the Qualcomm and Mediatek drivers. All of those hit high because we add so many lines of clk data. Coming in fourth place is i.MX which also adds a bunch of clk data. This accounts for the new driver additions this time around. Otherwise the patches are lots of little cleanups and fixes for various clk drivers that have baked in linux-next for a while. I suppose one highlight or theme is that more clk drivers are being updated to work as modules, which is interesting to see such critical SoC infrastructure work as a loadable module. New Drivers: - Support qcom SM8150/SM8250 video and display clks - Support Mediatek MT8167 clks - Add clock for CRC block found on vf610 SoCs - Add support for the Renesas R-Car V3U (R8A779A0) SoC - Add support for the VSP for Resizing clock on Renesas RZ/G1H - Support Allwinner A100 SoC clks Removed Drivers: - Remove i.MX21 clock driver, as i.MX21 platform support is being dropped Updates: - Change how qcom's display port clks work - Small non-critical fixes for TI clk driver - Remove various unused variables in clk drivers - Allow Rockchip clk driver to be a module - Remove most __clk_lookup() calls in Samsung drivers (yay!) - Support building i.MX ARMv8 platforms clock driver as module - Some kerneldoc fixes here and there - A couple of minor i.MX clk data corrections - Update audio clock inverter and fdiv2 flag on Amlogic g12 - Make amlogic clk drivers configurable in Kconfig - Fix Renesas VSP clock names to match corrected hardware documentation - Sigma-delta modulation on Allwinner R40 - Various fixes for at91 clk driver - Use semicolons instead of commas in some places - Mark some variables const so they can move to RO memory -----BEGIN PGP SIGNATURE----- iQJFBAABCAAvFiEE9L57QeeUxqYDyoaDrQKIl8bklSUFAl+R0K0RHHNib3lkQGtl cm5lbC5vcmcACgkQrQKIl8bklSU9xw/+KRDZ/xo7GKeC7QrRt4q5eWIW4l/HzjYH yeht/i7cEXy+jSJuOTBj4sIpOdvzdBQfsqMiNg7RKdtYs0HbYFywxWtnvetptuM1 BCgSMDHHJ59EJSPEWAvE6bsl9xaVl4o0XEI2+qAoJ4OIcJVzVz+vRGQ7pDyEk2XT zTXRw4W+HftZXFB8Nw0JTj9YzBoZJzpnAB/vu2HzMYVAvoeQ8RhcdbipdSTjI+zY ++zkt8cmGP0iKloHbi3rk1A7w/ORJ//UjT24xmkwAO6t1CWEErVzXBtGkQ9K4ijy F2w5CzJb/szGCfnGlcchQ5kjB/FfgIKuLNlhTiptY+UZGIvSAbndhMSS3JFsqGbl aYUk5fpjdpneSsIPvHnnz1jIaK6OmHSoxmq7FgwaU+YDX6ZK6UKalMHbHUEpiNX+ 3a+FeKe2IVMZ0uVqpJGnd/o4Kud2CeRM1ufqu15ygbujfSH6xcO7fbUi/C8XJLMX 7PR0Ze0PhwkMezdlxb3WpK+4MrOny3JT0DTAbAQDdAwsKFP/Sex4QMWP8PUdTmGY dQcrgvuXYC1hufOaY1JzxjfGrhEBuJAr7BsjBI+etUpnJ9Z5Uhguti/lnKm7oAlI EceBJ4B5M1iUYTkKXYxLWWzUlkIKNzgHvjRM2Q6Nn5LbyVlzM4i284C/E4M8AmVB nSXy1nWkSAU= =sKI8 -----END PGP SIGNATURE----- Merge tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux Pull clk updates from Stephen Boyd: "This contains no changes to the core framework. It is a collection of various clk driver updates. The biggest driver updates in terms of lines of code is the Allwinner driver, closely followed by the Qualcomm and Mediatek drivers. All of those hit high because we add so many lines of clk data. Coming in fourth place is i.MX which also adds a bunch of clk data. This accounts for the new driver additions this time around. Otherwise the patches are lots of little cleanups and fixes for various clk drivers that have baked in linux-next for a while. I suppose one highlight or theme is that more clk drivers are being updated to work as modules, which is interesting to see such critical SoC infrastructure work as a loadable module. New Drivers: - Support qcom SM8150/SM8250 video and display clks - Support Mediatek MT8167 clks - Add clock for CRC block found on vf610 SoCs - Add support for the Renesas R-Car V3U (R8A779A0) SoC - Add support for the VSP for Resizing clock on Renesas RZ/G1H - Support Allwinner A100 SoC clks Removed Drivers: - Remove i.MX21 clock driver, as i.MX21 platform support is being dropped Updates: - Change how qcom's display port clks work - Small non-critical fixes for TI clk driver - Remove various unused variables in clk drivers - Allow Rockchip clk driver to be a module - Remove most __clk_lookup() calls in Samsung drivers (yay!) - Support building i.MX ARMv8 platforms clock driver as module - Some kerneldoc fixes here and there - A couple of minor i.MX clk data corrections - Update audio clock inverter and fdiv2 flag on Amlogic g12 - Make amlogic clk drivers configurable in Kconfig - Fix Renesas VSP clock names to match corrected hardware documentation - Sigma-delta modulation on Allwinner R40 - Various fixes for at91 clk driver - Use semicolons instead of commas in some places - Mark some variables const so they can move to RO memory" * tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux: (102 commits) clk: imx8mq: Fix usdhc parents order clk: qcom: gdsc: Keep RETAIN_FF bit set if gdsc is already on clk: Restrict CLK_HSDK to ARC_SOC_HSDK clk: at91: sam9x60: support only two programmable clocks clk: ingenic: Respect CLK_SET_RATE_PARENT in .round_rate clk: ingenic: Don't tag custom clocks with CLK_SET_RATE_PARENT clk: ingenic: Don't use CLK_SET_RATE_GATE for PLL clk: ingenic: Use readl_poll_timeout instead of custom loop clk: ingenic: Use to_clk_info() macro for all clocks clk: bcm2835: add missing release if devm_clk_hw_register fails clk: at91: clk-sam9x60-pll: remove unused variable clk: at91: clk-main: update key before writing AT91_CKGR_MOR clk: at91: remove the checking of parent_name clk: clk-prima2: fix return value check in prima2_clk_init() clk: mmp2: Fix the display clock divider base clk: pxa: Constify static struct clk_ops clk: baikal-t1: Mark Ethernet PLL as critical clk: qoriq: modify MAX_PLL_DIV to 32 clk: axi-clkgen: Set power bits for fractional mode clk: axi-clkgen: Add support for fractional dividers ...
372 lines
8.9 KiB
C
372 lines
8.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2015-2020, NVIDIA CORPORATION. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/clk/tegra.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/io.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "clk.h"
|
|
|
|
#define CLK_SOURCE_EMC 0x19c
|
|
#define CLK_SOURCE_EMC_2X_CLK_SRC GENMASK(31, 29)
|
|
#define CLK_SOURCE_EMC_MC_EMC_SAME_FREQ BIT(16)
|
|
#define CLK_SOURCE_EMC_2X_CLK_DIVISOR GENMASK(7, 0)
|
|
|
|
#define CLK_SRC_PLLM 0
|
|
#define CLK_SRC_PLLC 1
|
|
#define CLK_SRC_PLLP 2
|
|
#define CLK_SRC_CLK_M 3
|
|
#define CLK_SRC_PLLM_UD 4
|
|
#define CLK_SRC_PLLMB_UD 5
|
|
#define CLK_SRC_PLLMB 6
|
|
#define CLK_SRC_PLLP_UD 7
|
|
|
|
struct tegra210_clk_emc {
|
|
struct clk_hw hw;
|
|
void __iomem *regs;
|
|
|
|
struct tegra210_clk_emc_provider *provider;
|
|
|
|
struct clk *parents[8];
|
|
};
|
|
|
|
static inline struct tegra210_clk_emc *
|
|
to_tegra210_clk_emc(struct clk_hw *hw)
|
|
{
|
|
return container_of(hw, struct tegra210_clk_emc, hw);
|
|
}
|
|
|
|
static const char *tegra210_clk_emc_parents[] = {
|
|
"pll_m", "pll_c", "pll_p", "clk_m", "pll_m_ud", "pll_mb_ud",
|
|
"pll_mb", "pll_p_ud",
|
|
};
|
|
|
|
static u8 tegra210_clk_emc_get_parent(struct clk_hw *hw)
|
|
{
|
|
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
|
|
u32 value;
|
|
u8 src;
|
|
|
|
value = readl_relaxed(emc->regs + CLK_SOURCE_EMC);
|
|
src = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, value);
|
|
|
|
return src;
|
|
}
|
|
|
|
static unsigned long tegra210_clk_emc_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
|
|
u32 value, div;
|
|
|
|
/*
|
|
* CCF assumes that neither the parent nor its rate will change during
|
|
* ->set_rate(), so the parent rate passed in here was cached from the
|
|
* parent before the ->set_rate() call.
|
|
*
|
|
* This can lead to wrong results being reported for the EMC clock if
|
|
* the parent and/or parent rate have changed as part of the EMC rate
|
|
* change sequence. Fix this by overriding the parent clock with what
|
|
* we know to be the correct value after the rate change.
|
|
*/
|
|
parent_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
|
|
|
|
value = readl_relaxed(emc->regs + CLK_SOURCE_EMC);
|
|
|
|
div = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_DIVISOR, value);
|
|
div += 2;
|
|
|
|
return DIV_ROUND_UP(parent_rate * 2, div);
|
|
}
|
|
|
|
static long tegra210_clk_emc_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *prate)
|
|
{
|
|
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
|
|
struct tegra210_clk_emc_provider *provider = emc->provider;
|
|
unsigned int i;
|
|
|
|
if (!provider || !provider->configs || provider->num_configs == 0)
|
|
return clk_hw_get_rate(hw);
|
|
|
|
for (i = 0; i < provider->num_configs; i++) {
|
|
if (provider->configs[i].rate >= rate)
|
|
return provider->configs[i].rate;
|
|
}
|
|
|
|
return provider->configs[i - 1].rate;
|
|
}
|
|
|
|
static struct clk *tegra210_clk_emc_find_parent(struct tegra210_clk_emc *emc,
|
|
u8 index)
|
|
{
|
|
struct clk_hw *parent = clk_hw_get_parent_by_index(&emc->hw, index);
|
|
const char *name = clk_hw_get_name(parent);
|
|
|
|
/* XXX implement cache? */
|
|
|
|
return __clk_lookup(name);
|
|
}
|
|
|
|
static int tegra210_clk_emc_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
|
|
struct tegra210_clk_emc_provider *provider = emc->provider;
|
|
struct tegra210_clk_emc_config *config;
|
|
struct device *dev = provider->dev;
|
|
struct clk_hw *old, *new, *parent;
|
|
u8 old_idx, new_idx, index;
|
|
struct clk *clk;
|
|
unsigned int i;
|
|
int err;
|
|
|
|
if (!provider->configs || provider->num_configs == 0)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < provider->num_configs; i++) {
|
|
if (provider->configs[i].rate >= rate) {
|
|
config = &provider->configs[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == provider->num_configs)
|
|
config = &provider->configs[i - 1];
|
|
|
|
old_idx = tegra210_clk_emc_get_parent(hw);
|
|
new_idx = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, config->value);
|
|
|
|
old = clk_hw_get_parent_by_index(hw, old_idx);
|
|
new = clk_hw_get_parent_by_index(hw, new_idx);
|
|
|
|
/* if the rate has changed... */
|
|
if (config->parent_rate != clk_hw_get_rate(old)) {
|
|
/* ... but the clock source remains the same ... */
|
|
if (new_idx == old_idx) {
|
|
/* ... switch to the alternative clock source. */
|
|
switch (new_idx) {
|
|
case CLK_SRC_PLLM:
|
|
new_idx = CLK_SRC_PLLMB;
|
|
break;
|
|
|
|
case CLK_SRC_PLLM_UD:
|
|
new_idx = CLK_SRC_PLLMB_UD;
|
|
break;
|
|
|
|
case CLK_SRC_PLLMB_UD:
|
|
new_idx = CLK_SRC_PLLM_UD;
|
|
break;
|
|
|
|
case CLK_SRC_PLLMB:
|
|
new_idx = CLK_SRC_PLLM;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* This should never happen because we can't deal with
|
|
* it.
|
|
*/
|
|
if (WARN_ON(new_idx == old_idx))
|
|
return -EINVAL;
|
|
|
|
new = clk_hw_get_parent_by_index(hw, new_idx);
|
|
}
|
|
|
|
index = new_idx;
|
|
parent = new;
|
|
} else {
|
|
index = old_idx;
|
|
parent = old;
|
|
}
|
|
|
|
clk = tegra210_clk_emc_find_parent(emc, index);
|
|
if (IS_ERR(clk)) {
|
|
err = PTR_ERR(clk);
|
|
dev_err(dev, "failed to get parent clock for index %u: %d\n",
|
|
index, err);
|
|
return err;
|
|
}
|
|
|
|
/* set the new parent clock to the required rate */
|
|
if (clk_get_rate(clk) != config->parent_rate) {
|
|
err = clk_set_rate(clk, config->parent_rate);
|
|
if (err < 0) {
|
|
dev_err(dev, "failed to set rate %lu Hz for %pC: %d\n",
|
|
config->parent_rate, clk, err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/* enable the new parent clock */
|
|
if (parent != old) {
|
|
err = clk_prepare_enable(clk);
|
|
if (err < 0) {
|
|
dev_err(dev, "failed to enable parent clock %pC: %d\n",
|
|
clk, err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/* update the EMC source configuration to reflect the new parent */
|
|
config->value &= ~CLK_SOURCE_EMC_2X_CLK_SRC;
|
|
config->value |= FIELD_PREP(CLK_SOURCE_EMC_2X_CLK_SRC, index);
|
|
|
|
/*
|
|
* Finally, switch the EMC programming with both old and new parent
|
|
* clocks enabled.
|
|
*/
|
|
err = provider->set_rate(dev, config);
|
|
if (err < 0) {
|
|
dev_err(dev, "failed to set EMC rate to %lu Hz: %d\n", rate,
|
|
err);
|
|
|
|
/*
|
|
* If we're unable to switch to the new EMC frequency, we no
|
|
* longer need the new parent to be enabled.
|
|
*/
|
|
if (parent != old)
|
|
clk_disable_unprepare(clk);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* reparent to new parent clock and disable the old parent clock */
|
|
if (parent != old) {
|
|
clk = tegra210_clk_emc_find_parent(emc, old_idx);
|
|
if (IS_ERR(clk)) {
|
|
err = PTR_ERR(clk);
|
|
dev_err(dev,
|
|
"failed to get parent clock for index %u: %d\n",
|
|
old_idx, err);
|
|
return err;
|
|
}
|
|
|
|
clk_hw_reparent(hw, parent);
|
|
clk_disable_unprepare(clk);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static const struct clk_ops tegra210_clk_emc_ops = {
|
|
.get_parent = tegra210_clk_emc_get_parent,
|
|
.recalc_rate = tegra210_clk_emc_recalc_rate,
|
|
.round_rate = tegra210_clk_emc_round_rate,
|
|
.set_rate = tegra210_clk_emc_set_rate,
|
|
};
|
|
|
|
struct clk *tegra210_clk_register_emc(struct device_node *np,
|
|
void __iomem *regs)
|
|
{
|
|
struct tegra210_clk_emc *emc;
|
|
struct clk_init_data init;
|
|
struct clk *clk;
|
|
|
|
emc = kzalloc(sizeof(*emc), GFP_KERNEL);
|
|
if (!emc)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
emc->regs = regs;
|
|
|
|
init.name = "emc";
|
|
init.ops = &tegra210_clk_emc_ops;
|
|
init.flags = CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE;
|
|
init.parent_names = tegra210_clk_emc_parents;
|
|
init.num_parents = ARRAY_SIZE(tegra210_clk_emc_parents);
|
|
emc->hw.init = &init;
|
|
|
|
clk = clk_register(NULL, &emc->hw);
|
|
if (IS_ERR(clk)) {
|
|
kfree(emc);
|
|
return clk;
|
|
}
|
|
|
|
return clk;
|
|
}
|
|
|
|
int tegra210_clk_emc_attach(struct clk *clk,
|
|
struct tegra210_clk_emc_provider *provider)
|
|
{
|
|
struct clk_hw *hw = __clk_get_hw(clk);
|
|
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
|
|
struct device *dev = provider->dev;
|
|
unsigned int i;
|
|
int err;
|
|
|
|
if (!try_module_get(provider->owner))
|
|
return -ENODEV;
|
|
|
|
for (i = 0; i < provider->num_configs; i++) {
|
|
struct tegra210_clk_emc_config *config = &provider->configs[i];
|
|
struct clk_hw *parent;
|
|
bool same_freq;
|
|
u8 div, src;
|
|
|
|
div = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_DIVISOR, config->value);
|
|
src = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, config->value);
|
|
|
|
/* do basic sanity checking on the EMC timings */
|
|
if (div & 0x1) {
|
|
dev_err(dev, "invalid odd divider %u for rate %lu Hz\n",
|
|
div, config->rate);
|
|
err = -EINVAL;
|
|
goto put;
|
|
}
|
|
|
|
same_freq = config->value & CLK_SOURCE_EMC_MC_EMC_SAME_FREQ;
|
|
|
|
if (same_freq != config->same_freq) {
|
|
dev_err(dev,
|
|
"ambiguous EMC to MC ratio for rate %lu Hz\n",
|
|
config->rate);
|
|
err = -EINVAL;
|
|
goto put;
|
|
}
|
|
|
|
parent = clk_hw_get_parent_by_index(hw, src);
|
|
config->parent = src;
|
|
|
|
if (src == CLK_SRC_PLLM || src == CLK_SRC_PLLM_UD) {
|
|
config->parent_rate = config->rate * (1 + div / 2);
|
|
} else {
|
|
unsigned long rate = config->rate * (1 + div / 2);
|
|
|
|
config->parent_rate = clk_hw_get_rate(parent);
|
|
|
|
if (config->parent_rate != rate) {
|
|
dev_err(dev,
|
|
"rate %lu Hz does not match input\n",
|
|
config->rate);
|
|
err = -EINVAL;
|
|
goto put;
|
|
}
|
|
}
|
|
}
|
|
|
|
emc->provider = provider;
|
|
|
|
return 0;
|
|
|
|
put:
|
|
module_put(provider->owner);
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tegra210_clk_emc_attach);
|
|
|
|
void tegra210_clk_emc_detach(struct clk *clk)
|
|
{
|
|
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(__clk_get_hw(clk));
|
|
|
|
module_put(emc->provider->owner);
|
|
emc->provider = NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tegra210_clk_emc_detach);
|