diff options
author | Rafał Miłecki <rafal@milecki.pl> | 2016-09-13 03:06:04 -0400 |
---|---|---|
committer | Stephen Boyd <sboyd@codeaurora.org> | 2016-09-16 19:31:29 -0400 |
commit | bd8dd593f7d2211f2273e05741d157b0c8d020ae (patch) | |
tree | ec532241f43a7179811fece9ae464d5fa0413d3c | |
parent | dc19b6f5be5b6f82548981adc1c5019b7a82d7c7 (diff) |
clk: bcm: Add driver for BCM53573 ILP clock
This clock is present on BCM53573 devices (including BCM47189) that use
Cortex-A7. ILP is a part of PMU (Power Management Unit) multi-function
device so we use syscon (and regmap) for it.
Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
Acked-by: Rob Herring <robh@kernel.org>
[sboyd@codeaurora.org: Remove 0 from clk_init_data to silence sparse]
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
-rw-r--r-- | Documentation/devicetree/bindings/clock/brcm,bcm53573-ilp.txt | 36 | ||||
-rw-r--r-- | drivers/clk/bcm/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/bcm/clk-bcm53573-ilp.c | 148 |
3 files changed, 185 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/clock/brcm,bcm53573-ilp.txt b/Documentation/devicetree/bindings/clock/brcm,bcm53573-ilp.txt new file mode 100644 index 000000000000..2ebb107331dd --- /dev/null +++ b/Documentation/devicetree/bindings/clock/brcm,bcm53573-ilp.txt | |||
@@ -0,0 +1,36 @@ | |||
1 | Broadcom BCM53573 ILP clock | ||
2 | =========================== | ||
3 | |||
4 | This binding uses the common clock binding: | ||
5 | Documentation/devicetree/bindings/clock/clock-bindings.txt | ||
6 | |||
7 | This binding is used for ILP clock (sometimes referred as "slow clock") | ||
8 | on Broadcom BCM53573 devices using Cortex-A7 CPU. | ||
9 | |||
10 | ILP's rate has to be calculated on runtime and it depends on ALP clock | ||
11 | which has to be referenced. | ||
12 | |||
13 | This clock is part of PMU (Power Management Unit), a Broadcom's device | ||
14 | handing power-related aspects. Its node must be sub-node of the PMU | ||
15 | device. | ||
16 | |||
17 | Required properties: | ||
18 | - compatible: "brcm,bcm53573-ilp" | ||
19 | - clocks: has to reference an ALP clock | ||
20 | - #clock-cells: should be <0> | ||
21 | - clock-output-names: from common clock bindings, should contain clock | ||
22 | name | ||
23 | |||
24 | Example: | ||
25 | |||
26 | pmu@18012000 { | ||
27 | compatible = "simple-mfd", "syscon"; | ||
28 | reg = <0x18012000 0x00001000>; | ||
29 | |||
30 | ilp { | ||
31 | compatible = "brcm,bcm53573-ilp"; | ||
32 | clocks = <&alp>; | ||
33 | #clock-cells = <0>; | ||
34 | clock-output-names = "ilp"; | ||
35 | }; | ||
36 | }; | ||
diff --git a/drivers/clk/bcm/Makefile b/drivers/clk/bcm/Makefile index 123f377da0d0..d9dc848f18c9 100644 --- a/drivers/clk/bcm/Makefile +++ b/drivers/clk/bcm/Makefile | |||
@@ -6,6 +6,7 @@ obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm21664.o | |||
6 | obj-$(CONFIG_COMMON_CLK_IPROC) += clk-iproc-armpll.o clk-iproc-pll.o clk-iproc-asiu.o | 6 | obj-$(CONFIG_COMMON_CLK_IPROC) += clk-iproc-armpll.o clk-iproc-pll.o clk-iproc-asiu.o |
7 | obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o | 7 | obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o |
8 | obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835-aux.o | 8 | obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835-aux.o |
9 | obj-$(CONFIG_ARCH_BCM_53573) += clk-bcm53573-ilp.o | ||
9 | obj-$(CONFIG_CLK_BCM_CYGNUS) += clk-cygnus.o | 10 | obj-$(CONFIG_CLK_BCM_CYGNUS) += clk-cygnus.o |
10 | obj-$(CONFIG_CLK_BCM_NSP) += clk-nsp.o | 11 | obj-$(CONFIG_CLK_BCM_NSP) += clk-nsp.o |
11 | obj-$(CONFIG_CLK_BCM_NS2) += clk-ns2.o | 12 | obj-$(CONFIG_CLK_BCM_NS2) += clk-ns2.o |
diff --git a/drivers/clk/bcm/clk-bcm53573-ilp.c b/drivers/clk/bcm/clk-bcm53573-ilp.c new file mode 100644 index 000000000000..36eb3716ffb0 --- /dev/null +++ b/drivers/clk/bcm/clk-bcm53573-ilp.c | |||
@@ -0,0 +1,148 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 as | ||
6 | * published by the Free Software Foundation. | ||
7 | */ | ||
8 | |||
9 | #include <linux/clk-provider.h> | ||
10 | #include <linux/err.h> | ||
11 | #include <linux/io.h> | ||
12 | #include <linux/mfd/syscon.h> | ||
13 | #include <linux/of.h> | ||
14 | #include <linux/of_address.h> | ||
15 | #include <linux/regmap.h> | ||
16 | #include <linux/slab.h> | ||
17 | |||
18 | #define PMU_XTAL_FREQ_RATIO 0x66c | ||
19 | #define XTAL_ALP_PER_4ILP 0x00001fff | ||
20 | #define XTAL_CTL_EN 0x80000000 | ||
21 | #define PMU_SLOW_CLK_PERIOD 0x6dc | ||
22 | |||
23 | struct bcm53573_ilp { | ||
24 | struct clk_hw hw; | ||
25 | struct regmap *regmap; | ||
26 | }; | ||
27 | |||
28 | static int bcm53573_ilp_enable(struct clk_hw *hw) | ||
29 | { | ||
30 | struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw); | ||
31 | |||
32 | regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0x10199); | ||
33 | regmap_write(ilp->regmap, 0x674, 0x10000); | ||
34 | |||
35 | return 0; | ||
36 | } | ||
37 | |||
38 | static void bcm53573_ilp_disable(struct clk_hw *hw) | ||
39 | { | ||
40 | struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw); | ||
41 | |||
42 | regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0); | ||
43 | regmap_write(ilp->regmap, 0x674, 0); | ||
44 | } | ||
45 | |||
46 | static unsigned long bcm53573_ilp_recalc_rate(struct clk_hw *hw, | ||
47 | unsigned long parent_rate) | ||
48 | { | ||
49 | struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw); | ||
50 | struct regmap *regmap = ilp->regmap; | ||
51 | u32 last_val, cur_val; | ||
52 | int sum = 0, num = 0, loop_num = 0; | ||
53 | int avg; | ||
54 | |||
55 | /* Enable measurement */ | ||
56 | regmap_write(regmap, PMU_XTAL_FREQ_RATIO, XTAL_CTL_EN); | ||
57 | |||
58 | /* Read initial value */ | ||
59 | regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &last_val); | ||
60 | last_val &= XTAL_ALP_PER_4ILP; | ||
61 | |||
62 | /* | ||
63 | * At minimum we should loop for a bit to let hardware do the | ||
64 | * measurement. This isn't very accurate however, so for a better | ||
65 | * precision lets try getting 20 different values for and use average. | ||
66 | */ | ||
67 | while (num < 20) { | ||
68 | regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &cur_val); | ||
69 | cur_val &= XTAL_ALP_PER_4ILP; | ||
70 | |||
71 | if (cur_val != last_val) { | ||
72 | /* Got different value, use it */ | ||
73 | sum += cur_val; | ||
74 | num++; | ||
75 | loop_num = 0; | ||
76 | last_val = cur_val; | ||
77 | } else if (++loop_num > 5000) { | ||
78 | /* Same value over and over, give up */ | ||
79 | sum += cur_val; | ||
80 | num++; | ||
81 | break; | ||
82 | } | ||
83 | |||
84 | cpu_relax(); | ||
85 | } | ||
86 | |||
87 | /* Disable measurement to save power */ | ||
88 | regmap_write(regmap, PMU_XTAL_FREQ_RATIO, 0x0); | ||
89 | |||
90 | avg = sum / num; | ||
91 | |||
92 | return parent_rate * 4 / avg; | ||
93 | } | ||
94 | |||
95 | static const struct clk_ops bcm53573_ilp_clk_ops = { | ||
96 | .enable = bcm53573_ilp_enable, | ||
97 | .disable = bcm53573_ilp_disable, | ||
98 | .recalc_rate = bcm53573_ilp_recalc_rate, | ||
99 | }; | ||
100 | |||
101 | static void bcm53573_ilp_init(struct device_node *np) | ||
102 | { | ||
103 | struct bcm53573_ilp *ilp; | ||
104 | struct clk_init_data init = { }; | ||
105 | const char *parent_name; | ||
106 | int err; | ||
107 | |||
108 | ilp = kzalloc(sizeof(*ilp), GFP_KERNEL); | ||
109 | if (!ilp) | ||
110 | return; | ||
111 | |||
112 | parent_name = of_clk_get_parent_name(np, 0); | ||
113 | if (!parent_name) { | ||
114 | err = -ENOENT; | ||
115 | goto err_free_ilp; | ||
116 | } | ||
117 | |||
118 | ilp->regmap = syscon_node_to_regmap(of_get_parent(np)); | ||
119 | if (IS_ERR(ilp->regmap)) { | ||
120 | err = PTR_ERR(ilp->regmap); | ||
121 | goto err_free_ilp; | ||
122 | } | ||
123 | |||
124 | init.name = np->name; | ||
125 | init.ops = &bcm53573_ilp_clk_ops; | ||
126 | init.parent_names = &parent_name; | ||
127 | init.num_parents = 1; | ||
128 | |||
129 | ilp->hw.init = &init; | ||
130 | err = clk_hw_register(NULL, &ilp->hw); | ||
131 | if (err) | ||
132 | goto err_free_ilp; | ||
133 | |||
134 | err = of_clk_add_hw_provider(np, of_clk_hw_simple_get, &ilp->hw); | ||
135 | if (err) | ||
136 | goto err_clk_hw_unregister; | ||
137 | |||
138 | return; | ||
139 | |||
140 | err_clk_hw_unregister: | ||
141 | clk_hw_unregister(&ilp->hw); | ||
142 | err_free_ilp: | ||
143 | kfree(ilp); | ||
144 | pr_err("Failed to init ILP clock: %d\n", err); | ||
145 | } | ||
146 | |||
147 | /* We need it very early for arch code, before device model gets ready */ | ||
148 | CLK_OF_DECLARE(bcm53573_ilp_clk, "brcm,bcm53573-ilp", bcm53573_ilp_init); | ||