This patch adds just consumers part of the framework just to enable easy
review.
Up until now, nvmem drivers 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.rip...@free-electrons.com
[Maxime Ripard: intial version of the framework]
Signed-off-by: Srinivas Kandagatla srinivas.kandaga...@linaro.org
---
drivers/nvmem/core.c | 415 +
include/linux/nvmem-consumer.h | 61 ++
2 files changed, 476 insertions(+)
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index bde5528..de14c36 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -365,6 +365,421 @@ int nvmem_unregister(struct nvmem_device *nvmem)
}
EXPORT_SYMBOL_GPL(nvmem_unregister);
+static struct nvmem_device *__nvmem_device_get(struct device_node *np,
+ struct nvmem_cell **cellp,
+ const char *cell_id)
+{
+ struct nvmem_device *nvmem = NULL;
+
+ mutex_lock(nvmem_mutex);
+
+ if (np) {
+ nvmem = of_nvmem_find(np);
+ if (!nvmem) {
+ mutex_unlock(nvmem_mutex);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+ } else {
+ struct nvmem_cell *cell = nvmem_find_cell(cell_id);
+
+ if (cell) {
+ nvmem = cell-nvmem;
+ *cellp = cell;
+ }
+
+ if (!nvmem) {
+ mutex_unlock(nvmem_mutex);
+ return ERR_PTR(-ENOENT);
+ }
+ }
+
+ nvmem-users++;
+ mutex_unlock(nvmem_mutex);
+
+ if (!try_module_get(nvmem-owner)) {
+ dev_err(nvmem-dev,
+ could not increase module refcount for cell %s\n,
+ nvmem-name);
+
+ mutex_lock(nvmem_mutex);
+ nvmem-users--;
+ mutex_unlock(nvmem_mutex);
+
+ return ERR_PTR(-EINVAL);
+ }
+
+ return nvmem;
+}
+
+static void __nvmem_device_put(struct nvmem_device *nvmem)
+{
+ module_put(nvmem-owner);
+ mutex_lock(nvmem_mutex);
+ nvmem-users--;
+ mutex_unlock(nvmem_mutex);
+}
+
+static struct nvmem_cell *nvmem_cell_get_from_list(const char *cell_id)
+{
+ struct nvmem_cell *cell = NULL;
+ struct nvmem_device *nvmem;
+
+ nvmem = __nvmem_device_get(NULL, cell, cell_id);
+ if (IS_ERR(nvmem))
+ return ERR_CAST(nvmem);
+
+ return cell;
+}
+
+#if IS_ENABLED(CONFIG_NVMEM) IS_ENABLED(CONFIG_OF)
+/**
+ * of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id
+ *
+ * @dev node: Device tree node that uses the nvmem cell
+ * @id: nvmem cell name from nvmem-cell-names property.
+ *
+ * Return: Will be an ERR_PTR() on error or a valid pointer
+ * to a struct nvmem_cell. The nvmem_cell will be freed by the
+ * nvmem_cell_put().
+ */
+struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
+ const char *name)
+{
+ struct device_node *cell_np, *nvmem_np;
+ struct nvmem_cell *cell;
+ struct nvmem_device *nvmem;
+ const __be32 *addr;
+ int rval, len, index;
+
+ index = of_property_match_string(np, nvmem-cell-names, name);
+
+ cell_np = of_parse_phandle(np, nvmem-cells, index);
+ if (!cell_np)
+ return ERR_PTR(-EINVAL);
+
+ nvmem_np = of_get_next_parent(cell_np);
+ if (!nvmem_np)
+ return ERR_PTR(-EINVAL);
+
+ nvmem = __nvmem_device_get(nvmem_np, NULL, NULL);
+ if (IS_ERR(nvmem))
+ return ERR_CAST(nvmem);
+
+ addr = of_get_property(cell_np, reg, len);
+ if (!addr || (len 2 * sizeof(int))) {
+ dev_err(nvmem-dev, nvmem: invalid reg on %s\n,
+ cell_np-full_name);
+ rval = -EINVAL;
+ goto err_mem;
+ }
+
+ cell = kzalloc(sizeof(*cell), GFP_KERNEL);
+ if (!cell) {
+ rval = -ENOMEM;
+ goto err_mem;
+ }
+
+ cell-nvmem = nvmem;
+ cell-offset = be32_to_cpup(addr++);
+ cell-bytes = be32_to_cpup(addr);
+ cell-name = cell_np-name;
+
+