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