Blame view

drivers/mfd/syscon.c 6.04 KB
87d687301   Dong Aisheng   mfd: Add syscon d...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  /*
   * System Control Driver
   *
   * Copyright (C) 2012 Freescale Semiconductor, Inc.
   * Copyright (C) 2012 Linaro Ltd.
   *
   * Author: Dong Aisheng <dong.aisheng@linaro.org>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
   * (at your option) any later version.
   */
  
  #include <linux/err.h>
  #include <linux/io.h>
  #include <linux/module.h>
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
18
  #include <linux/list.h>
87d687301   Dong Aisheng   mfd: Add syscon d...
19
20
21
  #include <linux/of.h>
  #include <linux/of_address.h>
  #include <linux/of_platform.h>
29f9b6cf7   Pawel Moll   mfd: syscon: Add ...
22
  #include <linux/platform_data/syscon.h>
87d687301   Dong Aisheng   mfd: Add syscon d...
23
24
  #include <linux/platform_device.h>
  #include <linux/regmap.h>
75177deee   Fabio Estevam   mfd: syscon: Fix ...
25
  #include <linux/mfd/syscon.h>
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
26
  #include <linux/slab.h>
87d687301   Dong Aisheng   mfd: Add syscon d...
27
28
  
  static struct platform_driver syscon_driver;
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
29
30
  static DEFINE_SPINLOCK(syscon_list_slock);
  static LIST_HEAD(syscon_list);
87d687301   Dong Aisheng   mfd: Add syscon d...
31
  struct syscon {
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
32
  	struct device_node *np;
87d687301   Dong Aisheng   mfd: Add syscon d...
33
  	struct regmap *regmap;
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
34
35
  	struct list_head list;
  };
c131045d5   Philipp Zabel   mfd: syscon: Make...
36
  static const struct regmap_config syscon_regmap_config = {
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
37
38
39
  	.reg_bits = 32,
  	.val_bits = 32,
  	.reg_stride = 4,
87d687301   Dong Aisheng   mfd: Add syscon d...
40
  };
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
41
  static struct syscon *of_syscon_register(struct device_node *np)
87d687301   Dong Aisheng   mfd: Add syscon d...
42
  {
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
43
44
45
  	struct syscon *syscon;
  	struct regmap *regmap;
  	void __iomem *base;
db2fb60cd   Damien Riegel   mfd: syscon: Add ...
46
  	u32 reg_io_width;
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
47
48
  	int ret;
  	struct regmap_config syscon_config = syscon_regmap_config;
ca668f0ed   Philipp Zabel   mfd: syscon: Set ...
49
  	struct resource res;
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
50
51
52
53
54
55
56
  
  	if (!of_device_is_compatible(np, "syscon"))
  		return ERR_PTR(-EINVAL);
  
  	syscon = kzalloc(sizeof(*syscon), GFP_KERNEL);
  	if (!syscon)
  		return ERR_PTR(-ENOMEM);
ca668f0ed   Philipp Zabel   mfd: syscon: Set ...
57
58
59
60
61
62
  	if (of_address_to_resource(np, 0, &res)) {
  		ret = -ENOMEM;
  		goto err_map;
  	}
  
  	base = ioremap(res.start, resource_size(&res));
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
63
64
65
66
67
68
69
70
  	if (!base) {
  		ret = -ENOMEM;
  		goto err_map;
  	}
  
  	/* Parse the device's DT node for an endianness specification */
  	if (of_property_read_bool(np, "big-endian"))
  		syscon_config.val_format_endian = REGMAP_ENDIAN_BIG;
d29ccdb3f   Paul Burton   mfd: syscon: Supp...
71
  	else if (of_property_read_bool(np, "little-endian"))
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
72
  		syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE;
d29ccdb3f   Paul Burton   mfd: syscon: Supp...
73
74
  	else if (of_property_read_bool(np, "native-endian"))
  		syscon_config.val_format_endian = REGMAP_ENDIAN_NATIVE;
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
75

db2fb60cd   Damien Riegel   mfd: syscon: Add ...
76
77
78
79
80
81
82
83
84
85
86
  	/*
  	 * search for reg-io-width property in DT. If it is not provided,
  	 * default to 4 bytes. regmap_init_mmio will return an error if values
  	 * are invalid so there is no need to check them here.
  	 */
  	ret = of_property_read_u32(np, "reg-io-width", &reg_io_width);
  	if (ret)
  		reg_io_width = 4;
  
  	syscon_config.reg_stride = reg_io_width;
  	syscon_config.val_bits = reg_io_width * 8;
ca668f0ed   Philipp Zabel   mfd: syscon: Set ...
87
  	syscon_config.max_register = resource_size(&res) - reg_io_width;
db2fb60cd   Damien Riegel   mfd: syscon: Add ...
88

bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
89
90
91
92
93
94
95
96
97
98
99
100
101
102
  	regmap = regmap_init_mmio(NULL, base, &syscon_config);
  	if (IS_ERR(regmap)) {
  		pr_err("regmap init failed
  ");
  		ret = PTR_ERR(regmap);
  		goto err_regmap;
  	}
  
  	syscon->regmap = regmap;
  	syscon->np = np;
  
  	spin_lock(&syscon_list_slock);
  	list_add_tail(&syscon->list, &syscon_list);
  	spin_unlock(&syscon_list_slock);
87d687301   Dong Aisheng   mfd: Add syscon d...
103

bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
104
105
106
107
108
109
110
  	return syscon;
  
  err_regmap:
  	iounmap(base);
  err_map:
  	kfree(syscon);
  	return ERR_PTR(ret);
87d687301   Dong Aisheng   mfd: Add syscon d...
111
112
113
114
  }
  
  struct regmap *syscon_node_to_regmap(struct device_node *np)
  {
bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
115
  	struct syscon *entry, *syscon = NULL;
87d687301   Dong Aisheng   mfd: Add syscon d...
116

bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
117
  	spin_lock(&syscon_list_slock);
87d687301   Dong Aisheng   mfd: Add syscon d...
118

bdb0066df   Pankaj Dubey   mfd: syscon: Deco...
119
120
121
122
123
124
125
126
127
128
129
130
131
  	list_for_each_entry(entry, &syscon_list, list)
  		if (entry->np == np) {
  			syscon = entry;
  			break;
  		}
  
  	spin_unlock(&syscon_list_slock);
  
  	if (!syscon)
  		syscon = of_syscon_register(np);
  
  	if (IS_ERR(syscon))
  		return ERR_CAST(syscon);
87d687301   Dong Aisheng   mfd: Add syscon d...
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
  
  	return syscon->regmap;
  }
  EXPORT_SYMBOL_GPL(syscon_node_to_regmap);
  
  struct regmap *syscon_regmap_lookup_by_compatible(const char *s)
  {
  	struct device_node *syscon_np;
  	struct regmap *regmap;
  
  	syscon_np = of_find_compatible_node(NULL, NULL, s);
  	if (!syscon_np)
  		return ERR_PTR(-ENODEV);
  
  	regmap = syscon_node_to_regmap(syscon_np);
  	of_node_put(syscon_np);
  
  	return regmap;
  }
  EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible);
5ab3a89a7   Alexander Shiyan   mfd: syscon: Add ...
152
153
  static int syscon_match_pdevname(struct device *dev, void *data)
  {
5ab3a89a7   Alexander Shiyan   mfd: syscon: Add ...
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
  	return !strcmp(dev_name(dev), (const char *)data);
  }
  
  struct regmap *syscon_regmap_lookup_by_pdevname(const char *s)
  {
  	struct device *dev;
  	struct syscon *syscon;
  
  	dev = driver_find_device(&syscon_driver.driver, NULL, (void *)s,
  				 syscon_match_pdevname);
  	if (!dev)
  		return ERR_PTR(-EPROBE_DEFER);
  
  	syscon = dev_get_drvdata(dev);
  
  	return syscon->regmap;
  }
  EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_pdevname);
87d687301   Dong Aisheng   mfd: Add syscon d...
172
173
174
175
176
  struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np,
  					const char *property)
  {
  	struct device_node *syscon_np;
  	struct regmap *regmap;
45330bb43   Pankaj Dubey   mfd: syscon: Allo...
177
178
179
180
  	if (property)
  		syscon_np = of_parse_phandle(np, property, 0);
  	else
  		syscon_np = np;
87d687301   Dong Aisheng   mfd: Add syscon d...
181
182
183
184
185
186
187
188
189
  	if (!syscon_np)
  		return ERR_PTR(-ENODEV);
  
  	regmap = syscon_node_to_regmap(syscon_np);
  	of_node_put(syscon_np);
  
  	return regmap;
  }
  EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle);
f791be492   Bill Pemberton   mfd: remove use o...
190
  static int syscon_probe(struct platform_device *pdev)
87d687301   Dong Aisheng   mfd: Add syscon d...
191
192
  {
  	struct device *dev = &pdev->dev;
29f9b6cf7   Pawel Moll   mfd: syscon: Add ...
193
  	struct syscon_platform_data *pdata = dev_get_platdata(dev);
87d687301   Dong Aisheng   mfd: Add syscon d...
194
  	struct syscon *syscon;
c131045d5   Philipp Zabel   mfd: syscon: Make...
195
  	struct regmap_config syscon_config = syscon_regmap_config;
5ab3a89a7   Alexander Shiyan   mfd: syscon: Add ...
196
  	struct resource *res;
f10111cc8   Alexander Shiyan   mfd: syscon: Remo...
197
  	void __iomem *base;
87d687301   Dong Aisheng   mfd: Add syscon d...
198

5ab3a89a7   Alexander Shiyan   mfd: syscon: Add ...
199
  	syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL);
87d687301   Dong Aisheng   mfd: Add syscon d...
200
201
  	if (!syscon)
  		return -ENOMEM;
5ab3a89a7   Alexander Shiyan   mfd: syscon: Add ...
202
203
204
  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  	if (!res)
  		return -ENOENT;
87d687301   Dong Aisheng   mfd: Add syscon d...
205

f10111cc8   Alexander Shiyan   mfd: syscon: Remo...
206
207
  	base = devm_ioremap(dev, res->start, resource_size(res));
  	if (!base)
5ab3a89a7   Alexander Shiyan   mfd: syscon: Add ...
208
  		return -ENOMEM;
87d687301   Dong Aisheng   mfd: Add syscon d...
209

c131045d5   Philipp Zabel   mfd: syscon: Make...
210
  	syscon_config.max_register = res->end - res->start - 3;
29f9b6cf7   Pawel Moll   mfd: syscon: Add ...
211
  	if (pdata)
c131045d5   Philipp Zabel   mfd: syscon: Make...
212
213
  		syscon_config.name = pdata->label;
  	syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config);
87d687301   Dong Aisheng   mfd: Add syscon d...
214
215
216
217
218
  	if (IS_ERR(syscon->regmap)) {
  		dev_err(dev, "regmap init failed
  ");
  		return PTR_ERR(syscon->regmap);
  	}
87d687301   Dong Aisheng   mfd: Add syscon d...
219
  	platform_set_drvdata(pdev, syscon);
38d8974e3   Alexander Shiyan   mfd: syscon: Move...
220
221
  	dev_dbg(dev, "regmap %pR registered
  ", res);
87d687301   Dong Aisheng   mfd: Add syscon d...
222
223
224
  
  	return 0;
  }
5ab3a89a7   Alexander Shiyan   mfd: syscon: Add ...
225
226
227
228
  static const struct platform_device_id syscon_ids[] = {
  	{ "syscon", },
  	{ }
  };
87d687301   Dong Aisheng   mfd: Add syscon d...
229
230
231
232
  
  static struct platform_driver syscon_driver = {
  	.driver = {
  		.name = "syscon",
87d687301   Dong Aisheng   mfd: Add syscon d...
233
234
  	},
  	.probe		= syscon_probe,
5ab3a89a7   Alexander Shiyan   mfd: syscon: Add ...
235
  	.id_table	= syscon_ids,
87d687301   Dong Aisheng   mfd: Add syscon d...
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
  };
  
  static int __init syscon_init(void)
  {
  	return platform_driver_register(&syscon_driver);
  }
  postcore_initcall(syscon_init);
  
  static void __exit syscon_exit(void)
  {
  	platform_driver_unregister(&syscon_driver);
  }
  module_exit(syscon_exit);
  
  MODULE_AUTHOR("Dong Aisheng <dong.aisheng@linaro.org>");
  MODULE_DESCRIPTION("System Control driver");
  MODULE_LICENSE("GPL v2");