aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/nvmem
diff options
context:
space:
mode:
authorSrinivas Kandagatla <srinivas.kandagatla@linaro.org>2015-07-27 07:13:19 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2015-08-05 16:43:12 -0400
commiteace75cfdcf7d9937d8c1fb226780123c64d72c4 (patch)
tree58796a4629d0ab29b98da320b7e0e06c37d6884f /drivers/nvmem
parent4edd70c133f3921c594883d8f9da31a7261f8b4f (diff)
nvmem: Add a simple NVMEM framework for nvmem providers
This patch adds just providers part of the framework just to enable easy review. Up until now, NVMEM drivers like eeprom were stored in drivers/misc, where they all had to duplicate pretty much the same code to register a sysfs file, allow in-kernel users to access the content of the devices they were driving, etc. This was also a problem as far as other in-kernel users were involved, since the solutions used were pretty much different from on driver to another, there was a rather big abstraction leak. This introduction of this framework aims at solving this. It also introduces DT representation for consumer devices to go get the data they require (MAC Addresses, SoC/Revision ID, part numbers, and so on) from the nvmems. Having regmap interface to this framework would give much better abstraction for nvmems on different buses. Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> [Maxime Ripard: intial version of eeprom framework] Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> Tested-by: Stefan Wahren <stefan.wahren@i2se.com> Tested-by: Philipp Zabel <p.zabel@pengutronix.de> Tested-by: Rajendra Nayak <rnayak@codeaurora.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/nvmem')
-rw-r--r--drivers/nvmem/Kconfig13
-rw-r--r--drivers/nvmem/Makefile6
-rw-r--r--drivers/nvmem/core.c406
3 files changed, 425 insertions, 0 deletions
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
new file mode 100644
index 000000000000..de90c82d891b
--- /dev/null
+++ b/drivers/nvmem/Kconfig
@@ -0,0 +1,13 @@
1menuconfig NVMEM
2 tristate "NVMEM Support"
3 select REGMAP
4 help
5 Support for NVMEM(Non Volatile Memory) devices like EEPROM, EFUSES...
6
7 This framework is designed to provide a generic interface to NVMEM
8 from both the Linux Kernel and the userspace.
9
10 This driver can also be built as a module. If so, the module
11 will be called nvmem_core.
12
13 If unsure, say no.
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
new file mode 100644
index 000000000000..6df2c6952ad5
--- /dev/null
+++ b/drivers/nvmem/Makefile
@@ -0,0 +1,6 @@
1#
2# Makefile for nvmem drivers.
3#
4
5obj-$(CONFIG_NVMEM) += nvmem_core.o
6nvmem_core-y := core.o
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
new file mode 100644
index 000000000000..2b024915e224
--- /dev/null
+++ b/drivers/nvmem/core.c
@@ -0,0 +1,406 @@
1/*
2 * nvmem framework core.
3 *
4 * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
5 * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 and
9 * only version 2 as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
17#include <linux/device.h>
18#include <linux/export.h>
19#include <linux/fs.h>
20#include <linux/idr.h>
21#include <linux/init.h>
22#include <linux/module.h>
23#include <linux/nvmem-consumer.h>
24#include <linux/nvmem-provider.h>
25#include <linux/of.h>
26#include <linux/regmap.h>
27#include <linux/slab.h>
28
29struct nvmem_device {
30 const char *name;
31 struct regmap *regmap;
32 struct module *owner;
33 struct device dev;
34 int stride;
35 int word_size;
36 int ncells;
37 int id;
38 int users;
39 size_t size;
40 bool read_only;
41};
42
43struct nvmem_cell {
44 const char *name;
45 int offset;
46 int bytes;
47 int bit_offset;
48 int nbits;
49 struct nvmem_device *nvmem;
50 struct list_head node;
51};
52
53static DEFINE_MUTEX(nvmem_mutex);
54static DEFINE_IDA(nvmem_ida);
55
56static LIST_HEAD(nvmem_cells);
57static DEFINE_MUTEX(nvmem_cells_mutex);
58
59#define to_nvmem_device(d) container_of(d, struct nvmem_device, dev)
60
61static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj,
62 struct bin_attribute *attr,
63 char *buf, loff_t pos, size_t count)
64{
65 struct device *dev = container_of(kobj, struct device, kobj);
66 struct nvmem_device *nvmem = to_nvmem_device(dev);
67 int rc;
68
69 /* Stop the user from reading */
70 if (pos > nvmem->size)
71 return 0;
72
73 if (pos + count > nvmem->size)
74 count = nvmem->size - pos;
75
76 count = round_down(count, nvmem->word_size);
77
78 rc = regmap_raw_read(nvmem->regmap, pos, buf, count);
79
80 if (IS_ERR_VALUE(rc))
81 return rc;
82
83 return count;
84}
85
86static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj,
87 struct bin_attribute *attr,
88 char *buf, loff_t pos, size_t count)
89{
90 struct device *dev = container_of(kobj, struct device, kobj);
91 struct nvmem_device *nvmem = to_nvmem_device(dev);
92 int rc;
93
94 /* Stop the user from writing */
95 if (pos > nvmem->size)
96 return 0;
97
98 if (pos + count > nvmem->size)
99 count = nvmem->size - pos;
100
101 count = round_down(count, nvmem->word_size);
102
103 rc = regmap_raw_write(nvmem->regmap, pos, buf, count);
104
105 if (IS_ERR_VALUE(rc))
106 return rc;
107
108 return count;
109}
110
111/* default read/write permissions */
112static struct bin_attribute bin_attr_rw_nvmem = {
113 .attr = {
114 .name = "nvmem",
115 .mode = S_IWUSR | S_IRUGO,
116 },
117 .read = bin_attr_nvmem_read,
118 .write = bin_attr_nvmem_write,
119};
120
121static struct bin_attribute *nvmem_bin_rw_attributes[] = {
122 &bin_attr_rw_nvmem,
123 NULL,
124};
125
126static const struct attribute_group nvmem_bin_rw_group = {
127 .bin_attrs = nvmem_bin_rw_attributes,
128};
129
130static const struct attribute_group *nvmem_rw_dev_groups[] = {
131 &nvmem_bin_rw_group,
132 NULL,
133};
134
135/* read only permission */
136static struct bin_attribute bin_attr_ro_nvmem = {
137 .attr = {
138 .name = "nvmem",
139 .mode = S_IRUGO,
140 },
141 .read = bin_attr_nvmem_read,
142};
143
144static struct bin_attribute *nvmem_bin_ro_attributes[] = {
145 &bin_attr_ro_nvmem,
146 NULL,
147};
148
149static const struct attribute_group nvmem_bin_ro_group = {
150 .bin_attrs = nvmem_bin_ro_attributes,
151};
152
153static const struct attribute_group *nvmem_ro_dev_groups[] = {
154 &nvmem_bin_ro_group,
155 NULL,
156};
157
158static void nvmem_release(struct device *dev)
159{
160 struct nvmem_device *nvmem = to_nvmem_device(dev);
161
162 ida_simple_remove(&nvmem_ida, nvmem->id);
163 kfree(nvmem);
164}
165
166static const struct device_type nvmem_provider_type = {
167 .release = nvmem_release,
168};
169
170static struct bus_type nvmem_bus_type = {
171 .name = "nvmem",
172};
173
174static int of_nvmem_match(struct device *dev, void *nvmem_np)
175{
176 return dev->of_node == nvmem_np;
177}
178
179static struct nvmem_device *of_nvmem_find(struct device_node *nvmem_np)
180{
181 struct device *d;
182
183 if (!nvmem_np)
184 return NULL;
185
186 d = bus_find_device(&nvmem_bus_type, NULL, nvmem_np, of_nvmem_match);
187
188 if (!d)
189 return NULL;
190
191 return to_nvmem_device(d);
192}
193
194static struct nvmem_cell *nvmem_find_cell(const char *cell_id)
195{
196 struct nvmem_cell *p;
197
198 list_for_each_entry(p, &nvmem_cells, node)
199 if (p && !strcmp(p->name, cell_id))
200 return p;
201
202 return NULL;
203}
204
205static void nvmem_cell_drop(struct nvmem_cell *cell)
206{
207 mutex_lock(&nvmem_cells_mutex);
208 list_del(&cell->node);
209 mutex_unlock(&nvmem_cells_mutex);
210 kfree(cell);
211}
212
213static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem)
214{
215 struct nvmem_cell *cell;
216 struct list_head *p, *n;
217
218 list_for_each_safe(p, n, &nvmem_cells) {
219 cell = list_entry(p, struct nvmem_cell, node);
220 if (cell->nvmem == nvmem)
221 nvmem_cell_drop(cell);
222 }
223}
224
225static void nvmem_cell_add(struct nvmem_cell *cell)
226{
227 mutex_lock(&nvmem_cells_mutex);
228 list_add_tail(&cell->node, &nvmem_cells);
229 mutex_unlock(&nvmem_cells_mutex);
230}
231
232static int nvmem_cell_info_to_nvmem_cell(struct nvmem_device *nvmem,
233 const struct nvmem_cell_info *info,
234 struct nvmem_cell *cell)
235{
236 cell->nvmem = nvmem;
237 cell->offset = info->offset;
238 cell->bytes = info->bytes;
239 cell->name = info->name;
240
241 cell->bit_offset = info->bit_offset;
242 cell->nbits = info->nbits;
243
244 if (cell->nbits)
245 cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
246 BITS_PER_BYTE);
247
248 if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
249 dev_err(&nvmem->dev,
250 "cell %s unaligned to nvmem stride %d\n",
251 cell->name, nvmem->stride);
252 return -EINVAL;
253 }
254
255 return 0;
256}
257
258static int nvmem_add_cells(struct nvmem_device *nvmem,
259 const struct nvmem_config *cfg)
260{
261 struct nvmem_cell **cells;
262 const struct nvmem_cell_info *info = cfg->cells;
263 int i, rval;
264
265 cells = kcalloc(cfg->ncells, sizeof(*cells), GFP_KERNEL);
266 if (!cells)
267 return -ENOMEM;
268
269 for (i = 0; i < cfg->ncells; i++) {
270 cells[i] = kzalloc(sizeof(**cells), GFP_KERNEL);
271 if (!cells[i]) {
272 rval = -ENOMEM;
273 goto err;
274 }
275
276 rval = nvmem_cell_info_to_nvmem_cell(nvmem, &info[i], cells[i]);
277 if (IS_ERR_VALUE(rval)) {
278 kfree(cells[i]);
279 goto err;
280 }
281
282 nvmem_cell_add(cells[i]);
283 }
284
285 nvmem->ncells = cfg->ncells;
286 /* remove tmp array */
287 kfree(cells);
288
289 return 0;
290err:
291 while (--i)
292 nvmem_cell_drop(cells[i]);
293
294 return rval;
295}
296
297/**
298 * nvmem_register() - Register a nvmem device for given nvmem_config.
299 * Also creates an binary entry in /sys/bus/nvmem/devices/dev-name/nvmem
300 *
301 * @config: nvmem device configuration with which nvmem device is created.
302 *
303 * Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device
304 * on success.
305 */
306
307struct nvmem_device *nvmem_register(const struct nvmem_config *config)
308{
309 struct nvmem_device *nvmem;
310 struct device_node *np;
311 struct regmap *rm;
312 int rval;
313
314 if (!config->dev)
315 return ERR_PTR(-EINVAL);
316
317 rm = dev_get_regmap(config->dev, NULL);
318 if (!rm) {
319 dev_err(config->dev, "Regmap not found\n");
320 return ERR_PTR(-EINVAL);
321 }
322
323 nvmem = kzalloc(sizeof(*nvmem), GFP_KERNEL);
324 if (!nvmem)
325 return ERR_PTR(-ENOMEM);
326
327 rval = ida_simple_get(&nvmem_ida, 0, 0, GFP_KERNEL);
328 if (rval < 0) {
329 kfree(nvmem);
330 return ERR_PTR(rval);
331 }
332
333 nvmem->id = rval;
334 nvmem->regmap = rm;
335 nvmem->owner = config->owner;
336 nvmem->stride = regmap_get_reg_stride(rm);
337 nvmem->word_size = regmap_get_val_bytes(rm);
338 nvmem->size = regmap_get_max_register(rm) + nvmem->stride;
339 nvmem->dev.type = &nvmem_provider_type;
340 nvmem->dev.bus = &nvmem_bus_type;
341 nvmem->dev.parent = config->dev;
342 np = config->dev->of_node;
343 nvmem->dev.of_node = np;
344 dev_set_name(&nvmem->dev, "%s%d",
345 config->name ? : "nvmem", config->id);
346
347 nvmem->read_only = of_property_read_bool(np, "read-only") |
348 config->read_only;
349
350 nvmem->dev.groups = nvmem->read_only ? nvmem_ro_dev_groups :
351 nvmem_rw_dev_groups;
352
353 device_initialize(&nvmem->dev);
354
355 dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
356
357 rval = device_add(&nvmem->dev);
358 if (rval) {
359 ida_simple_remove(&nvmem_ida, nvmem->id);
360 kfree(nvmem);
361 return ERR_PTR(rval);
362 }
363
364 if (config->cells)
365 nvmem_add_cells(nvmem, config);
366
367 return nvmem;
368}
369EXPORT_SYMBOL_GPL(nvmem_register);
370
371/**
372 * nvmem_unregister() - Unregister previously registered nvmem device
373 *
374 * @nvmem: Pointer to previously registered nvmem device.
375 *
376 * Return: Will be an negative on error or a zero on success.
377 */
378int nvmem_unregister(struct nvmem_device *nvmem)
379{
380 if (nvmem->users)
381 return -EBUSY;
382
383 nvmem_device_remove_all_cells(nvmem);
384 device_del(&nvmem->dev);
385
386 return 0;
387}
388EXPORT_SYMBOL_GPL(nvmem_unregister);
389
390static int __init nvmem_init(void)
391{
392 return bus_register(&nvmem_bus_type);
393}
394
395static void __exit nvmem_exit(void)
396{
397 bus_unregister(&nvmem_bus_type);
398}
399
400subsys_initcall(nvmem_init);
401module_exit(nvmem_exit);
402
403MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
404MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com");
405MODULE_DESCRIPTION("nvmem Driver Core");
406MODULE_LICENSE("GPL v2");