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 | ||
bdb0066d | 43 | static struct syscon *of_syscon_register(struct device_node *np) |
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 PD |
53 | |
54 | if (!of_device_is_compatible(np, "syscon")) | |
55 | return ERR_PTR(-EINVAL); | |
56 | ||
57 | syscon = kzalloc(sizeof(*syscon), GFP_KERNEL); | |
58 | if (!syscon) | |
59 | return ERR_PTR(-ENOMEM); | |
60 | ||
ca668f0e PZ |
61 | if (of_address_to_resource(np, 0, &res)) { |
62 | ret = -ENOMEM; | |
63 | goto err_map; | |
64 | } | |
65 | ||
66 | base = ioremap(res.start, resource_size(&res)); | |
bdb0066d PD |
67 | if (!base) { |
68 | ret = -ENOMEM; | |
69 | goto err_map; | |
70 | } | |
71 | ||
72 | /* Parse the device's DT node for an endianness specification */ | |
73 | if (of_property_read_bool(np, "big-endian")) | |
74 | syscon_config.val_format_endian = REGMAP_ENDIAN_BIG; | |
d29ccdb3 | 75 | else if (of_property_read_bool(np, "little-endian")) |
bdb0066d | 76 | syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE; |
d29ccdb3 PB |
77 | else if (of_property_read_bool(np, "native-endian")) |
78 | syscon_config.val_format_endian = REGMAP_ENDIAN_NATIVE; | |
bdb0066d | 79 | |
db2fb60c DR |
80 | /* |
81 | * search for reg-io-width property in DT. If it is not provided, | |
82 | * default to 4 bytes. regmap_init_mmio will return an error if values | |
83 | * are invalid so there is no need to check them here. | |
84 | */ | |
85 | ret = of_property_read_u32(np, "reg-io-width", ®_io_width); | |
86 | if (ret) | |
87 | reg_io_width = 4; | |
88 | ||
3bafc09e BW |
89 | ret = of_hwspin_lock_get_id(np, 0); |
90 | if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) { | |
91 | syscon_config.use_hwlock = true; | |
92 | syscon_config.hwlock_id = ret; | |
93 | syscon_config.hwlock_mode = HWLOCK_IRQSTATE; | |
94 | } else if (ret < 0) { | |
95 | switch (ret) { | |
96 | case -ENOENT: | |
97 | /* Ignore missing hwlock, it's optional. */ | |
98 | break; | |
99 | default: | |
100 | pr_err("Failed to retrieve valid hwlock: %d\n", ret); | |
101 | /* fall-through */ | |
102 | case -EPROBE_DEFER: | |
103 | goto err_regmap; | |
104 | } | |
105 | } | |
106 | ||
408d1d57 | 107 | syscon_config.name = of_node_full_name(np); |
db2fb60c DR |
108 | syscon_config.reg_stride = reg_io_width; |
109 | syscon_config.val_bits = reg_io_width * 8; | |
ca668f0e | 110 | syscon_config.max_register = resource_size(&res) - reg_io_width; |
500f9ff5 | 111 | syscon_config.name = of_node_full_name(np); |
db2fb60c | 112 | |
bdb0066d PD |
113 | regmap = regmap_init_mmio(NULL, base, &syscon_config); |
114 | if (IS_ERR(regmap)) { | |
115 | pr_err("regmap init failed\n"); | |
116 | ret = PTR_ERR(regmap); | |
117 | goto err_regmap; | |
118 | } | |
119 | ||
a00406b7 FG |
120 | clk = of_clk_get(np, 0); |
121 | if (IS_ERR(clk)) { | |
122 | ret = PTR_ERR(clk); | |
123 | /* clock is optional */ | |
124 | if (ret != -ENOENT) | |
125 | goto err_clk; | |
126 | } else { | |
127 | ret = regmap_mmio_attach_clk(regmap, clk); | |
128 | if (ret) | |
129 | goto err_attach; | |
130 | } | |
131 | ||
bdb0066d PD |
132 | syscon->regmap = regmap; |
133 | syscon->np = np; | |
134 | ||
135 | spin_lock(&syscon_list_slock); | |
136 | list_add_tail(&syscon->list, &syscon_list); | |
137 | spin_unlock(&syscon_list_slock); | |
87d68730 | 138 | |
bdb0066d PD |
139 | return syscon; |
140 | ||
a00406b7 FG |
141 | err_attach: |
142 | if (!IS_ERR(clk)) | |
143 | clk_put(clk); | |
144 | err_clk: | |
145 | regmap_exit(regmap); | |
bdb0066d PD |
146 | err_regmap: |
147 | iounmap(base); | |
148 | err_map: | |
149 | kfree(syscon); | |
150 | return ERR_PTR(ret); | |
87d68730 DA |
151 | } |
152 | ||
153 | struct regmap *syscon_node_to_regmap(struct device_node *np) | |
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) | |
168 | syscon = of_syscon_register(np); | |
169 | ||
170 | if (IS_ERR(syscon)) | |
171 | return ERR_CAST(syscon); | |
87d68730 DA |
172 | |
173 | return syscon->regmap; | |
174 | } | |
175 | EXPORT_SYMBOL_GPL(syscon_node_to_regmap); | |
176 | ||
177 | struct regmap *syscon_regmap_lookup_by_compatible(const char *s) | |
178 | { | |
179 | struct device_node *syscon_np; | |
180 | struct regmap *regmap; | |
181 | ||
182 | syscon_np = of_find_compatible_node(NULL, NULL, s); | |
183 | if (!syscon_np) | |
184 | return ERR_PTR(-ENODEV); | |
185 | ||
186 | regmap = syscon_node_to_regmap(syscon_np); | |
187 | of_node_put(syscon_np); | |
188 | ||
189 | return regmap; | |
190 | } | |
191 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible); | |
192 | ||
193 | struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np, | |
194 | const char *property) | |
195 | { | |
196 | struct device_node *syscon_np; | |
197 | struct regmap *regmap; | |
198 | ||
45330bb4 PD |
199 | if (property) |
200 | syscon_np = of_parse_phandle(np, property, 0); | |
201 | else | |
202 | syscon_np = np; | |
203 | ||
87d68730 DA |
204 | if (!syscon_np) |
205 | return ERR_PTR(-ENODEV); | |
206 | ||
207 | regmap = syscon_node_to_regmap(syscon_np); | |
208 | of_node_put(syscon_np); | |
209 | ||
210 | return regmap; | |
211 | } | |
212 | EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle); | |
213 | ||
f791be49 | 214 | static int syscon_probe(struct platform_device *pdev) |
87d68730 DA |
215 | { |
216 | struct device *dev = &pdev->dev; | |
29f9b6cf | 217 | struct syscon_platform_data *pdata = dev_get_platdata(dev); |
87d68730 | 218 | struct syscon *syscon; |
c131045d | 219 | struct regmap_config syscon_config = syscon_regmap_config; |
5ab3a89a | 220 | struct resource *res; |
f10111cc | 221 | void __iomem *base; |
87d68730 | 222 | |
5ab3a89a | 223 | syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL); |
87d68730 DA |
224 | if (!syscon) |
225 | return -ENOMEM; | |
226 | ||
5ab3a89a AS |
227 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
228 | if (!res) | |
229 | return -ENOENT; | |
87d68730 | 230 | |
f10111cc AS |
231 | base = devm_ioremap(dev, res->start, resource_size(res)); |
232 | if (!base) | |
5ab3a89a | 233 | return -ENOMEM; |
87d68730 | 234 | |
c131045d | 235 | syscon_config.max_register = res->end - res->start - 3; |
29f9b6cf | 236 | if (pdata) |
c131045d PZ |
237 | syscon_config.name = pdata->label; |
238 | syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config); | |
87d68730 DA |
239 | if (IS_ERR(syscon->regmap)) { |
240 | dev_err(dev, "regmap init failed\n"); | |
241 | return PTR_ERR(syscon->regmap); | |
242 | } | |
243 | ||
87d68730 DA |
244 | platform_set_drvdata(pdev, syscon); |
245 | ||
38d8974e | 246 | dev_dbg(dev, "regmap %pR registered\n", res); |
87d68730 DA |
247 | |
248 | return 0; | |
249 | } | |
250 | ||
5ab3a89a AS |
251 | static const struct platform_device_id syscon_ids[] = { |
252 | { "syscon", }, | |
253 | { } | |
254 | }; | |
87d68730 DA |
255 | |
256 | static struct platform_driver syscon_driver = { | |
257 | .driver = { | |
258 | .name = "syscon", | |
87d68730 DA |
259 | }, |
260 | .probe = syscon_probe, | |
5ab3a89a | 261 | .id_table = syscon_ids, |
87d68730 DA |
262 | }; |
263 | ||
264 | static int __init syscon_init(void) | |
265 | { | |
266 | return platform_driver_register(&syscon_driver); | |
267 | } | |
268 | postcore_initcall(syscon_init); |