mirror of
https://github.com/torvalds/linux.git
synced 2024-11-15 08:31:55 +00:00
c942fddf87
Based on 3 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version this program is distributed in the hope that 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 this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] this program is distributed in the hope that 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 this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] this program is distributed in the hope that 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
377 lines
8.8 KiB
C
377 lines
8.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright 2013 Emilio López
|
|
*
|
|
* Emilio López <emilio@elopez.com.ar>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "clk-factors.h"
|
|
|
|
/**
|
|
* sun4i_a10_get_mod0_factors() - calculates m, n factors for MOD0-style clocks
|
|
* MOD0 rate is calculated as follows
|
|
* rate = (parent_rate >> p) / (m + 1);
|
|
*/
|
|
|
|
static void sun4i_a10_get_mod0_factors(struct factors_request *req)
|
|
{
|
|
u8 div, calcm, calcp;
|
|
|
|
/* These clocks can only divide, so we will never be able to achieve
|
|
* frequencies higher than the parent frequency */
|
|
if (req->rate > req->parent_rate)
|
|
req->rate = req->parent_rate;
|
|
|
|
div = DIV_ROUND_UP(req->parent_rate, req->rate);
|
|
|
|
if (div < 16)
|
|
calcp = 0;
|
|
else if (div / 2 < 16)
|
|
calcp = 1;
|
|
else if (div / 4 < 16)
|
|
calcp = 2;
|
|
else
|
|
calcp = 3;
|
|
|
|
calcm = DIV_ROUND_UP(div, 1 << calcp);
|
|
|
|
req->rate = (req->parent_rate >> calcp) / calcm;
|
|
req->m = calcm - 1;
|
|
req->p = calcp;
|
|
}
|
|
|
|
/* user manual says "n" but it's really "p" */
|
|
static const struct clk_factors_config sun4i_a10_mod0_config = {
|
|
.mshift = 0,
|
|
.mwidth = 4,
|
|
.pshift = 16,
|
|
.pwidth = 2,
|
|
};
|
|
|
|
static const struct factors_data sun4i_a10_mod0_data = {
|
|
.enable = 31,
|
|
.mux = 24,
|
|
.muxmask = BIT(1) | BIT(0),
|
|
.table = &sun4i_a10_mod0_config,
|
|
.getter = sun4i_a10_get_mod0_factors,
|
|
};
|
|
|
|
static DEFINE_SPINLOCK(sun4i_a10_mod0_lock);
|
|
|
|
static void __init sun4i_a10_mod0_setup(struct device_node *node)
|
|
{
|
|
void __iomem *reg;
|
|
|
|
reg = of_iomap(node, 0);
|
|
if (!reg) {
|
|
/*
|
|
* This happens with mod0 clk nodes instantiated through
|
|
* mfd, as those do not have their resources assigned at
|
|
* CLK_OF_DECLARE time yet, so do not print an error.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
sunxi_factors_register(node, &sun4i_a10_mod0_data,
|
|
&sun4i_a10_mod0_lock, reg);
|
|
}
|
|
CLK_OF_DECLARE_DRIVER(sun4i_a10_mod0, "allwinner,sun4i-a10-mod0-clk",
|
|
sun4i_a10_mod0_setup);
|
|
|
|
static int sun4i_a10_mod0_clk_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct resource *r;
|
|
void __iomem *reg;
|
|
|
|
if (!np)
|
|
return -ENODEV;
|
|
|
|
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
reg = devm_ioremap_resource(&pdev->dev, r);
|
|
if (IS_ERR(reg))
|
|
return PTR_ERR(reg);
|
|
|
|
sunxi_factors_register(np, &sun4i_a10_mod0_data,
|
|
&sun4i_a10_mod0_lock, reg);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id sun4i_a10_mod0_clk_dt_ids[] = {
|
|
{ .compatible = "allwinner,sun4i-a10-mod0-clk" },
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
static struct platform_driver sun4i_a10_mod0_clk_driver = {
|
|
.driver = {
|
|
.name = "sun4i-a10-mod0-clk",
|
|
.of_match_table = sun4i_a10_mod0_clk_dt_ids,
|
|
},
|
|
.probe = sun4i_a10_mod0_clk_probe,
|
|
};
|
|
builtin_platform_driver(sun4i_a10_mod0_clk_driver);
|
|
|
|
static const struct factors_data sun9i_a80_mod0_data __initconst = {
|
|
.enable = 31,
|
|
.mux = 24,
|
|
.muxmask = BIT(3) | BIT(2) | BIT(1) | BIT(0),
|
|
.table = &sun4i_a10_mod0_config,
|
|
.getter = sun4i_a10_get_mod0_factors,
|
|
};
|
|
|
|
static void __init sun9i_a80_mod0_setup(struct device_node *node)
|
|
{
|
|
void __iomem *reg;
|
|
|
|
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
|
|
if (IS_ERR(reg)) {
|
|
pr_err("Could not get registers for mod0-clk: %pOFn\n",
|
|
node);
|
|
return;
|
|
}
|
|
|
|
sunxi_factors_register(node, &sun9i_a80_mod0_data,
|
|
&sun4i_a10_mod0_lock, reg);
|
|
}
|
|
CLK_OF_DECLARE(sun9i_a80_mod0, "allwinner,sun9i-a80-mod0-clk", sun9i_a80_mod0_setup);
|
|
|
|
static DEFINE_SPINLOCK(sun5i_a13_mbus_lock);
|
|
|
|
static void __init sun5i_a13_mbus_setup(struct device_node *node)
|
|
{
|
|
void __iomem *reg;
|
|
|
|
reg = of_iomap(node, 0);
|
|
if (!reg) {
|
|
pr_err("Could not get registers for a13-mbus-clk\n");
|
|
return;
|
|
}
|
|
|
|
/* The MBUS clocks needs to be always enabled */
|
|
sunxi_factors_register_critical(node, &sun4i_a10_mod0_data,
|
|
&sun5i_a13_mbus_lock, reg);
|
|
}
|
|
CLK_OF_DECLARE(sun5i_a13_mbus, "allwinner,sun5i-a13-mbus-clk", sun5i_a13_mbus_setup);
|
|
|
|
struct mmc_phase {
|
|
struct clk_hw hw;
|
|
u8 offset;
|
|
void __iomem *reg;
|
|
spinlock_t *lock;
|
|
};
|
|
|
|
#define to_mmc_phase(_hw) container_of(_hw, struct mmc_phase, hw)
|
|
|
|
static int mmc_get_phase(struct clk_hw *hw)
|
|
{
|
|
struct clk *mmc, *mmc_parent, *clk = hw->clk;
|
|
struct mmc_phase *phase = to_mmc_phase(hw);
|
|
unsigned int mmc_rate, mmc_parent_rate;
|
|
u16 step, mmc_div;
|
|
u32 value;
|
|
u8 delay;
|
|
|
|
value = readl(phase->reg);
|
|
delay = (value >> phase->offset) & 0x3;
|
|
|
|
if (!delay)
|
|
return 180;
|
|
|
|
/* Get the main MMC clock */
|
|
mmc = clk_get_parent(clk);
|
|
if (!mmc)
|
|
return -EINVAL;
|
|
|
|
/* And its rate */
|
|
mmc_rate = clk_get_rate(mmc);
|
|
if (!mmc_rate)
|
|
return -EINVAL;
|
|
|
|
/* Now, get the MMC parent (most likely some PLL) */
|
|
mmc_parent = clk_get_parent(mmc);
|
|
if (!mmc_parent)
|
|
return -EINVAL;
|
|
|
|
/* And its rate */
|
|
mmc_parent_rate = clk_get_rate(mmc_parent);
|
|
if (!mmc_parent_rate)
|
|
return -EINVAL;
|
|
|
|
/* Get MMC clock divider */
|
|
mmc_div = mmc_parent_rate / mmc_rate;
|
|
|
|
step = DIV_ROUND_CLOSEST(360, mmc_div);
|
|
return delay * step;
|
|
}
|
|
|
|
static int mmc_set_phase(struct clk_hw *hw, int degrees)
|
|
{
|
|
struct clk *mmc, *mmc_parent, *clk = hw->clk;
|
|
struct mmc_phase *phase = to_mmc_phase(hw);
|
|
unsigned int mmc_rate, mmc_parent_rate;
|
|
unsigned long flags;
|
|
u32 value;
|
|
u8 delay;
|
|
|
|
/* Get the main MMC clock */
|
|
mmc = clk_get_parent(clk);
|
|
if (!mmc)
|
|
return -EINVAL;
|
|
|
|
/* And its rate */
|
|
mmc_rate = clk_get_rate(mmc);
|
|
if (!mmc_rate)
|
|
return -EINVAL;
|
|
|
|
/* Now, get the MMC parent (most likely some PLL) */
|
|
mmc_parent = clk_get_parent(mmc);
|
|
if (!mmc_parent)
|
|
return -EINVAL;
|
|
|
|
/* And its rate */
|
|
mmc_parent_rate = clk_get_rate(mmc_parent);
|
|
if (!mmc_parent_rate)
|
|
return -EINVAL;
|
|
|
|
if (degrees != 180) {
|
|
u16 step, mmc_div;
|
|
|
|
/* Get MMC clock divider */
|
|
mmc_div = mmc_parent_rate / mmc_rate;
|
|
|
|
/*
|
|
* We can only outphase the clocks by multiple of the
|
|
* PLL's period.
|
|
*
|
|
* Since the MMC clock in only a divider, and the
|
|
* formula to get the outphasing in degrees is deg =
|
|
* 360 * delta / period
|
|
*
|
|
* If we simplify this formula, we can see that the
|
|
* only thing that we're concerned about is the number
|
|
* of period we want to outphase our clock from, and
|
|
* the divider set by the MMC clock.
|
|
*/
|
|
step = DIV_ROUND_CLOSEST(360, mmc_div);
|
|
delay = DIV_ROUND_CLOSEST(degrees, step);
|
|
} else {
|
|
delay = 0;
|
|
}
|
|
|
|
spin_lock_irqsave(phase->lock, flags);
|
|
value = readl(phase->reg);
|
|
value &= ~GENMASK(phase->offset + 3, phase->offset);
|
|
value |= delay << phase->offset;
|
|
writel(value, phase->reg);
|
|
spin_unlock_irqrestore(phase->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct clk_ops mmc_clk_ops = {
|
|
.get_phase = mmc_get_phase,
|
|
.set_phase = mmc_set_phase,
|
|
};
|
|
|
|
/*
|
|
* sunxi_mmc_setup - Common setup function for mmc module clocks
|
|
*
|
|
* The only difference between module clocks on different platforms is the
|
|
* width of the mux register bits and the valid values, which are passed in
|
|
* through struct factors_data. The phase clocks parts are identical.
|
|
*/
|
|
static void __init sunxi_mmc_setup(struct device_node *node,
|
|
const struct factors_data *data,
|
|
spinlock_t *lock)
|
|
{
|
|
struct clk_onecell_data *clk_data;
|
|
const char *parent;
|
|
void __iomem *reg;
|
|
int i;
|
|
|
|
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
|
|
if (IS_ERR(reg)) {
|
|
pr_err("Couldn't map the %pOFn clock registers\n", node);
|
|
return;
|
|
}
|
|
|
|
clk_data = kmalloc(sizeof(*clk_data), GFP_KERNEL);
|
|
if (!clk_data)
|
|
return;
|
|
|
|
clk_data->clks = kcalloc(3, sizeof(*clk_data->clks), GFP_KERNEL);
|
|
if (!clk_data->clks)
|
|
goto err_free_data;
|
|
|
|
clk_data->clk_num = 3;
|
|
clk_data->clks[0] = sunxi_factors_register(node, data, lock, reg);
|
|
if (!clk_data->clks[0])
|
|
goto err_free_clks;
|
|
|
|
parent = __clk_get_name(clk_data->clks[0]);
|
|
|
|
for (i = 1; i < 3; i++) {
|
|
struct clk_init_data init = {
|
|
.num_parents = 1,
|
|
.parent_names = &parent,
|
|
.ops = &mmc_clk_ops,
|
|
};
|
|
struct mmc_phase *phase;
|
|
|
|
phase = kmalloc(sizeof(*phase), GFP_KERNEL);
|
|
if (!phase)
|
|
continue;
|
|
|
|
phase->hw.init = &init;
|
|
phase->reg = reg;
|
|
phase->lock = lock;
|
|
|
|
if (i == 1)
|
|
phase->offset = 8;
|
|
else
|
|
phase->offset = 20;
|
|
|
|
if (of_property_read_string_index(node, "clock-output-names",
|
|
i, &init.name))
|
|
init.name = node->name;
|
|
|
|
clk_data->clks[i] = clk_register(NULL, &phase->hw);
|
|
if (IS_ERR(clk_data->clks[i])) {
|
|
kfree(phase);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
|
|
|
|
return;
|
|
|
|
err_free_clks:
|
|
kfree(clk_data->clks);
|
|
err_free_data:
|
|
kfree(clk_data);
|
|
}
|
|
|
|
static DEFINE_SPINLOCK(sun4i_a10_mmc_lock);
|
|
|
|
static void __init sun4i_a10_mmc_setup(struct device_node *node)
|
|
{
|
|
sunxi_mmc_setup(node, &sun4i_a10_mod0_data, &sun4i_a10_mmc_lock);
|
|
}
|
|
CLK_OF_DECLARE(sun4i_a10_mmc, "allwinner,sun4i-a10-mmc-clk", sun4i_a10_mmc_setup);
|
|
|
|
static DEFINE_SPINLOCK(sun9i_a80_mmc_lock);
|
|
|
|
static void __init sun9i_a80_mmc_setup(struct device_node *node)
|
|
{
|
|
sunxi_mmc_setup(node, &sun9i_a80_mod0_data, &sun9i_a80_mmc_lock);
|
|
}
|
|
CLK_OF_DECLARE(sun9i_a80_mmc, "allwinner,sun9i-a80-mmc-clk", sun9i_a80_mmc_setup);
|