Allwinner clocks changes for 4.5

Clock patches for the Allwinner SoCs:
   - H3 clocks
   - A10/A20 Video Engine clocks
   - DRAM gates
   - A80 special CPU clock
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJWcyKFAAoJEBx+YmzsjxAgtNMP/jJWzfh8bLB+eCZHGtBm/Y86
 em0DaAFWoAYOo0iFGRNkiJ+Gk5lOFx/qLjMbHitfEk00lNs9L2Goyzhko1WnaLQ6
 ZmJK1X3bcd5Uyqa3O8RP3zu0NUDin4sU4ZhKT8PuSkPEO69wSlgn+4R3e46jlRVg
 +1dbeEs68N10PjIBPqoCDYuP3YJW+nbdJQ03M4M9W6xNVgxtz/FVv1i3W61m3+r2
 LInUfr4A/+Mr3hNlGol4PEq8HP4xdfXRnrnSPNKJfysxc+9e2al6hmfiiBlSyhV2
 Z6HPG2qaxWMGCBG/q0SOh0I1vL8CgWbw0E04Wu5hzSBBaaS+KIEU6KqMQKFLyw5C
 U5V4z0g2ePs8SZD3SB1taB2y3dVtCwtOWvo/teAOKc7zXe/3wPRAWyxg4N5IGX/C
 bwHQGrDKuwsXVySAXqPXEojwTToF/TxqsS1RpunjLA2JIb1dvREUP0MMPXgPZJKN
 yiZLfeWw4SDonSPagSy/AQuEjih5ekZmT2aIOU4oCAqjpre6gQaolQn1xriL+ACf
 qdT4Hcg2hzlkjPB0N9izI7Krcef4oZpzZqls2/M9CnmTeMY4l2z9lZylqto4HnLF
 VT3duWXlmh8g9YGiWpMWwtcl0kXxP2mYM959k9KFrtvX7lhN4T2hmJwRnnCRCKre
 Zpt/9dotvmyM5Dno0Pee
 =Tahe
 -----END PGP SIGNATURE-----

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

Allwinner clocks changes for 4.5

Clock patches for the Allwinner SoCs:
  - H3 clocks
  - A10/A20 Video Engine clocks
  - DRAM gates
  - A80 special CPU clock
This commit is contained in:
Michael Turquette 2015-12-23 23:23:18 -08:00
commit cf87a88f51
9 changed files with 638 additions and 12 deletions

View file

@ -27,7 +27,9 @@ Required properties:
"allwinner,sun5i-a10s-ahb-gates-clk" - for the AHB gates on A10s
"allwinner,sun7i-a20-ahb-gates-clk" - for the AHB gates on A20
"allwinner,sun6i-a31-ar100-clk" - for the AR100 on A31
"allwinner,sun9i-a80-cpus-clk" - for the CPUS on A80
"allwinner,sun6i-a31-ahb1-clk" - for the AHB1 clock on A31
"allwinner,sun8i-h3-ahb2-clk" - for the AHB2 clock on H3
"allwinner,sun6i-a31-ahb1-gates-clk" - for the AHB1 gates on A31
"allwinner,sun8i-a23-ahb1-gates-clk" - for the AHB1 gates on A23
"allwinner,sun9i-a80-ahb0-gates-clk" - for the AHB0 gates on A80
@ -55,6 +57,9 @@ Required properties:
"allwinner,sun9i-a80-apb1-gates-clk" - for the APB1 gates on A80
"allwinner,sun6i-a31-apb2-gates-clk" - for the APB2 gates on A31
"allwinner,sun8i-a23-apb2-gates-clk" - for the APB2 gates on A23
"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-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
@ -68,8 +73,10 @@ Required properties:
"allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13
"allwinner,sun6i-a31-usb-clk" - for usb gates + resets on A31
"allwinner,sun8i-a23-usb-clk" - for usb gates + resets on A23
"allwinner,sun8i-h3-usb-clk" - for usb gates + resets on H3
"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
Required properties for all clocks:
- reg : shall be the control register address for the clock.
@ -89,6 +96,9 @@ Required properties for all clocks:
And "allwinner,*-usb-clk" clocks also require:
- reset-cells : shall be set to 1
The "allwinner,sun4i-a10-ve-clk" clock also requires:
- reset-cells : shall be set to 0
The "allwinner,sun9i-a80-mmc-config-clk" clock also requires:
- #reset-cells : shall be set to 1
- resets : shall be the reset control phandle for the mmc block.

View file

@ -7,14 +7,19 @@ obj-y += clk-a10-codec.o
obj-y += clk-a10-hosc.o
obj-y += clk-a10-mod1.o
obj-y += clk-a10-pll2.o
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-sun8i-bus-gates.o
obj-y += clk-sun8i-mbus.o
obj-y += clk-sun9i-core.o
obj-y += clk-sun9i-mmc.o
obj-y += clk-usb.o
obj-$(CONFIG_MACH_SUN9I) += clk-sun8i-apb0.o
obj-$(CONFIG_MACH_SUN9I) += clk-sun9i-cpus.o
obj-$(CONFIG_MFD_SUN6I_PRCM) += \
clk-sun6i-ar100.o clk-sun6i-apb0.o clk-sun6i-apb0-gates.o \
clk-sun8i-apb0.o

View file

@ -0,0 +1,171 @@
/*
* Copyright 2015 Chen-Yu Tsai
*
* Chen-Yu Tsai <wens@csie.org>
*
* 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/reset-controller.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
static DEFINE_SPINLOCK(ve_lock);
#define SUN4I_VE_ENABLE 31
#define SUN4I_VE_DIVIDER_SHIFT 16
#define SUN4I_VE_DIVIDER_WIDTH 3
#define SUN4I_VE_RESET 0
/**
* sunxi_ve_reset... - reset bit in ve clk registers handling
*/
struct ve_reset_data {
void __iomem *reg;
spinlock_t *lock;
struct reset_controller_dev rcdev;
};
static int sunxi_ve_reset_assert(struct reset_controller_dev *rcdev,
unsigned long id)
{
struct ve_reset_data *data = container_of(rcdev,
struct ve_reset_data,
rcdev);
unsigned long flags;
u32 reg;
spin_lock_irqsave(data->lock, flags);
reg = readl(data->reg);
writel(reg & ~BIT(SUN4I_VE_RESET), data->reg);
spin_unlock_irqrestore(data->lock, flags);
return 0;
}
static int sunxi_ve_reset_deassert(struct reset_controller_dev *rcdev,
unsigned long id)
{
struct ve_reset_data *data = container_of(rcdev,
struct ve_reset_data,
rcdev);
unsigned long flags;
u32 reg;
spin_lock_irqsave(data->lock, flags);
reg = readl(data->reg);
writel(reg | BIT(SUN4I_VE_RESET), data->reg);
spin_unlock_irqrestore(data->lock, flags);
return 0;
}
static int sunxi_ve_of_xlate(struct reset_controller_dev *rcdev,
const struct of_phandle_args *reset_spec)
{
if (WARN_ON(reset_spec->args_count != 0))
return -EINVAL;
return 0;
}
static struct reset_control_ops sunxi_ve_reset_ops = {
.assert = sunxi_ve_reset_assert,
.deassert = sunxi_ve_reset_deassert,
};
static void __init sun4i_ve_clk_setup(struct device_node *node)
{
struct clk *clk;
struct clk_divider *div;
struct clk_gate *gate;
struct ve_reset_data *reset_data;
const char *parent;
const char *clk_name = node->name;
void __iomem *reg;
int err;
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg))
return;
div = kzalloc(sizeof(*div), GFP_KERNEL);
if (!div)
goto err_unmap;
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
goto err_free_div;
of_property_read_string(node, "clock-output-names", &clk_name);
parent = of_clk_get_parent_name(node, 0);
gate->reg = reg;
gate->bit_idx = SUN4I_VE_ENABLE;
gate->lock = &ve_lock;
div->reg = reg;
div->shift = SUN4I_VE_DIVIDER_SHIFT;
div->width = SUN4I_VE_DIVIDER_WIDTH;
div->lock = &ve_lock;
clk = clk_register_composite(NULL, clk_name, &parent, 1,
NULL, NULL,
&div->hw, &clk_divider_ops,
&gate->hw, &clk_gate_ops,
CLK_SET_RATE_PARENT);
if (IS_ERR(clk))
goto err_free_gate;
err = of_clk_add_provider(node, of_clk_src_simple_get, clk);
if (err)
goto err_unregister_clk;
reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL);
if (!reset_data)
goto err_del_provider;
reset_data->reg = reg;
reset_data->lock = &ve_lock;
reset_data->rcdev.nr_resets = 1;
reset_data->rcdev.ops = &sunxi_ve_reset_ops;
reset_data->rcdev.of_node = node;
reset_data->rcdev.of_xlate = sunxi_ve_of_xlate;
reset_data->rcdev.of_reset_n_cells = 0;
err = reset_controller_register(&reset_data->rcdev);
if (err)
goto err_free_reset;
return;
err_free_reset:
kfree(reset_data);
err_del_provider:
of_clk_del_provider(node);
err_unregister_clk:
clk_unregister(clk);
err_free_gate:
kfree(gate);
err_free_div:
kfree(div);
err_unmap:
iounmap(reg);
}
CLK_OF_DECLARE(sun4i_ve, "allwinner,sun4i-a10-ve-clk",
sun4i_ve_clk_setup);

View file

@ -140,6 +140,8 @@ CLK_OF_DECLARE(sun9i_a80_apb0, "allwinner,sun9i-a80-apb0-gates-clk",
sunxi_simple_gates_init);
CLK_OF_DECLARE(sun9i_a80_apb1, "allwinner,sun9i-a80-apb1-gates-clk",
sunxi_simple_gates_init);
CLK_OF_DECLARE(sun9i_a80_apbs, "allwinner,sun9i-a80-apbs-gates-clk",
sunxi_simple_gates_init);
static const int sun4i_a10_ahb_critical_clocks[] __initconst = {
14, /* ahb_sdram */
@ -158,3 +160,15 @@ CLK_OF_DECLARE(sun5i_a13_ahb, "allwinner,sun5i-a13-ahb-gates-clk",
sun4i_a10_ahb_init);
CLK_OF_DECLARE(sun7i_a20_ahb, "allwinner,sun7i-a20-ahb-gates-clk",
sun4i_a10_ahb_init);
static const int sun4i_a10_dram_critical_clocks[] __initconst = {
15, /* dram_output */
};
static void __init sun4i_a10_dram_init(struct device_node *node)
{
sunxi_simple_gates_setup(node, sun4i_a10_dram_critical_clocks,
ARRAY_SIZE(sun4i_a10_dram_critical_clocks));
}
CLK_OF_DECLARE(sun4i_a10_dram, "allwinner,sun4i-a10-dram-gates-clk",
sun4i_a10_dram_init);

View file

@ -17,13 +17,77 @@
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
static struct clk *sun8i_a23_apb0_register(struct device_node *node,
void __iomem *reg)
{
const char *clk_name = node->name;
const char *clk_parent;
struct clk *clk;
int ret;
clk_parent = of_clk_get_parent_name(node, 0);
if (!clk_parent)
return ERR_PTR(-EINVAL);
of_property_read_string(node, "clock-output-names", &clk_name);
/* The A23 APB0 clock is a standard 2 bit wide divider clock */
clk = clk_register_divider(NULL, clk_name, clk_parent, 0, reg,
0, 2, CLK_DIVIDER_POWER_OF_TWO, NULL);
if (IS_ERR(clk))
return clk;
ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
if (ret)
goto err_unregister;
return clk;
err_unregister:
clk_unregister_divider(clk);
return ERR_PTR(ret);
}
static void sun8i_a23_apb0_setup(struct device_node *node)
{
void __iomem *reg;
struct resource res;
struct clk *clk;
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg)) {
/*
* This happens with clk nodes instantiated through mfd,
* as those do not have their resources assigned in the
* device tree. Do not print an error in this case.
*/
if (PTR_ERR(reg) != -EINVAL)
pr_err("Could not get registers for a23-apb0-clk\n");
return;
}
clk = sun8i_a23_apb0_register(node, reg);
if (IS_ERR(clk))
goto err_unmap;
return;
err_unmap:
iounmap(reg);
of_address_to_resource(node, 0, &res);
release_mem_region(res.start, resource_size(&res));
}
CLK_OF_DECLARE(sun8i_a23_apb0, "allwinner,sun8i-a23-apb0-clk",
sun8i_a23_apb0_setup);
static int sun8i_a23_apb0_clk_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const char *clk_name = np->name;
const char *clk_parent;
struct resource *r;
void __iomem *reg;
struct clk *clk;
@ -33,19 +97,11 @@ static int sun8i_a23_apb0_clk_probe(struct platform_device *pdev)
if (IS_ERR(reg))
return PTR_ERR(reg);
clk_parent = of_clk_get_parent_name(np, 0);
if (!clk_parent)
return -EINVAL;
of_property_read_string(np, "clock-output-names", &clk_name);
/* The A23 APB0 clock is a standard 2 bit wide divider clock */
clk = clk_register_divider(&pdev->dev, clk_name, clk_parent, 0, reg,
0, 2, CLK_DIVIDER_POWER_OF_TWO, NULL);
clk = sun8i_a23_apb0_register(np, reg);
if (IS_ERR(clk))
return PTR_ERR(clk);
return of_clk_add_provider(np, of_clk_src_simple_get, clk);
return 0;
}
static const struct of_device_id sun8i_a23_apb0_clk_dt_ids[] = {

View file

@ -0,0 +1,112 @@
/*
* Copyright (C) 2015 Jens Kuske <jenskuske@gmail.com>
*
* Based on clk-simple-gates.c, which is:
* 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.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
static DEFINE_SPINLOCK(gates_lock);
static void __init sun8i_h3_bus_gates_init(struct device_node *node)
{
static const char * const names[] = { "ahb1", "ahb2", "apb1", "apb2" };
enum { AHB1, AHB2, APB1, APB2, PARENT_MAX } clk_parent;
const char *parents[PARENT_MAX];
struct clk_onecell_data *clk_data;
const char *clk_name;
struct property *prop;
struct resource res;
void __iomem *clk_reg;
void __iomem *reg;
const __be32 *p;
int number, i;
u8 clk_bit;
int index;
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg))
return;
for (i = 0; i < ARRAY_SIZE(names); i++) {
int idx = of_property_match_string(node, "clock-names",
names[i]);
if (idx < 0)
return;
parents[i] = of_clk_get_parent_name(node, idx);
}
clk_data = kmalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
if (!clk_data)
goto err_unmap;
number = of_property_count_u32_elems(node, "clock-indices");
of_property_read_u32_index(node, "clock-indices", number - 1, &number);
clk_data->clks = kcalloc(number + 1, sizeof(struct clk *), GFP_KERNEL);
if (!clk_data->clks)
goto err_free_data;
i = 0;
of_property_for_each_u32(node, "clock-indices", prop, p, index) {
of_property_read_string_index(node, "clock-output-names",
i, &clk_name);
if (index == 17 || (index >= 29 && index <= 31))
clk_parent = AHB2;
else if (index <= 63 || index >= 128)
clk_parent = AHB1;
else if (index >= 64 && index <= 95)
clk_parent = APB1;
else if (index >= 96 && index <= 127)
clk_parent = APB2;
clk_reg = reg + 4 * (index / 32);
clk_bit = index % 32;
clk_data->clks[index] = clk_register_gate(NULL, clk_name,
parents[clk_parent],
0, clk_reg, clk_bit,
0, &gates_lock);
i++;
if (IS_ERR(clk_data->clks[index])) {
WARN_ON(true);
continue;
}
}
clk_data->clk_num = number + 1;
of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
return;
err_free_data:
kfree(clk_data);
err_unmap:
iounmap(reg);
of_address_to_resource(node, 0, &res);
release_mem_region(res.start, resource_size(&res));
}
CLK_OF_DECLARE(sun8i_h3_bus_gates, "allwinner,sun8i-h3-bus-gates-clk",
sun8i_h3_bus_gates_init);

View file

@ -0,0 +1,240 @@
/*
* Copyright (C) 2015 Chen-Yu Tsai
*
* Chen-Yu Tsai <wens@csie.org>
*
* Allwinner A80 CPUS clock driver
*
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/of.h>
#include <linux/of_address.h>
static DEFINE_SPINLOCK(sun9i_a80_cpus_lock);
/**
* sun9i_a80_cpus_clk_setup() - Setup function for a80 cpus composite clk
*/
#define SUN9I_CPUS_MAX_PARENTS 4
#define SUN9I_CPUS_MUX_PARENT_PLL4 3
#define SUN9I_CPUS_MUX_SHIFT 16
#define SUN9I_CPUS_MUX_MASK GENMASK(17, 16)
#define SUN9I_CPUS_MUX_GET_PARENT(reg) ((reg & SUN9I_CPUS_MUX_MASK) >> \
SUN9I_CPUS_MUX_SHIFT)
#define SUN9I_CPUS_DIV_SHIFT 4
#define SUN9I_CPUS_DIV_MASK GENMASK(5, 4)
#define SUN9I_CPUS_DIV_GET(reg) ((reg & SUN9I_CPUS_DIV_MASK) >> \
SUN9I_CPUS_DIV_SHIFT)
#define SUN9I_CPUS_DIV_SET(reg, div) ((reg & ~SUN9I_CPUS_DIV_MASK) | \
(div << SUN9I_CPUS_DIV_SHIFT))
#define SUN9I_CPUS_PLL4_DIV_SHIFT 8
#define SUN9I_CPUS_PLL4_DIV_MASK GENMASK(12, 8)
#define SUN9I_CPUS_PLL4_DIV_GET(reg) ((reg & SUN9I_CPUS_PLL4_DIV_MASK) >> \
SUN9I_CPUS_PLL4_DIV_SHIFT)
#define SUN9I_CPUS_PLL4_DIV_SET(reg, div) ((reg & ~SUN9I_CPUS_PLL4_DIV_MASK) | \
(div << SUN9I_CPUS_PLL4_DIV_SHIFT))
struct sun9i_a80_cpus_clk {
struct clk_hw hw;
void __iomem *reg;
};
#define to_sun9i_a80_cpus_clk(_hw) container_of(_hw, struct sun9i_a80_cpus_clk, hw)
static unsigned long sun9i_a80_cpus_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct sun9i_a80_cpus_clk *cpus = to_sun9i_a80_cpus_clk(hw);
unsigned long rate;
u32 reg;
/* Fetch the register value */
reg = readl(cpus->reg);
/* apply pre-divider first if parent is pll4 */
if (SUN9I_CPUS_MUX_GET_PARENT(reg) == SUN9I_CPUS_MUX_PARENT_PLL4)
parent_rate /= SUN9I_CPUS_PLL4_DIV_GET(reg) + 1;
/* clk divider */
rate = parent_rate / (SUN9I_CPUS_DIV_GET(reg) + 1);
return rate;
}
static long sun9i_a80_cpus_clk_round(unsigned long rate, u8 *divp, u8 *pre_divp,
u8 parent, unsigned long parent_rate)
{
u8 div, pre_div = 1;
/*
* clock can only divide, so we will never be able to achieve
* frequencies higher than the parent frequency
*/
if (parent_rate && rate > parent_rate)
rate = parent_rate;
div = DIV_ROUND_UP(parent_rate, rate);
/* calculate pre-divider if parent is pll4 */
if (parent == SUN9I_CPUS_MUX_PARENT_PLL4 && div > 4) {
/* pre-divider is 1 ~ 32 */
if (div < 32) {
pre_div = div;
div = 1;
} else if (div < 64) {
pre_div = DIV_ROUND_UP(div, 2);
div = 2;
} else if (div < 96) {
pre_div = DIV_ROUND_UP(div, 3);
div = 3;
} else {
pre_div = DIV_ROUND_UP(div, 4);
div = 4;
}
}
/* we were asked to pass back divider values */
if (divp) {
*divp = div - 1;
*pre_divp = pre_div - 1;
}
return parent_rate / pre_div / div;
}
static int sun9i_a80_cpus_clk_determine_rate(struct clk_hw *clk,
struct clk_rate_request *req)
{
struct clk_hw *parent, *best_parent = NULL;
int i, num_parents;
unsigned long parent_rate, best = 0, child_rate, best_child_rate = 0;
unsigned long rate = req->rate;
/* find the parent that can help provide the fastest rate <= rate */
num_parents = clk_hw_get_num_parents(clk);
for (i = 0; i < num_parents; i++) {
parent = clk_hw_get_parent_by_index(clk, i);
if (!parent)
continue;
if (clk_hw_get_flags(clk) & CLK_SET_RATE_PARENT)
parent_rate = clk_hw_round_rate(parent, rate);
else
parent_rate = clk_hw_get_rate(parent);
child_rate = sun9i_a80_cpus_clk_round(rate, NULL, NULL, i,
parent_rate);
if (child_rate <= rate && child_rate > best_child_rate) {
best_parent = parent;
best = parent_rate;
best_child_rate = child_rate;
}
}
if (!best_parent)
return -EINVAL;
req->best_parent_hw = best_parent;
req->best_parent_rate = best;
req->rate = best_child_rate;
return 0;
}
static int sun9i_a80_cpus_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct sun9i_a80_cpus_clk *cpus = to_sun9i_a80_cpus_clk(hw);
unsigned long flags;
u8 div, pre_div, parent;
u32 reg;
spin_lock_irqsave(&sun9i_a80_cpus_lock, flags);
reg = readl(cpus->reg);
/* need to know which parent is used to apply pre-divider */
parent = SUN9I_CPUS_MUX_GET_PARENT(reg);
sun9i_a80_cpus_clk_round(rate, &div, &pre_div, parent, parent_rate);
reg = SUN9I_CPUS_DIV_SET(reg, div);
reg = SUN9I_CPUS_PLL4_DIV_SET(reg, pre_div);
writel(reg, cpus->reg);
spin_unlock_irqrestore(&sun9i_a80_cpus_lock, flags);
return 0;
}
static const struct clk_ops sun9i_a80_cpus_clk_ops = {
.determine_rate = sun9i_a80_cpus_clk_determine_rate,
.recalc_rate = sun9i_a80_cpus_clk_recalc_rate,
.set_rate = sun9i_a80_cpus_clk_set_rate,
};
static void sun9i_a80_cpus_setup(struct device_node *node)
{
const char *clk_name = node->name;
const char *parents[SUN9I_CPUS_MAX_PARENTS];
struct resource res;
struct sun9i_a80_cpus_clk *cpus;
struct clk_mux *mux;
struct clk *clk;
int ret;
cpus = kzalloc(sizeof(*cpus), GFP_KERNEL);
if (!cpus)
return;
cpus->reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(cpus->reg))
goto err_free_cpus;
of_property_read_string(node, "clock-output-names", &clk_name);
/* we have a mux, we will have >1 parents */
ret = of_clk_parent_fill(node, parents, SUN9I_CPUS_MAX_PARENTS);
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
if (!mux)
goto err_unmap;
/* set up clock properties */
mux->reg = cpus->reg;
mux->shift = SUN9I_CPUS_MUX_SHIFT;
/* un-shifted mask is what mux_clk expects */
mux->mask = SUN9I_CPUS_MUX_MASK >> SUN9I_CPUS_MUX_SHIFT;
mux->lock = &sun9i_a80_cpus_lock;
clk = clk_register_composite(NULL, clk_name, parents, ret,
&mux->hw, &clk_mux_ops,
&cpus->hw, &sun9i_a80_cpus_clk_ops,
NULL, NULL, 0);
if (IS_ERR(clk))
goto err_free_mux;
ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
if (ret)
goto err_unregister;
return;
err_unregister:
clk_unregister(clk);
err_free_mux:
kfree(mux);
err_unmap:
iounmap(cpus->reg);
of_address_to_resource(node, 0, &res);
release_mem_region(res.start, resource_size(&res));
err_free_cpus:
kfree(cpus);
}
CLK_OF_DECLARE(sun9i_a80_cpus, "allwinner,sun9i-a80-cpus-clk",
sun9i_a80_cpus_setup);

View file

@ -778,6 +778,10 @@ static const struct mux_data sun6i_a31_ahb1_mux_data __initconst = {
.shift = 12,
};
static const struct mux_data sun8i_h3_ahb2_mux_data __initconst = {
.shift = 0,
};
static void __init sunxi_mux_clk_setup(struct device_node *node,
struct mux_data *data)
{
@ -1130,6 +1134,7 @@ static const struct of_device_id clk_divs_match[] __initconst = {
static const struct of_device_id clk_mux_match[] __initconst = {
{.compatible = "allwinner,sun4i-a10-cpu-clk", .data = &sun4i_cpu_mux_data,},
{.compatible = "allwinner,sun6i-a31-ahb1-mux-clk", .data = &sun6i_a31_ahb1_mux_data,},
{.compatible = "allwinner,sun8i-h3-ahb2-clk", .data = &sun8i_h3_ahb2_mux_data,},
{}
};
@ -1212,6 +1217,7 @@ CLK_OF_DECLARE(sun6i_a31_clk_init, "allwinner,sun6i-a31", sun6i_init_clocks);
CLK_OF_DECLARE(sun6i_a31s_clk_init, "allwinner,sun6i-a31s", sun6i_init_clocks);
CLK_OF_DECLARE(sun8i_a23_clk_init, "allwinner,sun8i-a23", sun6i_init_clocks);
CLK_OF_DECLARE(sun8i_a33_clk_init, "allwinner,sun8i-a33", sun6i_init_clocks);
CLK_OF_DECLARE(sun8i_h3_clk_init, "allwinner,sun8i-h3", sun6i_init_clocks);
static void __init sun9i_init_clocks(struct device_node *node)
{

View file

@ -243,3 +243,15 @@ static void __init sun9i_a80_usb_phy_setup(struct device_node *node)
sunxi_usb_clk_setup(node, &sun9i_a80_usb_phy_data, &a80_usb_phy_lock);
}
CLK_OF_DECLARE(sun9i_a80_usb_phy, "allwinner,sun9i-a80-usb-phy-clk", sun9i_a80_usb_phy_setup);
static const struct usb_clk_data sun8i_h3_usb_clk_data __initconst = {
.clk_mask = BIT(19) | BIT(18) | BIT(17) | BIT(16) |
BIT(11) | BIT(10) | BIT(9) | BIT(8),
.reset_mask = BIT(3) | BIT(2) | BIT(1) | BIT(0),
};
static void __init sun8i_h3_usb_setup(struct device_node *node)
{
sunxi_usb_clk_setup(node, &sun8i_h3_usb_clk_data, &sun4i_a10_usb_lock);
}
CLK_OF_DECLARE(sun8i_h3_usb, "allwinner,sun8i-h3-usb-clk", sun8i_h3_usb_setup);