Commit 51de8760 authored by Stephen Boyd's avatar Stephen Boyd

Merge tag 'sunxi-clocks-for-4.7' of...

Merge tag 'sunxi-clocks-for-4.7' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux into clk-next

Pull Allwinner clock driver updates from Maxime Ripard:

As usual, a bunch of clocks patches for 4.7, mostly fixes and cleanups, and
display-related clocks.

* tag 'sunxi-clocks-for-4.7' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux:
  clk: sunxi: Let divs clocks read the base factor clock name from devicetree
  clk: sunxi: Add TCON channel1 clock
  clk: sunxi: Add PLL3 clock
  dt-bindings: clk: sun5i: add DRAM gates compatible
  clk: sunxi: Use resource_size
  clk: sunxi: Add sun6i/8i display support
  clk: sunxi: mod1 clock should modify it's parent
parents 5bc75324 ff2bb893
......@@ -10,6 +10,7 @@ Required properties:
"allwinner,sun4i-a10-pll1-clk" - for the main PLL clock and PLL4
"allwinner,sun6i-a31-pll1-clk" - for the main PLL clock on A31
"allwinner,sun8i-a23-pll1-clk" - for the main PLL clock on A23
"allwinner,sun4i-a10-pll3-clk" - for the video PLL clock on A10
"allwinner,sun9i-a80-pll4-clk" - for the peripheral PLLs on A80
"allwinner,sun4i-a10-pll5-clk" - for the PLL5 clock
"allwinner,sun4i-a10-pll6-clk" - for the PLL6 clock
......@@ -64,6 +65,7 @@ Required properties:
"allwinner,sun8i-h3-bus-gates-clk" - for the bus gates on H3
"allwinner,sun9i-a80-apbs-gates-clk" - for the APBS gates on A80
"allwinner,sun4i-a10-dram-gates-clk" - for the DRAM gates on A10
"allwinner,sun5i-a13-dram-gates-clk" - for the DRAM gates on A13
"allwinner,sun5i-a13-mbus-clk" - for the MBUS clock on A13
"allwinner,sun4i-a10-mmc-clk" - for the MMC clock
"allwinner,sun9i-a80-mmc-clk" - for mmc module clocks on A80
......@@ -73,6 +75,7 @@ Required properties:
"allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23
"allwinner,sun7i-a20-out-clk" - for the external output clocks
"allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31
"allwinner,sun4i-a10-tcon-ch1-clk" - for the TCON channel 1 clock on the A10
"allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20
"allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13
"allwinner,sun6i-a31-usb-clk" - for usb gates + resets on A31
......@@ -81,6 +84,7 @@ Required properties:
"allwinner,sun9i-a80-usb-mod-clk" - for usb gates + resets on A80
"allwinner,sun9i-a80-usb-phy-clk" - for usb phy gates + resets on A80
"allwinner,sun4i-a10-ve-clk" - for the Video Engine clock
"allwinner,sun6i-a31-display-clk" - for the display clocks
Required properties for all clocks:
- reg : shall be the control register address for the clock.
......
......@@ -11,6 +11,8 @@ obj-y += clk-a10-ve.o
obj-y += clk-a20-gmac.o
obj-y += clk-mod0.o
obj-y += clk-simple-gates.o
obj-y += clk-sun4i-pll3.o
obj-y += clk-sun4i-tcon-ch1.o
obj-y += clk-sun8i-bus-gates.o
obj-y += clk-sun8i-mbus.o
obj-y += clk-sun9i-core.o
......
......@@ -62,7 +62,7 @@ static void __init sun4i_mod1_clk_setup(struct device_node *node)
clk = clk_register_composite(NULL, clk_name, parents, i,
&mux->hw, &clk_mux_ops,
NULL, NULL,
&gate->hw, &clk_gate_ops, 0);
&gate->hw, &clk_gate_ops, CLK_SET_RATE_PARENT);
if (IS_ERR(clk))
goto err_free_gate;
......
/*
* Copyright 2015 Maxime Ripard
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* 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.
*/
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#define SUN4I_A10_PLL3_GATE_BIT 31
#define SUN4I_A10_PLL3_DIV_WIDTH 7
#define SUN4I_A10_PLL3_DIV_SHIFT 0
static DEFINE_SPINLOCK(sun4i_a10_pll3_lock);
static void __init sun4i_a10_pll3_setup(struct device_node *node)
{
const char *clk_name = node->name, *parent;
struct clk_multiplier *mult;
struct clk_gate *gate;
struct resource res;
void __iomem *reg;
struct clk *clk;
int ret;
of_property_read_string(node, "clock-output-names", &clk_name);
parent = of_clk_get_parent_name(node, 0);
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg)) {
pr_err("%s: Could not map the clock registers\n", clk_name);
return;
}
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
goto err_unmap;
gate->reg = reg;
gate->bit_idx = SUN4I_A10_PLL3_GATE_BIT;
gate->lock = &sun4i_a10_pll3_lock;
mult = kzalloc(sizeof(*mult), GFP_KERNEL);
if (!mult)
goto err_free_gate;
mult->reg = reg;
mult->shift = SUN4I_A10_PLL3_DIV_SHIFT;
mult->width = SUN4I_A10_PLL3_DIV_WIDTH;
mult->lock = &sun4i_a10_pll3_lock;
clk = clk_register_composite(NULL, clk_name,
&parent, 1,
NULL, NULL,
&mult->hw, &clk_multiplier_ops,
&gate->hw, &clk_gate_ops,
0);
if (IS_ERR(clk)) {
pr_err("%s: Couldn't register the clock\n", clk_name);
goto err_free_mult;
}
ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
if (ret) {
pr_err("%s: Couldn't register DT provider\n",
clk_name);
goto err_clk_unregister;
}
return;
err_clk_unregister:
clk_unregister_composite(clk);
err_free_mult:
kfree(mult);
err_free_gate:
kfree(gate);
err_unmap:
iounmap(reg);
of_address_to_resource(node, 0, &res);
release_mem_region(res.start, resource_size(&res));
}
CLK_OF_DECLARE(sun4i_a10_pll3, "allwinner,sun4i-a10-pll3-clk",
sun4i_a10_pll3_setup);
/*
* Copyright 2015 Maxime Ripard
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* 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.
*/
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#define TCON_CH1_SCLK2_PARENTS 4
#define TCON_CH1_SCLK2_GATE_BIT BIT(31)
#define TCON_CH1_SCLK2_MUX_MASK 3
#define TCON_CH1_SCLK2_MUX_SHIFT 24
#define TCON_CH1_SCLK2_DIV_MASK 0xf
#define TCON_CH1_SCLK2_DIV_SHIFT 0
#define TCON_CH1_SCLK1_GATE_BIT BIT(15)
#define TCON_CH1_SCLK1_HALF_BIT BIT(11)
struct tcon_ch1_clk {
struct clk_hw hw;
spinlock_t lock;
void __iomem *reg;
};
#define hw_to_tclk(hw) container_of(hw, struct tcon_ch1_clk, hw)
static void tcon_ch1_disable(struct clk_hw *hw)
{
struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
unsigned long flags;
u32 reg;
spin_lock_irqsave(&tclk->lock, flags);
reg = readl(tclk->reg);
reg &= ~(TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT);
writel(reg, tclk->reg);
spin_unlock_irqrestore(&tclk->lock, flags);
}
static int tcon_ch1_enable(struct clk_hw *hw)
{
struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
unsigned long flags;
u32 reg;
spin_lock_irqsave(&tclk->lock, flags);
reg = readl(tclk->reg);
reg |= TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT;
writel(reg, tclk->reg);
spin_unlock_irqrestore(&tclk->lock, flags);
return 0;
}
static int tcon_ch1_is_enabled(struct clk_hw *hw)
{
struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
u32 reg;
reg = readl(tclk->reg);
return reg & (TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT);
}
static u8 tcon_ch1_get_parent(struct clk_hw *hw)
{
struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
int num_parents = clk_hw_get_num_parents(hw);
u32 reg;
reg = readl(tclk->reg) >> TCON_CH1_SCLK2_MUX_SHIFT;
reg &= reg >> TCON_CH1_SCLK2_MUX_MASK;
if (reg >= num_parents)
return -EINVAL;
return reg;
}
static int tcon_ch1_set_parent(struct clk_hw *hw, u8 index)
{
struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
unsigned long flags;
u32 reg;
spin_lock_irqsave(&tclk->lock, flags);
reg = readl(tclk->reg);
reg &= ~(TCON_CH1_SCLK2_MUX_MASK << TCON_CH1_SCLK2_MUX_SHIFT);
reg |= index << TCON_CH1_SCLK2_MUX_SHIFT;
writel(reg, tclk->reg);
spin_unlock_irqrestore(&tclk->lock, flags);
return 0;
};
static unsigned long tcon_ch1_calc_divider(unsigned long rate,
unsigned long parent_rate,
u8 *div,
bool *half)
{
unsigned long best_rate = 0;
u8 best_m = 0, m;
bool is_double;
for (m = 1; m < 16; m++) {
u8 d;
for (d = 1; d < 3; d++) {
unsigned long tmp_rate;
tmp_rate = parent_rate / m / d;
if (tmp_rate > rate)
continue;
if (!best_rate ||
(rate - tmp_rate) < (rate - best_rate)) {
best_rate = tmp_rate;
best_m = m;
is_double = d;
}
}
}
if (div && half) {
*div = best_m;
*half = is_double;
}
return best_rate;
}
static int tcon_ch1_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
long best_rate = -EINVAL;
int i;
for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
unsigned long parent_rate;
unsigned long tmp_rate;
struct clk_hw *parent;
parent = clk_hw_get_parent_by_index(hw, i);
if (!parent)
continue;
parent_rate = clk_hw_get_rate(parent);
tmp_rate = tcon_ch1_calc_divider(req->rate, parent_rate,
NULL, NULL);
if (best_rate < 0 ||
(req->rate - tmp_rate) < (req->rate - best_rate)) {
best_rate = tmp_rate;
req->best_parent_rate = parent_rate;
req->best_parent_hw = parent;
}
}
if (best_rate < 0)
return best_rate;
req->rate = best_rate;
return 0;
}
static unsigned long tcon_ch1_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
u32 reg;
reg = readl(tclk->reg);
parent_rate /= (reg & TCON_CH1_SCLK2_DIV_MASK) + 1;
if (reg & TCON_CH1_SCLK1_HALF_BIT)
parent_rate /= 2;
return parent_rate;
}
static int tcon_ch1_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
unsigned long flags;
bool half;
u8 div_m;
u32 reg;
tcon_ch1_calc_divider(rate, parent_rate, &div_m, &half);
spin_lock_irqsave(&tclk->lock, flags);
reg = readl(tclk->reg);
reg &= ~(TCON_CH1_SCLK2_DIV_MASK | TCON_CH1_SCLK1_HALF_BIT);
reg |= (div_m - 1) & TCON_CH1_SCLK2_DIV_MASK;
if (half)
reg |= TCON_CH1_SCLK1_HALF_BIT;
writel(reg, tclk->reg);
spin_unlock_irqrestore(&tclk->lock, flags);
return 0;
}
static const struct clk_ops tcon_ch1_ops = {
.disable = tcon_ch1_disable,
.enable = tcon_ch1_enable,
.is_enabled = tcon_ch1_is_enabled,
.get_parent = tcon_ch1_get_parent,
.set_parent = tcon_ch1_set_parent,
.determine_rate = tcon_ch1_determine_rate,
.recalc_rate = tcon_ch1_recalc_rate,
.set_rate = tcon_ch1_set_rate,
};
static void __init tcon_ch1_setup(struct device_node *node)
{
const char *parents[TCON_CH1_SCLK2_PARENTS];
const char *clk_name = node->name;
struct clk_init_data init;
struct tcon_ch1_clk *tclk;
struct resource res;
struct clk *clk;
void __iomem *reg;
int ret;
of_property_read_string(node, "clock-output-names", &clk_name);
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg)) {
pr_err("%s: Could not map the clock registers\n", clk_name);
return;
}
ret = of_clk_parent_fill(node, parents, TCON_CH1_SCLK2_PARENTS);
if (ret != TCON_CH1_SCLK2_PARENTS) {
pr_err("%s Could not retrieve the parents\n", clk_name);
goto err_unmap;
}
tclk = kzalloc(sizeof(*tclk), GFP_KERNEL);
if (!tclk)
goto err_unmap;
init.name = clk_name;
init.ops = &tcon_ch1_ops;
init.parent_names = parents;
init.num_parents = TCON_CH1_SCLK2_PARENTS;
init.flags = CLK_SET_RATE_PARENT;
tclk->reg = reg;
tclk->hw.init = &init;
spin_lock_init(&tclk->lock);
clk = clk_register(NULL, &tclk->hw);
if (IS_ERR(clk)) {
pr_err("%s: Couldn't register the clock\n", clk_name);
goto err_free_data;
}
ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
if (ret) {
pr_err("%s: Couldn't register our clock provider\n", clk_name);
goto err_unregister_clk;
}
return;
err_unregister_clk:
clk_unregister(clk);
err_free_data:
kfree(tclk);
err_unmap:
iounmap(reg);
of_address_to_resource(node, 0, &res);
release_mem_region(res.start, resource_size(&res));
}
CLK_OF_DECLARE(tcon_ch1, "allwinner,sun4i-a10-tcon-ch1-clk",
tcon_ch1_setup);
......@@ -106,7 +106,7 @@ static int sun9i_a80_mmc_config_clk_probe(struct platform_device *pdev)
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/* one clock/reset pair per word */
count = DIV_ROUND_UP((r->end - r->start + 1), SUN9I_MMC_WIDTH);
count = DIV_ROUND_UP((resource_size(r)), SUN9I_MMC_WIDTH);
data->membase = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(data->membase))
return PTR_ERR(data->membase);
......
......@@ -523,21 +523,12 @@ static const struct factors_data sun4i_pll5_data __initconst = {
.enable = 31,
.table = &sun4i_pll5_config,
.getter = sun4i_get_pll5_factors,
.name = "pll5",
};
static const struct factors_data sun4i_pll6_data __initconst = {
.enable = 31,
.table = &sun4i_pll5_config,
.getter = sun4i_get_pll5_factors,
.name = "pll6",
};
static const struct factors_data sun6i_a31_pll6_data __initconst = {
.enable = 31,
.table = &sun6i_a31_pll6_config,
.getter = sun6i_a31_get_pll6_factors,
.name = "pll6x2",
};
static const struct factors_data sun5i_a13_ahb_data __initconst = {
......@@ -933,7 +924,7 @@ static const struct divs_data pll5_divs_data __initconst = {
};
static const struct divs_data pll6_divs_data __initconst = {
.factors = &sun4i_pll6_data,
.factors = &sun4i_pll5_data,
.ndivs = 4,
.div = {
{ .shift = 0, .table = pll6_sata_tbl, .gate = 14 }, /* M, SATA */
......@@ -975,6 +966,8 @@ static struct clk ** __init sunxi_divs_clk_setup(struct device_node *node,
struct clk_gate *gate = NULL;
struct clk_fixed_factor *fix_factor;
struct clk_divider *divider;
struct factors_data factors = *data->factors;
char *derived_name = NULL;
void __iomem *reg;
int ndivs = SUNXI_DIVS_MAX_QTY, i = 0;
int flags, clkflags;
......@@ -983,11 +976,37 @@ static struct clk ** __init sunxi_divs_clk_setup(struct device_node *node,
if (data->ndivs)
ndivs = data->ndivs;
/* Try to find a name for base factor clock */
for (i = 0; i < ndivs; i++) {
if (data->div[i].self) {
of_property_read_string_index(node, "clock-output-names",
i, &factors.name);
break;
}
}
/* If we don't have a .self clk use the first output-name up to '_' */
if (factors.name == NULL) {
char *endp;
of_property_read_string_index(node, "clock-output-names",
0, &clk_name);
endp = strchr(clk_name, '_');
if (endp) {
derived_name = kstrndup(clk_name, endp - clk_name,
GFP_KERNEL);
factors.name = derived_name;
} else {
factors.name = clk_name;
}
}
/* Set up factor clock that we will be dividing */
pclk = sunxi_factors_clk_setup(node, data->factors);
pclk = sunxi_factors_clk_setup(node, &factors);
if (!pclk)
return NULL;
parent = __clk_get_name(pclk);
kfree(derived_name);
reg = of_iomap(node, 0);
if (!reg) {
......@@ -1127,3 +1146,41 @@ static void __init sun6i_pll6_clk_setup(struct device_node *node)
}
CLK_OF_DECLARE(sun6i_pll6, "allwinner,sun6i-a31-pll6-clk",
sun6i_pll6_clk_setup);
/*
* sun6i display
*
* rate = parent_rate / (m + 1);
*/
static void sun6i_display_factors(struct factors_request *req)
{
u8 m;
if (req->rate > req->parent_rate)
req->rate = req->parent_rate;
m = DIV_ROUND_UP(req->parent_rate, req->rate);
req->rate = req->parent_rate / m;
req->m = m - 1;
}
static const struct clk_factors_config sun6i_display_config = {
.mshift = 0,
.mwidth = 4,
};
static const struct factors_data sun6i_display_data __initconst = {
.enable = 31,
.mux = 24,
.muxmask = BIT(2) | BIT(1) | BIT(0),
.table = &sun6i_display_config,
.getter = sun6i_display_factors,
};
static void __init sun6i_display_setup(struct device_node *node)
{
sunxi_factors_clk_setup(node, &sun6i_display_data);
}
CLK_OF_DECLARE(sun6i_display, "allwinner,sun6i-a31-display-clk",
sun6i_display_setup);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment