linux/drivers/clk/tegra/clk-tegra210-emc.c
Linus Torvalds 3fec0eaaf0 This pull request contains zero diff 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
 -----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
  ...
2020-10-22 12:53:28 -07:00

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);