diff options
author | Gregory CLEMENT <gregory.clement@free-electrons.com> | 2016-07-19 09:42:20 -0400 |
---|---|---|
committer | Stephen Boyd <sboyd@codeaurora.org> | 2016-08-15 17:10:20 -0400 |
commit | 96265523ace51b71dc1b2fd445a93c4b2f9bedb8 (patch) | |
tree | 3b764253866da0ad1ea03cb443ffff21d0ab2d0b | |
parent | c302588e17d084408210a45a0fa6243978a0f785 (diff) |
clk: mvebu Add the time base generator clocks for Armada 3700
These clocks are children of the xtal clock and each one can be selected
as a source for the peripheral clocks.
According to the datasheet it should be possible to modify their rate,
but currently it is not supported.
Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
-rw-r--r-- | drivers/clk/mvebu/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/mvebu/armada-37xx-tbg.c | 158 |
2 files changed, 159 insertions, 0 deletions
diff --git a/drivers/clk/mvebu/Makefile b/drivers/clk/mvebu/Makefile index 4257a36d0219..72e3512a9d4a 100644 --- a/drivers/clk/mvebu/Makefile +++ b/drivers/clk/mvebu/Makefile | |||
@@ -7,6 +7,7 @@ obj-$(CONFIG_ARMADA_375_CLK) += armada-375.o | |||
7 | obj-$(CONFIG_ARMADA_38X_CLK) += armada-38x.o | 7 | obj-$(CONFIG_ARMADA_38X_CLK) += armada-38x.o |
8 | obj-$(CONFIG_ARMADA_39X_CLK) += armada-39x.o | 8 | obj-$(CONFIG_ARMADA_39X_CLK) += armada-39x.o |
9 | obj-$(CONFIG_ARMADA_37XX_CLK) += armada-37xx-xtal.o | 9 | obj-$(CONFIG_ARMADA_37XX_CLK) += armada-37xx-xtal.o |
10 | obj-$(CONFIG_ARMADA_37XX_CLK) += armada-37xx-tbg.o | ||
10 | obj-$(CONFIG_ARMADA_XP_CLK) += armada-xp.o | 11 | obj-$(CONFIG_ARMADA_XP_CLK) += armada-xp.o |
11 | obj-$(CONFIG_ARMADA_AP806_SYSCON) += ap806-system-controller.o | 12 | obj-$(CONFIG_ARMADA_AP806_SYSCON) += ap806-system-controller.o |
12 | obj-$(CONFIG_ARMADA_CP110_SYSCON) += cp110-system-controller.o | 13 | obj-$(CONFIG_ARMADA_CP110_SYSCON) += cp110-system-controller.o |
diff --git a/drivers/clk/mvebu/armada-37xx-tbg.c b/drivers/clk/mvebu/armada-37xx-tbg.c new file mode 100644 index 000000000000..aa80db11f543 --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-tbg.c | |||
@@ -0,0 +1,158 @@ | |||
1 | /* | ||
2 | * Marvell Armada 37xx SoC Time Base Generator clocks | ||
3 | * | ||
4 | * Copyright (C) 2016 Marvell | ||
5 | * | ||
6 | * Gregory CLEMENT <gregory.clement@free-electrons.com> | ||
7 | * | ||
8 | * This file is licensed under the terms of the GNU General Public | ||
9 | * License version 2 or later. This program is licensed "as is" | ||
10 | * without any warranty of any kind, whether express or implied. | ||
11 | */ | ||
12 | |||
13 | #include <linux/clk-provider.h> | ||
14 | #include <linux/clk.h> | ||
15 | #include <linux/of.h> | ||
16 | #include <linux/of_address.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/slab.h> | ||
19 | |||
20 | #define NUM_TBG 4 | ||
21 | |||
22 | #define TBG_CTRL0 0x4 | ||
23 | #define TBG_CTRL1 0x8 | ||
24 | #define TBG_CTRL7 0x20 | ||
25 | #define TBG_CTRL8 0x30 | ||
26 | |||
27 | #define TBG_DIV_MASK 0x1FF | ||
28 | |||
29 | #define TBG_A_REFDIV 0 | ||
30 | #define TBG_B_REFDIV 16 | ||
31 | |||
32 | #define TBG_A_FBDIV 2 | ||
33 | #define TBG_B_FBDIV 18 | ||
34 | |||
35 | #define TBG_A_VCODIV_SE 0 | ||
36 | #define TBG_B_VCODIV_SE 16 | ||
37 | |||
38 | #define TBG_A_VCODIV_DIFF 1 | ||
39 | #define TBG_B_VCODIV_DIFF 17 | ||
40 | |||
41 | struct tbg_def { | ||
42 | char *name; | ||
43 | u32 refdiv_offset; | ||
44 | u32 fbdiv_offset; | ||
45 | u32 vcodiv_reg; | ||
46 | u32 vcodiv_offset; | ||
47 | }; | ||
48 | |||
49 | static const struct tbg_def tbg[NUM_TBG] = { | ||
50 | {"TBG-A-P", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL8, TBG_A_VCODIV_DIFF}, | ||
51 | {"TBG-B-P", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL8, TBG_B_VCODIV_DIFF}, | ||
52 | {"TBG-A-S", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL1, TBG_A_VCODIV_SE}, | ||
53 | {"TBG-B-S", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL1, TBG_B_VCODIV_SE}, | ||
54 | }; | ||
55 | |||
56 | static unsigned int tbg_get_mult(void __iomem *reg, const struct tbg_def *ptbg) | ||
57 | { | ||
58 | u32 val; | ||
59 | |||
60 | val = readl(reg + TBG_CTRL0); | ||
61 | |||
62 | return ((val >> ptbg->fbdiv_offset) & TBG_DIV_MASK) << 2; | ||
63 | } | ||
64 | |||
65 | static unsigned int tbg_get_div(void __iomem *reg, const struct tbg_def *ptbg) | ||
66 | { | ||
67 | u32 val; | ||
68 | unsigned int div; | ||
69 | |||
70 | val = readl(reg + TBG_CTRL7); | ||
71 | |||
72 | div = (val >> ptbg->refdiv_offset) & TBG_DIV_MASK; | ||
73 | if (div == 0) | ||
74 | div = 1; | ||
75 | val = readl(reg + ptbg->vcodiv_reg); | ||
76 | |||
77 | div *= 1 << ((val >> ptbg->vcodiv_offset) & TBG_DIV_MASK); | ||
78 | |||
79 | return div; | ||
80 | } | ||
81 | |||
82 | |||
83 | static int armada_3700_tbg_clock_probe(struct platform_device *pdev) | ||
84 | { | ||
85 | struct device_node *np = pdev->dev.of_node; | ||
86 | struct clk_hw_onecell_data *hw_tbg_data; | ||
87 | struct device *dev = &pdev->dev; | ||
88 | const char *parent_name; | ||
89 | struct resource *res; | ||
90 | struct clk *parent; | ||
91 | void __iomem *reg; | ||
92 | int i, ret; | ||
93 | |||
94 | hw_tbg_data = devm_kzalloc(&pdev->dev, sizeof(*hw_tbg_data) | ||
95 | + sizeof(*hw_tbg_data->hws) * NUM_TBG, | ||
96 | GFP_KERNEL); | ||
97 | if (!hw_tbg_data) | ||
98 | return -ENOMEM; | ||
99 | hw_tbg_data->num = NUM_TBG; | ||
100 | platform_set_drvdata(pdev, hw_tbg_data); | ||
101 | |||
102 | parent = devm_clk_get(dev, NULL); | ||
103 | if (IS_ERR(parent)) { | ||
104 | dev_err(dev, "Could get the clock parent\n"); | ||
105 | return -EINVAL; | ||
106 | } | ||
107 | parent_name = __clk_get_name(parent); | ||
108 | |||
109 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
110 | reg = devm_ioremap_resource(dev, res); | ||
111 | if (IS_ERR(reg)) | ||
112 | return PTR_ERR(reg); | ||
113 | |||
114 | for (i = 0; i < NUM_TBG; i++) { | ||
115 | const char *name; | ||
116 | unsigned int mult, div; | ||
117 | |||
118 | name = tbg[i].name; | ||
119 | mult = tbg_get_mult(reg, &tbg[i]); | ||
120 | div = tbg_get_div(reg, &tbg[i]); | ||
121 | hw_tbg_data->hws[i] = clk_hw_register_fixed_factor(NULL, name, | ||
122 | parent_name, 0, mult, div); | ||
123 | if (IS_ERR(hw_tbg_data->hws[i])) | ||
124 | dev_err(dev, "Can't register TBG clock %s\n", name); | ||
125 | } | ||
126 | |||
127 | ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, hw_tbg_data); | ||
128 | |||
129 | return ret; | ||
130 | } | ||
131 | |||
132 | static int armada_3700_tbg_clock_remove(struct platform_device *pdev) | ||
133 | { | ||
134 | int i; | ||
135 | struct clk_hw_onecell_data *hw_tbg_data = platform_get_drvdata(pdev); | ||
136 | |||
137 | of_clk_del_provider(pdev->dev.of_node); | ||
138 | for (i = 0; i < hw_tbg_data->num; i++) | ||
139 | clk_hw_unregister_fixed_factor(hw_tbg_data->hws[i]); | ||
140 | |||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | static const struct of_device_id armada_3700_tbg_clock_of_match[] = { | ||
145 | { .compatible = "marvell,armada-3700-tbg-clock", }, | ||
146 | { } | ||
147 | }; | ||
148 | |||
149 | static struct platform_driver armada_3700_tbg_clock_driver = { | ||
150 | .probe = armada_3700_tbg_clock_probe, | ||
151 | .remove = armada_3700_tbg_clock_remove, | ||
152 | .driver = { | ||
153 | .name = "marvell-armada-3700-tbg-clock", | ||
154 | .of_match_table = armada_3700_tbg_clock_of_match, | ||
155 | }, | ||
156 | }; | ||
157 | |||
158 | builtin_platform_driver(armada_3700_tbg_clock_driver); | ||