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:
Коммит
cf87a88f51
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
||||
|
|
|
@ -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[] = {
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче