]>
Commit | Line | Data |
---|---|---|
6e973d2c PM |
1 | /* |
2 | * This program is free software; you can redistribute it and/or modify | |
3 | * it under the terms of the GNU General Public License version 2 as | |
4 | * published by the Free Software Foundation. | |
5 | * | |
6 | * This program is distributed in the hope that it will be useful, | |
7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
9 | * GNU General Public License for more details. | |
10 | * | |
11 | * Copyright (C) 2013 ARM Limited | |
12 | */ | |
13 | ||
14 | #include <linux/amba/sp810.h> | |
6d31e3b2 SB |
15 | #include <linux/slab.h> |
16 | #include <linux/clk.h> | |
6e973d2c PM |
17 | #include <linux/clk-provider.h> |
18 | #include <linux/err.h> | |
62e59c4e | 19 | #include <linux/io.h> |
6e973d2c PM |
20 | #include <linux/of.h> |
21 | #include <linux/of_address.h> | |
22 | ||
23 | #define to_clk_sp810_timerclken(_hw) \ | |
24 | container_of(_hw, struct clk_sp810_timerclken, hw) | |
25 | ||
26 | struct clk_sp810; | |
27 | ||
28 | struct clk_sp810_timerclken { | |
29 | struct clk_hw hw; | |
30 | struct clk *clk; | |
31 | struct clk_sp810 *sp810; | |
32 | int channel; | |
33 | }; | |
34 | ||
35 | struct clk_sp810 { | |
36 | struct device_node *node; | |
6e973d2c PM |
37 | void __iomem *base; |
38 | spinlock_t lock; | |
39 | struct clk_sp810_timerclken timerclken[4]; | |
6e973d2c PM |
40 | }; |
41 | ||
42 | static u8 clk_sp810_timerclken_get_parent(struct clk_hw *hw) | |
43 | { | |
44 | struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); | |
45 | u32 val = readl(timerclken->sp810->base + SCCTRL); | |
46 | ||
47 | return !!(val & (1 << SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel))); | |
48 | } | |
49 | ||
50 | static int clk_sp810_timerclken_set_parent(struct clk_hw *hw, u8 index) | |
51 | { | |
52 | struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); | |
53 | struct clk_sp810 *sp810 = timerclken->sp810; | |
54 | u32 val, shift = SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel); | |
55 | unsigned long flags = 0; | |
56 | ||
57 | if (WARN_ON(index > 1)) | |
58 | return -EINVAL; | |
59 | ||
60 | spin_lock_irqsave(&sp810->lock, flags); | |
61 | ||
62 | val = readl(sp810->base + SCCTRL); | |
63 | val &= ~(1 << shift); | |
64 | val |= index << shift; | |
65 | writel(val, sp810->base + SCCTRL); | |
66 | ||
67 | spin_unlock_irqrestore(&sp810->lock, flags); | |
68 | ||
69 | return 0; | |
70 | } | |
71 | ||
6e973d2c | 72 | static const struct clk_ops clk_sp810_timerclken_ops = { |
6e973d2c PM |
73 | .get_parent = clk_sp810_timerclken_get_parent, |
74 | .set_parent = clk_sp810_timerclken_set_parent, | |
75 | }; | |
76 | ||
14b260ce | 77 | static struct clk *clk_sp810_timerclken_of_get(struct of_phandle_args *clkspec, |
6e973d2c PM |
78 | void *data) |
79 | { | |
80 | struct clk_sp810 *sp810 = data; | |
81 | ||
3294bee8 DC |
82 | if (WARN_ON(clkspec->args_count != 1 || |
83 | clkspec->args[0] >= ARRAY_SIZE(sp810->timerclken))) | |
6e973d2c PM |
84 | return NULL; |
85 | ||
86 | return sp810->timerclken[clkspec->args[0]].clk; | |
87 | } | |
88 | ||
11bee5e1 | 89 | static void __init clk_sp810_of_setup(struct device_node *node) |
6e973d2c PM |
90 | { |
91 | struct clk_sp810 *sp810 = kzalloc(sizeof(*sp810), GFP_KERNEL); | |
92 | const char *parent_names[2]; | |
62f47711 | 93 | int num = ARRAY_SIZE(parent_names); |
6e973d2c PM |
94 | char name[12]; |
95 | struct clk_init_data init; | |
ec7957a6 | 96 | static int instance; |
6e973d2c | 97 | int i; |
62f47711 | 98 | bool deprecated; |
6e973d2c | 99 | |
59fe6631 | 100 | if (!sp810) |
6e973d2c | 101 | return; |
6e973d2c | 102 | |
62f47711 | 103 | if (of_clk_parent_fill(node, parent_names, num) != num) { |
6e973d2c | 104 | pr_warn("Failed to obtain parent clocks for SP810!\n"); |
47c5ee34 | 105 | kfree(sp810); |
6e973d2c PM |
106 | return; |
107 | } | |
108 | ||
109 | sp810->node = node; | |
110 | sp810->base = of_iomap(node, 0); | |
111 | spin_lock_init(&sp810->lock); | |
112 | ||
113 | init.name = name; | |
114 | init.ops = &clk_sp810_timerclken_ops; | |
354e1210 | 115 | init.flags = 0; |
6e973d2c | 116 | init.parent_names = parent_names; |
62f47711 SB |
117 | init.num_parents = num; |
118 | ||
119 | deprecated = !of_find_property(node, "assigned-clock-parents", NULL); | |
6e973d2c PM |
120 | |
121 | for (i = 0; i < ARRAY_SIZE(sp810->timerclken); i++) { | |
ec7957a6 | 122 | snprintf(name, sizeof(name), "sp810_%d_%d", instance, i); |
6e973d2c PM |
123 | |
124 | sp810->timerclken[i].sp810 = sp810; | |
125 | sp810->timerclken[i].channel = i; | |
126 | sp810->timerclken[i].hw.init = &init; | |
127 | ||
62f47711 SB |
128 | /* |
129 | * If DT isn't setting the parent, force it to be | |
130 | * the 1 MHz clock without going through the framework. | |
131 | * We do this before clk_register() so that it can determine | |
132 | * the parent and setup the tree properly. | |
133 | */ | |
134 | if (deprecated) | |
135 | init.ops->set_parent(&sp810->timerclken[i].hw, 1); | |
136 | ||
6e973d2c PM |
137 | sp810->timerclken[i].clk = clk_register(NULL, |
138 | &sp810->timerclken[i].hw); | |
139 | WARN_ON(IS_ERR(sp810->timerclken[i].clk)); | |
140 | } | |
141 | ||
142 | of_clk_add_provider(node, clk_sp810_timerclken_of_get, sp810); | |
ec7957a6 | 143 | instance++; |
6e973d2c PM |
144 | } |
145 | CLK_OF_DECLARE(sp810, "arm,sp810", clk_sp810_of_setup); |