Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
87d68730 DA |
2 | /* |
3 | * System Control Driver | |
4 | * | |
5 | * Copyright (C) 2012 Freescale Semiconductor, Inc. | |
6 | * Copyright (C) 2012 Linaro Ltd. | |
7 | * | |
8 | * Author: Dong Aisheng <dong.aisheng@linaro.org> | |
87d68730 DA |
9 | */ |
10 | ||
a00406b7 | 11 | #include <linux/clk.h> |
87d68730 | 12 | #include <linux/err.h> |
3bafc09e | 13 | #include <linux/hwspinlock.h> |
87d68730 | 14 | #include <linux/io.h> |
1345da73 | 15 | #include <linux/init.h> |
bdb0066d | 16 | #include <linux/list.h> |
87d68730 DA |
17 | #include <linux/of.h> |
18 | #include <linux/of_address.h> | |
19 | #include <linux/of_platform.h> | |
29f9b6cf | 20 | #include <linux/platform_data/syscon.h> |
87d68730 DA |
21 | #include <linux/platform_device.h> |
22 | #include <linux/regmap.h> | |
75177dee | 23 | #include <linux/mfd/syscon.h> |
bdb0066d | 24 | #include <linux/slab.h> |
87d68730 DA |
25 | |
26 | static struct platform_driver syscon_driver; | |
27 | ||
bdb0066d PD |
28 | static DEFINE_SPINLOCK(syscon_list_slock); |
29 | static LIST_HEAD(syscon_list); | |
30 | ||
87d68730 | 31 | struct syscon { |
bdb0066d | 32 | struct device_node *np; |
87d68730 | 33 | struct regmap *regmap; |
bdb0066d PD |
34 | struct list_head list; |
35 | }; | |
36 | ||
c131045d | 37 | static const struct regmap_config syscon_regmap_config = { |
bdb0066d PD |
38 | .reg_bits = 32, |
39 | .val_bits = 32, | |
40 | .reg_stride = 4, | |
87d68730 DA |
41 | }; |
42 | ||
39233b7c | 43 | static struct syscon *of_syscon_register(struct device_node *np, bool check_clk) |
87d68730 | 44 | { |
a00406b7 | 45 | struct clk *clk; |
bdb0066d PD |
46 | struct syscon *syscon; |
47 | struct regmap *regmap; | |
48 | void __iomem *base; | |
db2fb60c | 49 | u32 reg_io_width; |
bdb0066d PD |
50 | int ret; |
51 | struct regmap_config syscon_config = syscon_regmap_config; | |
ca668f0e | 52 | struct resource res; |
bdb0066d | 53 | |
bdb0066d PD |
54 | syscon = kzalloc(sizeof(*syscon), GFP_KERNEL); |
55 | if (!syscon) | |
56 | return ERR_PTR(-ENOMEM); | |
57 | ||
ca668f0e PZ |
58 | if (of_address_to_resource(np, 0, &res)) { |
59 | ret = -ENOMEM; | |
60 | goto err_map; | |
61 | } | |
62 | ||
63 | base = ioremap(res.start, resource_size(&res)); | |
bdb0066d PD |
64 | if (!base) { |
65 | ret = -ENOMEM; | |
66 | goto err_map; | |
67 | } | |
68 | ||
69 | /* Parse the device's DT node for an endianness specification */ | |
70 | if (of_property_read_bool(np, "big-endian")) | |
71 | syscon_config.val_format_endian = REGMAP_ENDIAN_BIG; | |
d29ccdb3 | 72 | else if (of_property_read_bool(np, "little-endian")) |
bdb0066d | 73 | syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE; |
d29ccdb3 PB |
74 | else if (of_property_read_bool(np, "native-endian")) |
75 | syscon_config.val_format_endian = REGMAP_ENDIAN_NATIVE; | |
bdb0066d | 76 | |
db2fb60c DR |
77 | /* |
78 | * search for reg-io-width property in DT. If it is not provided, | |
79 | * default to 4 bytes. regmap_init_mmio will return an error if values | |
80 | * are invalid so there is no need to check them here. | |
81 | */ | |
82 | ret = of_property_read_u32(np, "reg-io-width", ®_io_width); | |
83 | if (ret) | |
84 | reg_io_width = 4; | |
85 | ||
3bafc09e BW |
86 | ret = of_hwspin_lock_get_id(np, 0); |
87 | if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) { | |
88 | syscon_config.use_hwlock = true; | |
89 | syscon_config.hwlock_id = ret; | |
90 | syscon_config.hwlock_mode = HWLOCK_IRQSTATE; | |
91 | } else if (ret < 0) { | |
92 | switch (ret) { | |
93 | case -ENOENT: | |
94 | /* Ignore missing hwlock, it's optional. */ | |
95 | break; | |
96 | default: | |
97 | pr_err("Failed to retrieve valid hwlock: %d\n", ret); | |
98 | /* fall-through */ | |
99 | case -EPROBE_DEFER: | |
100 | goto err_regmap; | |
101 | } | |
102 | } | |
103 | ||
408d1d57 | 104 | syscon_config.name = of_node_full_name(np); |
db2fb60c DR |
105 | syscon_config.reg_stride = reg_io_width; |
106 | syscon_config.val_bits = reg_io_width * 8; | |
ca668f0e | 107 | syscon_config.max_register = resource_size(&res) - reg_io_width; |
500f9ff5 | 108 | syscon_config.name = of_node_full_name(np); |
db2fb60c | 109 | |
bdb0066d PD |
110 | regmap = regmap_init_mmio(NULL, base, &syscon_config); |
111 | if (IS_ERR(regmap)) { | |
112 | pr_err("regmap init failed\n"); | |
113 | ret = PTR_ERR(regmap); | |
114 | goto err_regmap; | |
115 | } | |
116 | ||
39233b7c PC |
117 | if (check_clk) { |
118 | clk = of_clk_get(np, 0); | |
119 | if (IS_ERR(clk)) { | |
120 | ret = PTR_ERR(clk); | |
121 | /* clock is optional */ | |
122 | if (ret != -ENOENT) | |
123 | goto err_clk; | |
124 | } else { | |
125 | ret = regmap_mmio_attach_clk(regmap, clk); | |
126 | if (ret) | |
127 | goto err_attach; | |
128 | } | |
a00406b7 FG |
129 | } |
130 | ||
bdb0066d PD |
131 | syscon->regmap = regmap; |
132 | syscon->np = np; | |
133 | ||
134 | spin_lock(&syscon_list_slock); | |
135 | list_add_tail(&syscon->list, &syscon_list); | |
136 | spin_unlock(&syscon_list_slock); | |
87d68730 | 137 | |
bdb0066d PD |
138 | return syscon; |
139 | ||
a00406b7 FG |
140 | err_attach: |
141 | if (!IS_ERR(clk)) | |
142 | clk_put(clk); | |
143 | err_clk: | |
144 | regmap_exit(regmap); | |
bdb0066d PD |
145 | err_regmap: |
146 | iounmap(base); | |
147 | err_map: | |
148 | kfree(syscon); | |
149 | return ERR_PTR(ret); | |
87d68730 DA |
150 | } |
151 | ||
39233b7c PC |
152 | static struct regmap *device_node_get_regmap(struct device_node *np, |
153 | bool check_clk) | |
87d68730 | 154 | { |
bdb0066d | 155 | struct syscon *entry, *syscon = NULL; |
87d68730 | 156 | |
bdb0066d | 157 | spin_lock(&syscon_list_slock); |
87d68730 | 158 | |
bdb0066d PD |
159 | list_for_each_entry(entry, &syscon_list, list) |
160 | if (entry->np == np) { | |
161 | syscon = entry; | |
162 | break; | |
163 | } | |
164 | ||
165 | spin_unlock(&syscon_list_slock); | |
166 | ||
167 | if (!syscon) | |
39233b7c | 168 | syscon = of_syscon_register(np, check_clk); |
bdb0066d PD |
169 | |
170 | if (IS_ERR(syscon)) | |
171 | return ERR_CAST(syscon); | |
87d68730 DA |
172 | |
173 | return syscon->regmap; | |
174 | } | |
39233b7c PC |
175 | |
176 | struct regmap *device_node_to_regmap(struct device_node *np) | |
177 | { | |
178 | return device_node_get_regmap(np, false); | |
179 | } | |
180 | EXPORT_SYMBOL_GPL(device_node_to_regmap); | |
181 | ||
182 | struct regmap *syscon_node_to_regmap(struct device_node *np) | |
183 | { | |
184 | if (!of_device_is_compatible(np, "syscon")) | |
185 | return ERR_PTR(-EINVAL); | |
186 | ||
187 | return device_node_get_regmap(np, true); | |
188 | } | |
87d68730 DA |
189 | EXPORT_SYMBOL_GPL(syscon_node_to_regmap); |
190 | ||
191 | struct regmap *syscon_regmap_lookup_by_compatible(const char *s) | |
192 | { | |
193 | struct device_node *syscon_np; | |
194 | struct regmap *regmap; | |
195 | ||
196 | syscon_np = of_find_compatible_node(NULL, NULL, s); | |
197 | if (!syscon_np) | |
198 | return ERR_PTR(-ENODEV); | |
199 | ||
200 | regmap = syscon_node_to_regmap(syscon_np); | |
201 | of_node_put(syscon_np); | |
202 | ||
203 | return regmap; | |
204 | } | |
205 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible); | |
206 | ||
207 | struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np, | |
208 | const char *property) | |
209 | { | |
210 | struct device_node *syscon_np; | |
211 | struct regmap *regmap; | |
212 | ||
45330bb4 PD |
213 | if (property) |
214 | syscon_np = of_parse_phandle(np, property, 0); | |
215 | else | |
216 | syscon_np = np; | |
217 | ||
87d68730 DA |
218 | if (!syscon_np) |
219 | return ERR_PTR(-ENODEV); | |
220 | ||
221 | regmap = syscon_node_to_regmap(syscon_np); | |
222 | of_node_put(syscon_np); | |
223 | ||
224 | return regmap; | |
225 | } | |
226 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle); | |
227 | ||
f791be49 | 228 | static int syscon_probe(struct platform_device *pdev) |
87d68730 DA |
229 | { |
230 | struct device *dev = &pdev->dev; | |
29f9b6cf | 231 | struct syscon_platform_data *pdata = dev_get_platdata(dev); |
87d68730 | 232 | struct syscon *syscon; |
c131045d | 233 | struct regmap_config syscon_config = syscon_regmap_config; |
5ab3a89a | 234 | struct resource *res; |
f10111cc | 235 | void __iomem *base; |
87d68730 | 236 | |
5ab3a89a | 237 | syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL); |
87d68730 DA |
238 | if (!syscon) |
239 | return -ENOMEM; | |
240 | ||
5ab3a89a AS |
241 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
242 | if (!res) | |
243 | return -ENOENT; | |
87d68730 | 244 | |
f10111cc AS |
245 | base = devm_ioremap(dev, res->start, resource_size(res)); |
246 | if (!base) | |
5ab3a89a | 247 | return -ENOMEM; |
87d68730 | 248 | |
c131045d | 249 | syscon_config.max_register = res->end - res->start - 3; |
29f9b6cf | 250 | if (pdata) |
c131045d PZ |
251 | syscon_config.name = pdata->label; |
252 | syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config); | |
87d68730 DA |
253 | if (IS_ERR(syscon->regmap)) { |
254 | dev_err(dev, "regmap init failed\n"); | |
255 | return PTR_ERR(syscon->regmap); | |
256 | } | |
257 | ||
87d68730 DA |
258 | platform_set_drvdata(pdev, syscon); |
259 | ||
38d8974e | 260 | dev_dbg(dev, "regmap %pR registered\n", res); |
87d68730 DA |
261 | |
262 | return 0; | |
263 | } | |
264 | ||
5ab3a89a AS |
265 | static const struct platform_device_id syscon_ids[] = { |
266 | { "syscon", }, | |
267 | { } | |
268 | }; | |
87d68730 DA |
269 | |
270 | static struct platform_driver syscon_driver = { | |
271 | .driver = { | |
272 | .name = "syscon", | |
87d68730 DA |
273 | }, |
274 | .probe = syscon_probe, | |
5ab3a89a | 275 | .id_table = syscon_ids, |
87d68730 DA |
276 | }; |
277 | ||
278 | static int __init syscon_init(void) | |
279 | { | |
280 | return platform_driver_register(&syscon_driver); | |
281 | } | |
282 | postcore_initcall(syscon_init); |