* [PATCH 1/6] drivers: add nvmem framework from kernel
2016-04-29 17:24 [PATCH 0/6] AIODEV subsystem Andrey Smirnov
@ 2016-04-29 17:24 ` Andrey Smirnov
2016-04-29 17:24 ` [PATCH 2/6] ocotp: Register OCOTP with 'nvmem' Andrey Smirnov
` (5 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Andrey Smirnov @ 2016-04-29 17:24 UTC (permalink / raw)
To: barebox; +Cc: Steffen Trumtrar
From: Steffen Trumtrar <s.trumtrar@pengutronix.de>
Add the nvmem framework from Linux.
Based on the v4.4-rc3 version.
Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
---
drivers/Kconfig | 2 +-
drivers/Makefile | 2 +
drivers/nvmem/Kconfig | 7 +
drivers/nvmem/Makefile | 6 +
drivers/nvmem/core.c | 749 +++++++++++++++++++++++++++++++++++++++++
include/linux/nvmem-consumer.h | 157 +++++++++
include/linux/nvmem-provider.h | 49 +++
7 files changed, 971 insertions(+), 1 deletion(-)
create mode 100644 drivers/nvmem/Kconfig
create mode 100644 drivers/nvmem/Makefile
create mode 100644 drivers/nvmem/core.c
create mode 100644 include/linux/nvmem-consumer.h
create mode 100644 include/linux/nvmem-provider.h
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 3236696..90ab7c1 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -32,5 +32,5 @@ source "drivers/rtc/Kconfig"
source "drivers/firmware/Kconfig"
source "drivers/phy/Kconfig"
source "drivers/crypto/Kconfig"
-
+source "drivers/nvmem/Kconfig"
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index a4467a0..551b9a0 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -32,3 +32,5 @@ obj-$(CONFIG_FIRMWARE) += firmware/
obj-$(CONFIG_GENERIC_PHY) += phy/
obj-$(CONFIG_HAB) += hab/
obj-$(CONFIG_CRYPTO_HW) += crypto/
+obj-$(CONFIG_NVMEM) += nvmem/
+
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
new file mode 100644
index 0000000..999a432
--- /dev/null
+++ b/drivers/nvmem/Kconfig
@@ -0,0 +1,7 @@
+menuconfig NVMEM
+ bool "NVMEM Support"
+ help
+ Support for NVMEM(Non Volatile Memory) devices like EEPROM, EFUSES...
+
+ This framework is designed to provide a generic interface to NVMEM
+ If unsure, say no.
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
new file mode 100644
index 0000000..6df2c69
--- /dev/null
+++ b/drivers/nvmem/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for nvmem drivers.
+#
+
+obj-$(CONFIG_NVMEM) += nvmem_core.o
+nvmem_core-y := core.o
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
new file mode 100644
index 0000000..0214253
--- /dev/null
+++ b/drivers/nvmem/core.c
@@ -0,0 +1,749 @@
+/*
+ * nvmem framework core.
+ *
+ * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+ * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#define DEBUG
+#include <common.h>
+#include <libbb.h>
+#include <malloc.h>
+#include <of.h>
+#include <regmap.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
+
+struct nvmem_device {
+ const char *name;
+ struct device_d dev;
+ struct nvmem_bus *bus;
+ struct list_head node;
+ int stride;
+ int word_size;
+ int ncells;
+ int users;
+ size_t size;
+ bool read_only;
+ struct regmap *regmap;
+};
+
+struct nvmem_cell {
+ const char *name;
+ int offset;
+ int bytes;
+ int bit_offset;
+ int nbits;
+ struct nvmem_device *nvmem;
+ struct list_head node;
+};
+
+static LIST_HEAD(nvmem_cells);
+static LIST_HEAD(nvmem_devs);
+
+static struct nvmem_device *of_nvmem_find(struct device_node *nvmem_np)
+{
+ struct nvmem_device *dev;
+
+ if (!nvmem_np)
+ return NULL;
+
+ list_for_each_entry(dev, &nvmem_devs, node)
+ if (dev->dev.device_node->name && !strcmp(dev->dev.device_node->name, nvmem_np->name))
+ return dev;
+
+ return NULL;
+}
+
+static struct nvmem_cell *nvmem_find_cell(const char *cell_id)
+{
+ struct nvmem_cell *p;
+
+ list_for_each_entry(p, &nvmem_cells, node)
+ if (p && !strcmp(p->name, cell_id))
+ return p;
+
+ return NULL;
+}
+
+static void nvmem_cell_drop(struct nvmem_cell *cell)
+{
+ list_del(&cell->node);
+ kfree(cell);
+}
+
+static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem)
+{
+ struct nvmem_cell *cell;
+ struct list_head *p, *n;
+
+ list_for_each_safe(p, n, &nvmem_cells) {
+ cell = list_entry(p, struct nvmem_cell, node);
+ if (cell->nvmem == nvmem)
+ nvmem_cell_drop(cell);
+ }
+}
+
+static void nvmem_cell_add(struct nvmem_cell *cell)
+{
+ list_add_tail(&cell->node, &nvmem_cells);
+}
+
+static int nvmem_cell_info_to_nvmem_cell(struct nvmem_device *nvmem,
+ const struct nvmem_cell_info *info,
+ struct nvmem_cell *cell)
+{
+ cell->nvmem = nvmem;
+ cell->offset = info->offset;
+ cell->bytes = info->bytes;
+ cell->name = info->name;
+
+ cell->bit_offset = info->bit_offset;
+ cell->nbits = info->nbits;
+
+ if (cell->nbits)
+ cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
+ BITS_PER_BYTE);
+
+ if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
+ dev_err(&nvmem->dev,
+ "cell %s unaligned to nvmem stride %d\n",
+ cell->name, nvmem->stride);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nvmem_add_cells(struct nvmem_device *nvmem,
+ const struct nvmem_config *cfg)
+{
+ struct nvmem_cell **cells;
+ const struct nvmem_cell_info *info = cfg->cells;
+ int i, rval;
+
+ cells = kzalloc(sizeof(*cells)*cfg->ncells, GFP_KERNEL);
+ if (!cells)
+ return -ENOMEM;
+
+ for (i = 0; i < cfg->ncells; i++) {
+ cells[i] = kzalloc(sizeof(**cells), GFP_KERNEL);
+ if (!cells[i]) {
+ rval = -ENOMEM;
+ goto err;
+ }
+
+ rval = nvmem_cell_info_to_nvmem_cell(nvmem, &info[i], cells[i]);
+ if (IS_ERR_VALUE(rval)) {
+ kfree(cells[i]);
+ goto err;
+ }
+
+ nvmem_cell_add(cells[i]);
+ }
+
+ nvmem->ncells = cfg->ncells;
+ /* remove tmp array */
+ kfree(cells);
+
+ return 0;
+err:
+ while (--i)
+ nvmem_cell_drop(cells[i]);
+
+ return rval;
+}
+
+/**
+ * nvmem_register() - Register a nvmem device for given nvmem_config.
+ *
+ * @config: nvmem device configuration with which nvmem device is created.
+ *
+ * Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device
+ * on success.
+ */
+
+struct nvmem_device *nvmem_register(const struct nvmem_config *config, struct regmap *map)
+{
+ struct nvmem_device *nvmem;
+ struct device_node *np;
+ int rval;
+
+ if (!config->dev)
+ return ERR_PTR(-EINVAL);
+
+ nvmem = kzalloc(sizeof(*nvmem), GFP_KERNEL);
+ if (!nvmem)
+ return ERR_PTR(-ENOMEM);
+
+ nvmem->stride = regmap_get_reg_stride(map);
+ nvmem->word_size = regmap_get_val_bytes(map);
+ nvmem->size = regmap_get_max_register(map) + nvmem->stride;
+ nvmem->dev.parent = config->dev;
+ nvmem->regmap = map;
+ np = config->dev->device_node;
+ nvmem->dev.device_node = np;
+
+ nvmem->read_only = of_property_read_bool(np, "read-only") |
+ config->read_only;
+
+ safe_strncpy(nvmem->dev.name, config->name, MAX_DRIVER_NAME);
+ nvmem->dev.id = DEVICE_ID_DYNAMIC;
+
+ dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
+
+ rval = register_device(&nvmem->dev);
+ if (rval) {
+ kfree(nvmem);
+ return ERR_PTR(rval);
+ }
+
+ list_add_tail(&nvmem->node, &nvmem_devs);
+
+ if (config->cells)
+ nvmem_add_cells(nvmem, config);
+
+ return nvmem;
+}
+EXPORT_SYMBOL_GPL(nvmem_register);
+
+/**
+ * nvmem_unregister() - Unregister previously registered nvmem device
+ *
+ * @nvmem: Pointer to previously registered nvmem device.
+ *
+ * Return: Will be an negative on error or a zero on success.
+ */
+int nvmem_unregister(struct nvmem_device *nvmem)
+{
+ if (nvmem->users)
+ return -EBUSY;
+
+ nvmem_device_remove_all_cells(nvmem);
+ unregister_device(&nvmem->dev);
+
+ return 0;
+}
+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;
+
+ if (np) {
+ nvmem = of_nvmem_find(np);
+ if (!nvmem)
+ return ERR_PTR(-EPROBE_DEFER);
+ } else {
+ struct nvmem_cell *cell = nvmem_find_cell(cell_id);
+
+ if (cell) {
+ nvmem = cell->nvmem;
+ *cellp = cell;
+ }
+
+ if (!nvmem)
+ return ERR_PTR(-ENOENT);
+ }
+
+ nvmem->users++;
+
+ return nvmem;
+}
+
+static void __nvmem_device_put(struct nvmem_device *nvmem)
+{
+ nvmem->users--;
+}
+
+#if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OFTREE)
+/**
+ * of_nvmem_device_get() - Get nvmem device from a given id
+ *
+ * @dev node: Device tree node that uses the nvmem device
+ * @id: nvmem name from nvmem-names property.
+ *
+ * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
+ * on success.
+ */
+struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id)
+{
+
+ struct device_node *nvmem_np;
+ int index;
+
+ index = of_property_match_string(np, "nvmem-names", id);
+
+ nvmem_np = of_parse_phandle(np, "nvmem", index);
+ if (!nvmem_np)
+ return ERR_PTR(-EINVAL);
+
+ return __nvmem_device_get(nvmem_np, NULL, NULL);
+}
+EXPORT_SYMBOL_GPL(of_nvmem_device_get);
+#endif
+
+/**
+ * nvmem_device_get() - Get nvmem device from a given id
+ *
+ * @dev : Device that uses the nvmem device
+ * @id: nvmem name from nvmem-names property.
+ *
+ * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
+ * on success.
+ */
+struct nvmem_device *nvmem_device_get(struct device_d *dev, const char *dev_name)
+{
+ if (dev->device_node) { /* try dt first */
+ struct nvmem_device *nvmem;
+
+ nvmem = of_nvmem_device_get(dev->device_node, dev_name);
+
+ if (!IS_ERR(nvmem) || PTR_ERR(nvmem) == -EPROBE_DEFER)
+ return nvmem;
+
+ }
+
+ return ERR_PTR(-ENOENT);
+}
+EXPORT_SYMBOL_GPL(nvmem_device_get);
+
+/**
+ * nvmem_device_put() - put alredy got nvmem device
+ *
+ * @nvmem: pointer to nvmem device that needs to be released.
+ */
+void nvmem_device_put(struct nvmem_device *nvmem)
+{
+ __nvmem_device_put(nvmem);
+}
+EXPORT_SYMBOL_GPL(nvmem_device_put);
+
+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_OFTREE)
+/**
+ * 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);
+ if (index < 0)
+ return ERR_PTR(index);
+
+ cell_np = of_parse_phandle(np, "nvmem-cells", index);
+ if (!cell_np)
+ return ERR_PTR(-EINVAL);
+
+ nvmem_np = of_get_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(u32))) {
+ 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;
+
+ addr = of_get_property(cell_np, "bits", &len);
+ if (addr && len == (2 * sizeof(u32))) {
+ cell->bit_offset = be32_to_cpup(addr++);
+ cell->nbits = be32_to_cpup(addr);
+ }
+
+ if (cell->nbits)
+ cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
+ BITS_PER_BYTE);
+
+ if (cell->bytes < nvmem->word_size)
+ cell->bytes = nvmem->word_size;
+
+ if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
+ dev_err(&nvmem->dev,
+ "cell %s unaligned to nvmem stride %d\n",
+ cell->name, nvmem->stride);
+ rval = -EINVAL;
+ goto err_sanity;
+ }
+
+ nvmem_cell_add(cell);
+
+ return cell;
+
+err_sanity:
+ kfree(cell);
+
+err_mem:
+ __nvmem_device_put(nvmem);
+
+ return ERR_PTR(rval);
+}
+EXPORT_SYMBOL_GPL(of_nvmem_cell_get);
+#endif
+
+/**
+ * nvmem_cell_get() - Get nvmem cell of device form a given cell name
+ *
+ * @dev node: Device tree node that uses the nvmem cell
+ * @id: nvmem cell name to get.
+ *
+ * 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 *nvmem_cell_get(struct device_d *dev, const char *cell_id)
+{
+ struct nvmem_cell *cell;
+
+ if (dev->device_node) { /* try dt first */
+ cell = of_nvmem_cell_get(dev->device_node, cell_id);
+ if (!IS_ERR(cell) || PTR_ERR(cell) == -EPROBE_DEFER)
+ return cell;
+ }
+
+ return nvmem_cell_get_from_list(cell_id);
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_get);
+
+/**
+ * nvmem_cell_put() - Release previously allocated nvmem cell.
+ *
+ * @cell: Previously allocated nvmem cell by nvmem_cell_get()
+ */
+void nvmem_cell_put(struct nvmem_cell *cell)
+{
+ struct nvmem_device *nvmem = cell->nvmem;
+
+ __nvmem_device_put(nvmem);
+ nvmem_cell_drop(cell);
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_put);
+
+static inline void nvmem_shift_read_buffer_in_place(struct nvmem_cell *cell,
+ void *buf)
+{
+ u8 *p, *b;
+ int i, bit_offset = cell->bit_offset;
+
+ p = b = buf;
+ if (bit_offset) {
+ /* First shift */
+ *b++ >>= bit_offset;
+
+ /* setup rest of the bytes if any */
+ for (i = 1; i < cell->bytes; i++) {
+ /* Get bits from next byte and shift them towards msb */
+ *p |= *b << (BITS_PER_BYTE - bit_offset);
+
+ p = b;
+ *b++ >>= bit_offset;
+ }
+
+ /* result fits in less bytes */
+ if (cell->bytes != DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE))
+ *p-- = 0;
+ }
+ /* clear msb bits if any leftover in the last byte */
+ *p &= GENMASK((cell->nbits%BITS_PER_BYTE) - 1, 0);
+}
+
+static int __nvmem_cell_read(struct nvmem_device *nvmem,
+ struct nvmem_cell *cell,
+ void *buf, size_t *len)
+{
+ int rc;
+
+ rc = regmap_bulk_read(nvmem->regmap, cell->offset, buf, cell->bytes);
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ /* shift bits in-place */
+ if (cell->bit_offset || cell->nbits)
+ nvmem_shift_read_buffer_in_place(cell, buf);
+
+ *len = cell->bytes;
+
+ return 0;
+}
+
+/**
+ * nvmem_cell_read() - Read a given nvmem cell
+ *
+ * @cell: nvmem cell to be read.
+ * @len: pointer to length of cell which will be populated on successful read.
+ *
+ * Return: ERR_PTR() on error or a valid pointer to a char * buffer on success.
+ * The buffer should be freed by the consumer with a kfree().
+ */
+void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
+{
+ struct nvmem_device *nvmem = cell->nvmem;
+ u8 *buf;
+ int rc;
+
+ if (!nvmem)
+ return ERR_PTR(-EINVAL);
+
+ buf = kzalloc(cell->bytes, GFP_KERNEL);
+ if (!buf)
+ return ERR_PTR(-ENOMEM);
+
+ rc = __nvmem_cell_read(nvmem, cell, buf, len);
+ if (IS_ERR_VALUE(rc)) {
+ kfree(buf);
+ return ERR_PTR(rc);
+ }
+
+ return buf;
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_read);
+
+static inline void *nvmem_cell_prepare_write_buffer(struct nvmem_cell *cell,
+ u8 *_buf, int len)
+{
+ struct nvmem_device *nvmem = cell->nvmem;
+ int i, rc, nbits, bit_offset = cell->bit_offset;
+ u8 v, *p, *buf, *b, pbyte, pbits;
+
+ nbits = cell->nbits;
+ buf = kzalloc(cell->bytes, GFP_KERNEL);
+ if (!buf)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(buf, _buf, len);
+ p = b = buf;
+
+ if (bit_offset) {
+ pbyte = *b;
+ *b <<= bit_offset;
+
+ /* setup the first byte with lsb bits from nvmem */
+ rc = regmap_bulk_read(nvmem->regmap, cell->offset, &v, 1);
+ *b++ |= GENMASK(bit_offset - 1, 0) & v;
+
+ /* setup rest of the byte if any */
+ for (i = 1; i < cell->bytes; i++) {
+ /* Get last byte bits and shift them towards lsb */
+ pbits = pbyte >> (BITS_PER_BYTE - 1 - bit_offset);
+ pbyte = *b;
+ p = b;
+ *b <<= bit_offset;
+ *b++ |= pbits;
+ }
+ }
+
+ /* if it's not end on byte boundary */
+ if ((nbits + bit_offset) % BITS_PER_BYTE) {
+ /* setup the last byte with msb bits from nvmem */
+ rc = regmap_bulk_read(nvmem->regmap, cell->offset + cell->bytes - 1,
+ &v, 1);
+ *p |= GENMASK(7, (nbits + bit_offset) % BITS_PER_BYTE) & v;
+
+ }
+
+ return buf;
+}
+
+/**
+ * nvmem_cell_write() - Write to a given nvmem cell
+ *
+ * @cell: nvmem cell to be written.
+ * @buf: Buffer to be written.
+ * @len: length of buffer to be written to nvmem cell.
+ *
+ * Return: length of bytes written or negative on failure.
+ */
+int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len)
+{
+ struct nvmem_device *nvmem = cell->nvmem;
+ int rc;
+
+ if (!nvmem || nvmem->read_only ||
+ (cell->bit_offset == 0 && len != cell->bytes))
+ return -EINVAL;
+
+ if (cell->bit_offset || cell->nbits) {
+ buf = nvmem_cell_prepare_write_buffer(cell, buf, len);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+ }
+
+ rc = regmap_bulk_write(nvmem->regmap, cell->offset, buf, cell->bytes);
+
+ /* free the tmp buffer */
+ if (cell->bit_offset || cell->nbits)
+ kfree(buf);
+
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_write);
+
+/**
+ * nvmem_device_cell_read() - Read a given nvmem device and cell
+ *
+ * @nvmem: nvmem device to read from.
+ * @info: nvmem cell info to be read.
+ * @buf: buffer pointer which will be populated on successful read.
+ *
+ * Return: length of successful bytes read on success and negative
+ * error code on error.
+ */
+ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *info, void *buf)
+{
+ struct nvmem_cell cell;
+ int rc;
+ ssize_t len;
+
+ if (!nvmem)
+ return -EINVAL;
+
+ rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell);
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ rc = __nvmem_cell_read(nvmem, &cell, buf, &len);
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(nvmem_device_cell_read);
+
+/**
+ * nvmem_device_cell_write() - Write cell to a given nvmem device
+ *
+ * @nvmem: nvmem device to be written to.
+ * @info: nvmem cell info to be written
+ * @buf: buffer to be written to cell.
+ *
+ * Return: length of bytes written or negative error code on failure.
+ * */
+int nvmem_device_cell_write(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *info, void *buf)
+{
+ struct nvmem_cell cell;
+ int rc;
+
+ if (!nvmem)
+ return -EINVAL;
+
+ rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell);
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ return nvmem_cell_write(&cell, buf, cell.bytes);
+}
+EXPORT_SYMBOL_GPL(nvmem_device_cell_write);
+
+/**
+ * nvmem_device_read() - Read from a given nvmem device
+ *
+ * @nvmem: nvmem device to read from.
+ * @offset: offset in nvmem device.
+ * @bytes: number of bytes to read.
+ * @buf: buffer pointer which will be populated on successful read.
+ *
+ * Return: length of successful bytes read on success and negative
+ * error code on error.
+ */
+int nvmem_device_read(struct nvmem_device *nvmem,
+ unsigned int offset,
+ size_t bytes, void *buf)
+{
+ int rc;
+
+ if (!nvmem)
+ return -EINVAL;
+
+ rc = regmap_bulk_read(nvmem->regmap, offset, buf, bytes);
+
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+ return bytes;
+}
+EXPORT_SYMBOL_GPL(nvmem_device_read);
+
+/**
+ * nvmem_device_write() - Write cell to a given nvmem device
+ *
+ * @nvmem: nvmem device to be written to.
+ * @offset: offset in nvmem device.
+ * @bytes: number of bytes to write.
+ * @buf: buffer to be written.
+ *
+ * Return: length of bytes written or negative error code on failure.
+ * */
+int nvmem_device_write(struct nvmem_device *nvmem,
+ unsigned int offset,
+ size_t bytes, void *buf)
+{
+ int rc;
+
+ if (!nvmem)
+ return -EINVAL;
+
+ rc = regmap_bulk_write(nvmem->regmap, offset, buf, bytes);
+
+ if (IS_ERR_VALUE(rc))
+ return rc;
+
+
+ return bytes;
+}
+EXPORT_SYMBOL_GPL(nvmem_device_write);
diff --git a/include/linux/nvmem-consumer.h b/include/linux/nvmem-consumer.h
new file mode 100644
index 0000000..cae6ec7
--- /dev/null
+++ b/include/linux/nvmem-consumer.h
@@ -0,0 +1,157 @@
+/*
+ * nvmem framework consumer.
+ *
+ * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+ * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef _LINUX_NVMEM_CONSUMER_H
+#define _LINUX_NVMEM_CONSUMER_H
+
+struct device_d;
+struct device_node;
+/* consumer cookie */
+struct nvmem_cell;
+struct nvmem_device;
+
+struct nvmem_cell_info {
+ const char *name;
+ unsigned int offset;
+ unsigned int bytes;
+ unsigned int bit_offset;
+ unsigned int nbits;
+};
+
+#if IS_ENABLED(CONFIG_NVMEM)
+
+/* Cell based interface */
+struct nvmem_cell *nvmem_cell_get(struct device_d *dev, const char *name);
+struct nvmem_cell *devm_nvmem_cell_get(struct device_d *dev, const char *name);
+void nvmem_cell_put(struct nvmem_cell *cell);
+void devm_nvmem_cell_put(struct device_d *dev, struct nvmem_cell *cell);
+void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len);
+int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len);
+
+/* direct nvmem device read/write interface */
+struct nvmem_device *nvmem_device_get(struct device_d *dev, const char *name);
+struct nvmem_device *devm_nvmem_device_get(struct device_d *dev,
+ const char *name);
+void nvmem_device_put(struct nvmem_device *nvmem);
+void devm_nvmem_device_put(struct device_d *dev, struct nvmem_device *nvmem);
+int nvmem_device_read(struct nvmem_device *nvmem, unsigned int offset,
+ size_t bytes, void *buf);
+int nvmem_device_write(struct nvmem_device *nvmem, unsigned int offset,
+ size_t bytes, void *buf);
+ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *info, void *buf);
+int nvmem_device_cell_write(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *info, void *buf);
+
+#else
+
+static inline struct nvmem_cell *nvmem_cell_get(struct device_d *dev,
+ const char *name)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+static inline struct nvmem_cell *devm_nvmem_cell_get(struct device_d *dev,
+ const char *name)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+static inline void devm_nvmem_cell_put(struct device_d *dev,
+ struct nvmem_cell *cell)
+{
+
+}
+static inline void nvmem_cell_put(struct nvmem_cell *cell)
+{
+}
+
+static inline char *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+static inline int nvmem_cell_write(struct nvmem_cell *cell,
+ const char *buf, size_t len)
+{
+ return -ENOSYS;
+}
+
+static inline struct nvmem_device *nvmem_device_get(struct device_d *dev,
+ const char *name)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+static inline struct nvmem_device *devm_nvmem_device_get(struct device_d *dev,
+ const char *name)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+static inline void nvmem_device_put(struct nvmem_device *nvmem)
+{
+}
+
+static inline void devm_nvmem_device_put(struct device_d *dev,
+ struct nvmem_device *nvmem)
+{
+}
+
+static inline ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *info,
+ void *buf)
+{
+ return -ENOSYS;
+}
+
+static inline int nvmem_device_cell_write(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *info,
+ void *buf)
+{
+ return -ENOSYS;
+}
+
+static inline int nvmem_device_read(struct nvmem_device *nvmem,
+ unsigned int offset, size_t bytes,
+ void *buf)
+{
+ return -ENOSYS;
+}
+
+static inline int nvmem_device_write(struct nvmem_device *nvmem,
+ unsigned int offset, size_t bytes,
+ void *buf)
+{
+ return -ENOSYS;
+}
+#endif /* CONFIG_NVMEM */
+
+#if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OFTREE)
+struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
+ const char *name);
+struct nvmem_device *of_nvmem_device_get(struct device_node *np,
+ const char *name);
+#else
+static inline struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
+ const char *name)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+static inline struct nvmem_device *of_nvmem_device_get(struct device_node *np,
+ const char *name)
+{
+ return ERR_PTR(-ENOSYS);
+}
+#endif /* CONFIG_NVMEM && CONFIG_OF */
+
+#endif /* ifndef _LINUX_NVMEM_CONSUMER_H */
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
new file mode 100644
index 0000000..0376bb0
--- /dev/null
+++ b/include/linux/nvmem-provider.h
@@ -0,0 +1,49 @@
+/*
+ * nvmem framework provider.
+ *
+ * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+ * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef _LINUX_NVMEM_PROVIDER_H
+#define _LINUX_NVMEM_PROVIDER_H
+
+struct device_d;
+struct nvmem_device;
+struct nvmem_cell_info;
+
+struct nvmem_config {
+ struct device_d *dev;
+ const char *name;
+ int id;
+ const struct nvmem_cell_info *cells;
+ int ncells;
+ bool read_only;
+};
+
+#if IS_ENABLED(CONFIG_NVMEM)
+
+struct nvmem_device *nvmem_register(const struct nvmem_config *cfg,
+ struct regmap *map);
+int nvmem_unregister(struct nvmem_device *nvmem);
+
+#else
+
+static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c,
+ struct regmap *map)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+static inline int nvmem_unregister(struct nvmem_device *nvmem)
+{
+ return -ENOSYS;
+}
+
+#endif /* CONFIG_NVMEM */
+
+#endif /* ifndef _LINUX_NVMEM_PROVIDER_H */
--
2.5.5
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 2/6] ocotp: Register OCOTP with 'nvmem'
2016-04-29 17:24 [PATCH 0/6] AIODEV subsystem Andrey Smirnov
2016-04-29 17:24 ` [PATCH 1/6] drivers: add nvmem framework from kernel Andrey Smirnov
@ 2016-04-29 17:24 ` Andrey Smirnov
2016-04-29 17:24 ` [PATCH 3/6] drivers: Introduce AIODEV subsystem Andrey Smirnov
` (4 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Andrey Smirnov @ 2016-04-29 17:24 UTC (permalink / raw)
To: barebox; +Cc: Andrey Smirnov
From: Sascha Hauer <s.hauer@pengutronix.de>
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
---
arch/arm/mach-imx/ocotp.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/arm/mach-imx/ocotp.c b/arch/arm/mach-imx/ocotp.c
index 1dc9108..3901619 100644
--- a/arch/arm/mach-imx/ocotp.c
+++ b/arch/arm/mach-imx/ocotp.c
@@ -28,6 +28,7 @@
#include <clock.h>
#include <regmap.h>
#include <linux/clk.h>
+#include <linux/nvmem-provider.h>
/*
* a single MAC address reference has the form
@@ -85,6 +86,7 @@ struct ocotp_priv {
int sense_enable;
char ethaddr[6];
struct regmap_config map_config;
+ struct nvmem_config nvmem_config;
};
static int imx6_ocotp_set_timing(struct ocotp_priv *priv)
@@ -412,6 +414,12 @@ static int imx_ocotp_probe(struct device_d *dev)
if (ret)
return ret;
+ priv->nvmem_config.name = "imx-ocotp";
+ priv->nvmem_config.read_only = true;
+ priv->nvmem_config.dev = dev;
+
+ nvmem_register(&priv->nvmem_config, priv->map);
+
if (IS_ENABLED(CONFIG_IMX_OCOTP_WRITE)) {
dev_add_param_bool(&(priv->dev), "permanent_write_enable",
NULL, NULL, &priv->permanent_write_enable, NULL);
--
2.5.5
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 3/6] drivers: Introduce AIODEV subsystem
2016-04-29 17:24 [PATCH 0/6] AIODEV subsystem Andrey Smirnov
2016-04-29 17:24 ` [PATCH 1/6] drivers: add nvmem framework from kernel Andrey Smirnov
2016-04-29 17:24 ` [PATCH 2/6] ocotp: Register OCOTP with 'nvmem' Andrey Smirnov
@ 2016-04-29 17:24 ` Andrey Smirnov
2016-05-03 6:13 ` Sascha Hauer
2016-05-03 6:21 ` Sascha Hauer
2016-04-29 17:24 ` [PATCH 4/6] commands: Add 'hwmon' command Andrey Smirnov
` (3 subsequent siblings)
6 siblings, 2 replies; 12+ messages in thread
From: Andrey Smirnov @ 2016-04-29 17:24 UTC (permalink / raw)
To: barebox; +Cc: Andrey Smirnov
From: Sascha Hauer <s.hauer@pengutronix.de>
AIODEV/Aiodevice is a analog I/O framework that can be thought of as a
simplified hybrid between 'hwmon' and 'IIO' subsystems of Linux kernel
This commit is very heavily based on 'iodevice' framework proposal
written by Sascha Hauer.
Signed-off-by: Andrey Smirnov <andrew.smrinov@gmail.com>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/Kconfig | 1 +
drivers/Makefile | 1 +
drivers/aiodev/Kconfig | 8 +++
drivers/aiodev/Makefile | 2 +
drivers/aiodev/core.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++
include/aiodev.h | 39 ++++++++++++++
6 files changed, 186 insertions(+)
create mode 100644 drivers/aiodev/Kconfig
create mode 100644 drivers/aiodev/Makefile
create mode 100644 drivers/aiodev/core.c
create mode 100644 include/aiodev.h
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 90ab7c1..eef68f6 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -1,6 +1,7 @@
menu "Drivers"
source "drivers/of/Kconfig"
+source "drivers/aiodev/Kconfig"
source "drivers/amba/Kconfig"
source "drivers/serial/Kconfig"
source "drivers/net/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 551b9a0..03bbc81 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -33,4 +33,5 @@ obj-$(CONFIG_GENERIC_PHY) += phy/
obj-$(CONFIG_HAB) += hab/
obj-$(CONFIG_CRYPTO_HW) += crypto/
obj-$(CONFIG_NVMEM) += nvmem/
+obj-$(CONFIG_AIODEV) += aiodev/
diff --git a/drivers/aiodev/Kconfig b/drivers/aiodev/Kconfig
new file mode 100644
index 0000000..d6d4ac0
--- /dev/null
+++ b/drivers/aiodev/Kconfig
@@ -0,0 +1,8 @@
+#
+# Misc strange devices
+#
+menuconfig AIODEV
+ bool "Analog I/O drivers"
+
+if AIODEV
+endif
diff --git a/drivers/aiodev/Makefile b/drivers/aiodev/Makefile
new file mode 100644
index 0000000..806464e
--- /dev/null
+++ b/drivers/aiodev/Makefile
@@ -0,0 +1,2 @@
+
+obj-$(CONFIG_AIODEV) += core.o
diff --git a/drivers/aiodev/core.c b/drivers/aiodev/core.c
new file mode 100644
index 0000000..6dcb917
--- /dev/null
+++ b/drivers/aiodev/core.c
@@ -0,0 +1,135 @@
+#include <common.h>
+#include <aiodev.h>
+#include <linux/list.h>
+#include <malloc.h>
+
+LIST_HEAD(aiodevices);
+EXPORT_SYMBOL(aiodevices);
+
+struct aiochannel *aiochannel_get_by_name(const char *name)
+{
+ struct aiodevice *aiodev;
+ int i;
+
+ list_for_each_entry(aiodev, &aiodevices, list) {
+ for (i = 0; i < aiodev->num_channels; i++)
+ if (!strcmp(name, aiodev->channels[i]->name))
+ return aiodev->channels[i];
+ }
+
+ return ERR_PTR(-ENOENT);
+}
+EXPORT_SYMBOL(aiochannel_get_by_name);
+
+struct aiochannel *aiochannel_get(struct device_d *dev, int index)
+{
+ struct of_phandle_args spec;
+ struct aiodevice *aiodev;
+ int ret, chnum = 0;
+
+ if (!dev->device_node)
+ return ERR_PTR(-EINVAL);
+
+ ret = of_parse_phandle_with_args(dev->device_node,
+ "aio-channels",
+ "#aio-channel-cells",
+ index, &spec);
+ if (ret)
+ return ERR_PTR(ret);
+
+ list_for_each_entry(aiodev, &aiodevices, list) {
+ if (aiodev->hwdev->device_node == spec.np)
+ goto found;
+ }
+
+ return ERR_PTR(-EPROBE_DEFER);
+
+found:
+ if (spec.args_count)
+ chnum = spec.args[0];
+
+ if (chnum >= aiodev->num_channels)
+ return ERR_PTR(-EINVAL);
+
+ return aiodev->channels[chnum];
+}
+EXPORT_SYMBOL(aiochannel_get);
+
+int aiochannel_get_value(struct aiochannel *aiochan, int *value)
+{
+ struct aiodevice *aiodev = aiochan->aiodev;
+
+ return aiodev->read(aiochan, value);
+}
+EXPORT_SYMBOL(aiochannel_get_value);
+
+int aiochannel_get_index(struct aiochannel *aiochan)
+{
+ int i;
+ struct aiodevice *aiodev = aiochan->aiodev;
+
+ for (i = 0; i < aiodev->num_channels; i++)
+ if (aiodev->channels[i] == aiochan)
+ return i;
+
+ return -ENOENT;
+}
+EXPORT_SYMBOL(aiochannel_get_index);
+
+static int aiochannel_param_get_value(struct param_d *p, void *priv)
+{
+ struct aiochannel *aiochan = priv;
+
+ return aiochannel_get_value(aiochan, &aiochan->value);
+}
+
+int aiodevice_register(struct aiodevice *aiodev)
+{
+ int i, ret;
+
+ if (!aiodev->name) {
+ if (aiodev->hwdev &&
+ aiodev->hwdev->device_node) {
+ aiodev->dev.id = DEVICE_ID_SINGLE;
+
+ aiodev->name = of_alias_get(aiodev->hwdev->device_node);
+ if (!aiodev->name)
+ aiodev->name = aiodev->hwdev->device_node->name;
+ }
+ }
+
+ if (!aiodev->name) {
+ aiodev->name = "aiodev";
+ aiodev->dev.id = DEVICE_ID_DYNAMIC;
+ }
+
+ strcpy(aiodev->dev.name, aiodev->name);
+
+ aiodev->dev.parent = aiodev->hwdev;
+
+ ret = register_device(&aiodev->dev);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < aiodev->num_channels; i++) {
+ struct aiochannel *aiochan = aiodev->channels[i];
+ char *name;
+
+ aiochan->aiodev = aiodev;
+
+ name = xasprintf("in_value%d_%s", i, aiochan->unit);
+
+ dev_add_param_int(&aiodev->dev, name, NULL,
+ aiochannel_param_get_value,
+ &aiochan->value, "%d", aiochan);
+
+ aiochan->name = xasprintf("%s.%s", aiodev->name, name);
+
+ free(name);
+ }
+
+ list_add_tail(&aiodev->list, &aiodevices);
+
+ return 0;
+}
+EXPORT_SYMBOL(aiodevice_register);
diff --git a/include/aiodev.h b/include/aiodev.h
new file mode 100644
index 0000000..21d8568
--- /dev/null
+++ b/include/aiodev.h
@@ -0,0 +1,39 @@
+#ifndef __AIODEVICE_H
+#define __AIODEVICE_H
+
+struct aiodevice;
+struct aiochannel {
+ char *unit;
+ struct aiodevice *aiodev;
+
+ int value;
+ char *name;
+};
+
+struct aiodevice {
+ const char *name;
+ int (*read)(struct aiochannel *, int *val);
+ struct device_d dev;
+ struct device_d *hwdev;
+ struct aiochannel **channels;
+ int num_channels;
+ struct list_head list;
+};
+
+int aiodevice_register(struct aiodevice *aiodev);
+
+struct aiochannel *aiochannel_get(struct device_d *dev, int index);
+struct aiochannel *aiochannel_get_by_name(const char *name);
+
+int aiochannel_get_value(struct aiochannel *aiochan, int *value);
+int aiochannel_get_index(struct aiochannel *aiochan);
+
+static inline const char *aiochannel_get_unit(struct aiochannel *aiochan)
+{
+ return aiochan->unit;
+}
+
+extern struct list_head aiodevices;
+#define for_each_aiodevice(aiodevice) list_for_each_entry(aiodevice, &aiodevices, list)
+
+#endif
--
2.5.5
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 3/6] drivers: Introduce AIODEV subsystem
2016-04-29 17:24 ` [PATCH 3/6] drivers: Introduce AIODEV subsystem Andrey Smirnov
@ 2016-05-03 6:13 ` Sascha Hauer
2016-05-04 15:47 ` Andrey Smirnov
2016-05-03 6:21 ` Sascha Hauer
1 sibling, 1 reply; 12+ messages in thread
From: Sascha Hauer @ 2016-05-03 6:13 UTC (permalink / raw)
To: Andrey Smirnov; +Cc: barebox, Andrey Smirnov
On Fri, Apr 29, 2016 at 10:24:03AM -0700, Andrey Smirnov wrote:
> From: Sascha Hauer <s.hauer@pengutronix.de>
>
> AIODEV/Aiodevice is a analog I/O framework that can be thought of as a
> simplified hybrid between 'hwmon' and 'IIO' subsystems of Linux kernel
>
> This commit is very heavily based on 'iodevice' framework proposal
> written by Sascha Hauer.
>
> Signed-off-by: Andrey Smirnov <andrew.smrinov@gmail.com>
> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
> ---
> drivers/Kconfig | 1 +
> drivers/Makefile | 1 +
> drivers/aiodev/Kconfig | 8 +++
> drivers/aiodev/Makefile | 2 +
> drivers/aiodev/core.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++
> include/aiodev.h | 39 ++++++++++++++
> 6 files changed, 186 insertions(+)
> create mode 100644 drivers/aiodev/Kconfig
> create mode 100644 drivers/aiodev/Makefile
> create mode 100644 drivers/aiodev/core.c
> create mode 100644 include/aiodev.h
>
> diff --git a/drivers/aiodev/Makefile b/drivers/aiodev/Makefile
> new file mode 100644
> index 0000000..806464e
> --- /dev/null
> +++ b/drivers/aiodev/Makefile
> @@ -0,0 +1,2 @@
> +
> +obj-$(CONFIG_AIODEV) += core.o
> diff --git a/drivers/aiodev/core.c b/drivers/aiodev/core.c
> new file mode 100644
> index 0000000..6dcb917
> --- /dev/null
> +++ b/drivers/aiodev/core.c
> @@ -0,0 +1,135 @@
> +#include <common.h>
> +#include <aiodev.h>
> +#include <linux/list.h>
> +#include <malloc.h>
GPL Header missing.
> +
> +LIST_HEAD(aiodevices);
> +EXPORT_SYMBOL(aiodevices);
> +
> +struct aiochannel *aiochannel_get_by_name(const char *name)
> +{
> + struct aiodevice *aiodev;
> + int i;
> +
> + list_for_each_entry(aiodev, &aiodevices, list) {
> + for (i = 0; i < aiodev->num_channels; i++)
> + if (!strcmp(name, aiodev->channels[i]->name))
> + return aiodev->channels[i];
> + }
> +
> + return ERR_PTR(-ENOENT);
> +}
> +EXPORT_SYMBOL(aiochannel_get_by_name);
> +
> +struct aiochannel *aiochannel_get(struct device_d *dev, int index)
> +{
> + struct of_phandle_args spec;
> + struct aiodevice *aiodev;
> + int ret, chnum = 0;
> +
> + if (!dev->device_node)
> + return ERR_PTR(-EINVAL);
> +
> + ret = of_parse_phandle_with_args(dev->device_node,
> + "aio-channels",
> + "#aio-channel-cells",
> + index, &spec);
#io-channel-cells is part of the official binding in
/dts/Bindings/iio/iio-bindings.txt. We should work with this existing
binding.
> + if (ret)
> + return ERR_PTR(ret);
> +
> + list_for_each_entry(aiodev, &aiodevices, list) {
> + if (aiodev->hwdev->device_node == spec.np)
> + goto found;
> + }
> +
> + return ERR_PTR(-EPROBE_DEFER);
> +
> +found:
> + if (spec.args_count)
> + chnum = spec.args[0];
> +
> + if (chnum >= aiodev->num_channels)
> + return ERR_PTR(-EINVAL);
> +
> + return aiodev->channels[chnum];
> +}
> +EXPORT_SYMBOL(aiochannel_get);
> +
> +int aiochannel_get_value(struct aiochannel *aiochan, int *value)
> +{
> + struct aiodevice *aiodev = aiochan->aiodev;
> +
> + return aiodev->read(aiochan, value);
> +}
> +EXPORT_SYMBOL(aiochannel_get_value);
> +
> +int aiochannel_get_index(struct aiochannel *aiochan)
> +{
> + int i;
> + struct aiodevice *aiodev = aiochan->aiodev;
> +
> + for (i = 0; i < aiodev->num_channels; i++)
> + if (aiodev->channels[i] == aiochan)
> + return i;
This function is unused in your patches. If the information this
function provides is needed, maybe better add a index member to struct
aiochannel to get rid of this loop?
> +
> + return -ENOENT;
> +}
> +EXPORT_SYMBOL(aiochannel_get_index);
> +
> +static int aiochannel_param_get_value(struct param_d *p, void *priv)
> +{
> + struct aiochannel *aiochan = priv;
> +
> + return aiochannel_get_value(aiochan, &aiochan->value);
> +}
> +
> +int aiodevice_register(struct aiodevice *aiodev)
> +{
> + int i, ret;
> +
> + if (!aiodev->name) {
> + if (aiodev->hwdev &&
> + aiodev->hwdev->device_node) {
if (!aiodev->name && aiodev->hwdev &&
aiodev->hwdev->device_node)
?
Sascha
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 3/6] drivers: Introduce AIODEV subsystem
2016-05-03 6:13 ` Sascha Hauer
@ 2016-05-04 15:47 ` Andrey Smirnov
0 siblings, 0 replies; 12+ messages in thread
From: Andrey Smirnov @ 2016-05-04 15:47 UTC (permalink / raw)
To: Sascha Hauer; +Cc: barebox, Andrey Smirnov
On Mon, May 2, 2016 at 11:13 PM, Sascha Hauer <s.hauer@pengutronix.de> wrote:
> On Fri, Apr 29, 2016 at 10:24:03AM -0700, Andrey Smirnov wrote:
>> From: Sascha Hauer <s.hauer@pengutronix.de>
>>
>> AIODEV/Aiodevice is a analog I/O framework that can be thought of as a
>> simplified hybrid between 'hwmon' and 'IIO' subsystems of Linux kernel
>>
>> This commit is very heavily based on 'iodevice' framework proposal
>> written by Sascha Hauer.
>>
>> Signed-off-by: Andrey Smirnov <andrew.smrinov@gmail.com>
>> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
>> ---
>> drivers/Kconfig | 1 +
>> drivers/Makefile | 1 +
>> drivers/aiodev/Kconfig | 8 +++
>> drivers/aiodev/Makefile | 2 +
>> drivers/aiodev/core.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++
>> include/aiodev.h | 39 ++++++++++++++
>> 6 files changed, 186 insertions(+)
>> create mode 100644 drivers/aiodev/Kconfig
>> create mode 100644 drivers/aiodev/Makefile
>> create mode 100644 drivers/aiodev/core.c
>> create mode 100644 include/aiodev.h
>>
>> diff --git a/drivers/aiodev/Makefile b/drivers/aiodev/Makefile
>> new file mode 100644
>> index 0000000..806464e
>> --- /dev/null
>> +++ b/drivers/aiodev/Makefile
>> @@ -0,0 +1,2 @@
>> +
>> +obj-$(CONFIG_AIODEV) += core.o
>> diff --git a/drivers/aiodev/core.c b/drivers/aiodev/core.c
>> new file mode 100644
>> index 0000000..6dcb917
>> --- /dev/null
>> +++ b/drivers/aiodev/core.c
>> @@ -0,0 +1,135 @@
>> +#include <common.h>
>> +#include <aiodev.h>
>> +#include <linux/list.h>
>> +#include <malloc.h>
>
> GPL Header missing.
OK, will fix in v2.
>
>> +
>> +LIST_HEAD(aiodevices);
>> +EXPORT_SYMBOL(aiodevices);
>> +
>> +struct aiochannel *aiochannel_get_by_name(const char *name)
>> +{
>> + struct aiodevice *aiodev;
>> + int i;
>> +
>> + list_for_each_entry(aiodev, &aiodevices, list) {
>> + for (i = 0; i < aiodev->num_channels; i++)
>> + if (!strcmp(name, aiodev->channels[i]->name))
>> + return aiodev->channels[i];
>> + }
>> +
>> + return ERR_PTR(-ENOENT);
>> +}
>> +EXPORT_SYMBOL(aiochannel_get_by_name);
>> +
>> +struct aiochannel *aiochannel_get(struct device_d *dev, int index)
>> +{
>> + struct of_phandle_args spec;
>> + struct aiodevice *aiodev;
>> + int ret, chnum = 0;
>> +
>> + if (!dev->device_node)
>> + return ERR_PTR(-EINVAL);
>> +
>> + ret = of_parse_phandle_with_args(dev->device_node,
>> + "aio-channels",
>> + "#aio-channel-cells",
>> + index, &spec);
>
> #io-channel-cells is part of the official binding in
> /dts/Bindings/iio/iio-bindings.txt. We should work with this existing
> binding.
Oh, I didn't realize that it was original IIO DT binding. Will fix in v2.
>
>> + if (ret)
>> + return ERR_PTR(ret);
>> +
>> + list_for_each_entry(aiodev, &aiodevices, list) {
>> + if (aiodev->hwdev->device_node == spec.np)
>> + goto found;
>> + }
>> +
>> + return ERR_PTR(-EPROBE_DEFER);
>> +
>> +found:
>> + if (spec.args_count)
>> + chnum = spec.args[0];
>> +
>> + if (chnum >= aiodev->num_channels)
>> + return ERR_PTR(-EINVAL);
>> +
>> + return aiodev->channels[chnum];
>> +}
>> +EXPORT_SYMBOL(aiochannel_get);
>> +
>> +int aiochannel_get_value(struct aiochannel *aiochan, int *value)
>> +{
>> + struct aiodevice *aiodev = aiochan->aiodev;
>> +
>> + return aiodev->read(aiochan, value);
>> +}
>> +EXPORT_SYMBOL(aiochannel_get_value);
>> +
>> +int aiochannel_get_index(struct aiochannel *aiochan)
>> +{
>> + int i;
>> + struct aiodevice *aiodev = aiochan->aiodev;
>> +
>> + for (i = 0; i < aiodev->num_channels; i++)
>> + if (aiodev->channels[i] == aiochan)
>> + return i;
>
> This function is unused in your patches. If the information this
> function provides is needed, maybe better add a index member to struct
> aiochannel to get rid of this loop?
>
It's used in one of the custom drivers I have in my tree. And yeah, I
think it's a good idea to store index and get rid of the loop. Will do
that in v2.
>> +
>> + return -ENOENT;
>> +}
>> +EXPORT_SYMBOL(aiochannel_get_index);
>> +
>> +static int aiochannel_param_get_value(struct param_d *p, void *priv)
>> +{
>> + struct aiochannel *aiochan = priv;
>> +
>> + return aiochannel_get_value(aiochan, &aiochan->value);
>> +}
>> +
>> +int aiodevice_register(struct aiodevice *aiodev)
>> +{
>> + int i, ret;
>> +
>> + if (!aiodev->name) {
>> + if (aiodev->hwdev &&
>> + aiodev->hwdev->device_node) {
>
> if (!aiodev->name && aiodev->hwdev &&
> aiodev->hwdev->device_node)
>
> ?
Agreed, there's no need to have a standalone if. Will fix in v2.
Thank you for reviewing my pathes!
Andrey
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 3/6] drivers: Introduce AIODEV subsystem
2016-04-29 17:24 ` [PATCH 3/6] drivers: Introduce AIODEV subsystem Andrey Smirnov
2016-05-03 6:13 ` Sascha Hauer
@ 2016-05-03 6:21 ` Sascha Hauer
1 sibling, 0 replies; 12+ messages in thread
From: Sascha Hauer @ 2016-05-03 6:21 UTC (permalink / raw)
To: Andrey Smirnov; +Cc: barebox
On Fri, Apr 29, 2016 at 10:24:03AM -0700, Andrey Smirnov wrote:
> From: Sascha Hauer <s.hauer@pengutronix.de>
>
> AIODEV/Aiodevice is a analog I/O framework that can be thought of as a
> simplified hybrid between 'hwmon' and 'IIO' subsystems of Linux kernel
>
> This commit is very heavily based on 'iodevice' framework proposal
> written by Sascha Hauer.
>
> Signed-off-by: Andrey Smirnov <andrew.smrinov@gmail.com>
^^^^
Sascha
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 4/6] commands: Add 'hwmon' command
2016-04-29 17:24 [PATCH 0/6] AIODEV subsystem Andrey Smirnov
` (2 preceding siblings ...)
2016-04-29 17:24 ` [PATCH 3/6] drivers: Introduce AIODEV subsystem Andrey Smirnov
@ 2016-04-29 17:24 ` Andrey Smirnov
2016-04-29 17:24 ` [PATCH 5/6] aiodev: Add TEMPMON driver Andrey Smirnov
` (2 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Andrey Smirnov @ 2016-04-29 17:24 UTC (permalink / raw)
To: barebox; +Cc: Andrey Smirnov
Add 'hwmon' command which allows to display the readings of all
hardware monitoring sensors registered with Barebox.
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
---
commands/Kconfig | 8 ++++++++
commands/Makefile | 1 +
commands/hwmon.c | 35 +++++++++++++++++++++++++++++++++++
3 files changed, 44 insertions(+)
create mode 100644 commands/hwmon.c
diff --git a/commands/Kconfig b/commands/Kconfig
index 57f2878..44a457b 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -1830,6 +1830,14 @@ config CMD_HWCLOCK
help
The hwclock command allows to query or set the hardware clock (RTC).
+config CMD_HWMON
+ bool
+ depends on AIODEV
+ prompt "hwmon command"
+ default y
+ help
+ The hwmon command allows to query hardware sensors.
+
config CMD_I2C
bool
depends on I2C
diff --git a/commands/Makefile b/commands/Makefile
index 065d649..3c8ad77 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -107,6 +107,7 @@ obj-$(CONFIG_CMD_REGULATOR) += regulator.o
obj-$(CONFIG_CMD_LSPCI) += lspci.o
obj-$(CONFIG_CMD_IMD) += imd.o
obj-$(CONFIG_CMD_HWCLOCK) += hwclock.o
+obj-$(CONFIG_CMD_HWMON) += hwmon.o
obj-$(CONFIG_CMD_USBGADGET) += usbgadget.o
obj-$(CONFIG_CMD_FIRMWARELOAD) += firmwareload.o
obj-$(CONFIG_CMD_CMP) += cmp.o
diff --git a/commands/hwmon.c b/commands/hwmon.c
new file mode 100644
index 0000000..ace4503
--- /dev/null
+++ b/commands/hwmon.c
@@ -0,0 +1,35 @@
+#include <common.h>
+#include <command.h>
+#include <getopt.h>
+#include <linux/err.h>
+#include <linux/ctype.h>
+#include <string.h>
+#include <environment.h>
+#include <aiodev.h>
+
+static int do_hwmon(int argc, char *argv[])
+{
+ int i;
+ struct aiodevice *aiodev;
+
+ for_each_aiodevice(aiodev) {
+ for (i = 0; i < aiodev->num_channels; i++) {
+ struct aiochannel *chan = aiodev->channels[i];
+ int value;
+ int ret = aiochannel_get_value(chan, &value);
+
+ if (!ret)
+ printf("%s: %d %s\n", chan->name, value, chan->unit);
+ else
+ printf("%s: failed to read (%d)\n", chan->name, ret);
+ }
+ }
+
+ return 0;
+}
+
+BAREBOX_CMD_START(hwmon)
+ .cmd = do_hwmon,
+ BAREBOX_CMD_DESC("query hardware sensors (HWMON)")
+ BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP)
+BAREBOX_CMD_END
--
2.5.5
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 5/6] aiodev: Add TEMPMON driver
2016-04-29 17:24 [PATCH 0/6] AIODEV subsystem Andrey Smirnov
` (3 preceding siblings ...)
2016-04-29 17:24 ` [PATCH 4/6] commands: Add 'hwmon' command Andrey Smirnov
@ 2016-04-29 17:24 ` Andrey Smirnov
2016-04-29 17:24 ` [PATCH 6/6] aiodev: Add basic LM75 temperature driver Andrey Smirnov
2016-05-03 5:46 ` [PATCH 0/6] AIODEV subsystem Sascha Hauer
6 siblings, 0 replies; 12+ messages in thread
From: Andrey Smirnov @ 2016-04-29 17:24 UTC (permalink / raw)
To: barebox; +Cc: Andrey Smirnov
Port TEMPMON driver from U-Boot
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
---
arch/arm/dts/imx6qdl.dtsi | 14 +++
arch/arm/dts/imx6sx.dtsi | 14 +++
drivers/aiodev/Kconfig | 8 ++
drivers/aiodev/Makefile | 1 +
drivers/aiodev/imx_thermal.c | 215 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 252 insertions(+)
create mode 100644 drivers/aiodev/imx_thermal.c
diff --git a/arch/arm/dts/imx6qdl.dtsi b/arch/arm/dts/imx6qdl.dtsi
index 828be9c..0deafbc 100644
--- a/arch/arm/dts/imx6qdl.dtsi
+++ b/arch/arm/dts/imx6qdl.dtsi
@@ -8,3 +8,17 @@
ipu0 = &ipu1;
};
};
+
+&ocotp {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ analog1: ocotp_ana1@34 {
+ reg = <0x38 0x4>;
+ };
+};
+
+&tempmon {
+ nvmem-cells = <&analog1>;
+ nvmem-cell-names = "ocotp-ana1";
+};
diff --git a/arch/arm/dts/imx6sx.dtsi b/arch/arm/dts/imx6sx.dtsi
index 5a8ee46..89678b2 100644
--- a/arch/arm/dts/imx6sx.dtsi
+++ b/arch/arm/dts/imx6sx.dtsi
@@ -10,3 +10,17 @@
pwm7 = &pwm8;
};
};
+
+&ocotp {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ analog1: ocotp_ana1@34 {
+ reg = <0x34 0x4>;
+ };
+};
+
+&tempmon {
+ nvmem-cells = <&analog1>;
+ nvmem-cell-names = "ocotp-ana1";
+};
diff --git a/drivers/aiodev/Kconfig b/drivers/aiodev/Kconfig
index d6d4ac0..c877bb7 100644
--- a/drivers/aiodev/Kconfig
+++ b/drivers/aiodev/Kconfig
@@ -5,4 +5,12 @@ menuconfig AIODEV
bool "Analog I/O drivers"
if AIODEV
+
+config IMX_THERMAL
+ tristate "Temperature sensor driver for Freescale i.MX SoCs"
+ select NVMEM
+ help
+ Support for Temperature Monitor (TEMPMON) found on Freescale
+ i.MX SoCs.
+
endif
diff --git a/drivers/aiodev/Makefile b/drivers/aiodev/Makefile
index 806464e..5480a8a 100644
--- a/drivers/aiodev/Makefile
+++ b/drivers/aiodev/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_AIODEV) += core.o
+obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o
diff --git a/drivers/aiodev/imx_thermal.c b/drivers/aiodev/imx_thermal.c
new file mode 100644
index 0000000..bc1c4ac
--- /dev/null
+++ b/drivers/aiodev/imx_thermal.c
@@ -0,0 +1,215 @@
+#include <common.h>
+#include <init.h>
+#include <malloc.h>
+#include <clock.h>
+#include <driver.h>
+#include <xfuncs.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/math64.h>
+#include <linux/log2.h>
+#include <linux/clk.h>
+#include <mach/clock.h>
+#include <mach/imx6-anadig.h>
+#include <io.h>
+#include <aiodev.h>
+#include <linux/nvmem-consumer.h>
+
+#define FACTOR0 10000000
+#define MEASURE_FREQ 327
+
+struct imx_thermal_data {
+ int c1, c2;
+ void __iomem *base;
+ struct clk *clk;
+
+ struct aiodevice aiodev;
+ struct aiochannel aiochan;
+};
+
+static inline struct imx_thermal_data *
+to_imx_thermal_data(struct aiochannel *chan)
+{
+ return container_of(chan, struct imx_thermal_data, aiochan);
+}
+
+
+static int imx_thermal_read(struct aiochannel *chan, int *val)
+{
+ uint64_t start;
+ uint32_t tempsense0, tempsense1;
+ uint16_t n_meas;
+ struct imx_thermal_data *imx_thermal = to_imx_thermal_data(chan);
+
+ /*
+ * now we only use single measure, every time we read
+ * the temperature, we will power on/down anadig thermal
+ * module
+ */
+ writel(BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+ imx_thermal->base + HW_ANADIG_TEMPSENSE0_CLR);
+ writel(BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF,
+ imx_thermal->base + HW_ANADIG_ANA_MISC0_SET);
+
+ /* setup measure freq */
+ tempsense1 = readl(imx_thermal->base + HW_ANADIG_TEMPSENSE1);
+ tempsense1 &= ~BM_ANADIG_TEMPSENSE1_MEASURE_FREQ;
+ tempsense1 |= MEASURE_FREQ;
+ writel(tempsense1, imx_thermal->base + HW_ANADIG_TEMPSENSE1);
+
+ /* start the measurement process */
+ writel(BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
+ imx_thermal->base + HW_ANADIG_TEMPSENSE0_CLR);
+ writel(BM_ANADIG_TEMPSENSE0_FINISHED,
+ imx_thermal->base + HW_ANADIG_TEMPSENSE0_CLR);
+ writel(BM_ANADIG_TEMPSENSE0_MEASURE_TEMP,
+ imx_thermal->base + HW_ANADIG_TEMPSENSE0_SET);
+
+ /* make sure that the latest temp is valid */
+ start = get_time_ns();
+ do {
+ tempsense0 = readl(imx_thermal->base + HW_ANADIG_TEMPSENSE0);
+
+ if (is_timeout(start, 1 * SECOND)) {
+ dev_err(imx_thermal->aiodev.hwdev,
+ "Timeout waiting for measurement\n");
+ return -EIO;
+ }
+ } while (!(tempsense0 & BM_ANADIG_TEMPSENSE0_FINISHED));
+
+ n_meas = (tempsense0 & BM_ANADIG_TEMPSENSE0_TEMP_VALUE)
+ >> BP_ANADIG_TEMPSENSE0_TEMP_VALUE;
+ writel(BM_ANADIG_TEMPSENSE0_FINISHED,
+ imx_thermal->base + HW_ANADIG_TEMPSENSE0_CLR);
+
+ *val = (int)n_meas * imx_thermal->c1 + imx_thermal->c2;
+
+ /* power down anatop thermal sensor */
+ writel(BM_ANADIG_TEMPSENSE0_POWER_DOWN,
+ imx_thermal->base + HW_ANADIG_TEMPSENSE0_SET);
+ writel(BM_ANADIG_ANA_MISC0_REFTOP_SELBIASOFF,
+ imx_thermal->base + HW_ANADIG_ANA_MISC0_CLR);
+
+ return 0;
+}
+
+static int imx_thermal_probe(struct device_d *dev)
+{
+ uint32_t *ocotp_ana1;
+ struct device_node *node;
+ struct imx_thermal_data *imx_thermal;
+ struct device_d *anatop;
+ struct nvmem_cell *cell;
+ struct resource *res;
+ int t1, n1, t2, n2;
+ int ret;
+ size_t len;
+
+ cell = nvmem_cell_get(dev, "ocotp-ana1");
+ if (IS_ERR(cell)) {
+ dev_err(dev, "Failed to get calibration data cell: %s\n",
+ strerrorp(cell));
+ return PTR_ERR(cell);
+ }
+
+ ocotp_ana1 = nvmem_cell_read(cell, &len);
+ if (IS_ERR(ocotp_ana1)) {
+ ret = PTR_ERR(ocotp_ana1);
+ dev_err(dev, "Failed to read calibration data cell: %s\n",
+ strerrorp(ocotp_ana1));
+ goto put_nvmem_cell;
+ }
+
+ if (len != sizeof(*ocotp_ana1)) {
+ ret = -EINVAL;
+ dev_err(dev, "Unexpected size of calibration data cell\n");
+ goto free_calibration_data;
+ }
+
+ node = of_parse_phandle(dev->device_node, "fsl,tempmon", 0);
+ if (!node) {
+ ret = -EINVAL;
+ dev_err(dev, "Failed to parse fsl,tempmon\n");
+ goto free_calibration_data;
+ }
+
+ anatop = of_find_device_by_node(node);
+ if (!anatop) {
+ ret = -EINVAL;
+ dev_err(dev, "No device corresponds to fsl,tempmon\n");
+ goto free_calibration_data;
+ }
+
+ imx_thermal = xzalloc(sizeof(*imx_thermal));
+
+ res = dev_request_mem_resource(anatop, 0);
+ if (IS_ERR(res)) {
+ ret = PTR_ERR(res);
+ dev_err(dev, "Failed to request anatop resource: %s\n",
+ strerrorp(res));
+ goto free_imx_thermal;
+ }
+ imx_thermal->base = IOMEM(res->start);
+
+ n1 = *ocotp_ana1 >> 20;
+ t1 = 25;
+ n2 = (*ocotp_ana1 & 0x000FFF00) >> 8;
+ t2 = *ocotp_ana1 & 0xFF;
+
+ imx_thermal->c1 = (-1000 * (t2 - t1)) / (n1 - n2);
+ imx_thermal->c2 = 1000 * t2 + (1000 * n2 * (t2 - t1)) / (n1 - n2);
+
+ imx_thermal->clk = clk_get(dev, NULL);
+ if (IS_ERR(imx_thermal->clk)) {
+ ret = PTR_ERR(imx_thermal->clk);
+ goto free_res;
+ }
+
+ ret = clk_enable(imx_thermal->clk);
+ if (ret) {
+ dev_err(dev, "Failed to enable clock: %s\n",
+ strerror(ret));
+ goto put_clock;
+ }
+
+ imx_thermal->aiodev.num_channels = 1;
+ imx_thermal->aiodev.hwdev = dev;
+ imx_thermal->aiodev.channels =
+ xmalloc(imx_thermal->aiodev.num_channels *
+ sizeof(imx_thermal->aiodev.channels[0]));
+ imx_thermal->aiodev.channels[0] = &imx_thermal->aiochan;
+ imx_thermal->aiochan.unit = "mC";
+ imx_thermal->aiodev.read = imx_thermal_read;
+
+ ret = aiodevice_register(&imx_thermal->aiodev);
+ if (!ret)
+ goto free_calibration_data;
+
+ clk_disable(imx_thermal->clk);
+put_clock:
+ clk_put(imx_thermal->clk);
+free_res:
+ release_region(res);
+free_imx_thermal:
+ kfree(imx_thermal);
+free_calibration_data:
+ free(ocotp_ana1);
+put_nvmem_cell:
+ nvmem_cell_put(cell);
+ return ret;
+}
+
+static const struct of_device_id of_imx_thermal_match[] = {
+ { .compatible = "fsl,imx6q-tempmon", },
+ { .compatible = "fsl,imx6sx-tempmon", },
+ { /* end */ }
+};
+
+
+static struct driver_d imx_thermal_driver = {
+ .name = "imx_thermal",
+ .probe = imx_thermal_probe,
+ .of_compatible = DRV_OF_COMPAT(of_imx_thermal_match),
+};
+
+device_platform_driver(imx_thermal_driver);
--
2.5.5
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 6/6] aiodev: Add basic LM75 temperature driver
2016-04-29 17:24 [PATCH 0/6] AIODEV subsystem Andrey Smirnov
` (4 preceding siblings ...)
2016-04-29 17:24 ` [PATCH 5/6] aiodev: Add TEMPMON driver Andrey Smirnov
@ 2016-04-29 17:24 ` Andrey Smirnov
2016-05-03 5:46 ` [PATCH 0/6] AIODEV subsystem Sascha Hauer
6 siblings, 0 replies; 12+ messages in thread
From: Andrey Smirnov @ 2016-04-29 17:24 UTC (permalink / raw)
To: barebox; +Cc: Andrey Smirnov
From: Sascha Hauer <s.hauer@pengutronix.de>
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/aiodev/Kconfig | 6 ++
drivers/aiodev/Makefile | 1 +
drivers/aiodev/lm75.c | 262 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 269 insertions(+)
create mode 100644 drivers/aiodev/lm75.c
diff --git a/drivers/aiodev/Kconfig b/drivers/aiodev/Kconfig
index c877bb7..8a78e2a 100644
--- a/drivers/aiodev/Kconfig
+++ b/drivers/aiodev/Kconfig
@@ -13,4 +13,10 @@ config IMX_THERMAL
Support for Temperature Monitor (TEMPMON) found on Freescale
i.MX SoCs.
+config LM75
+ tristate "LM75 driver"
+ depends on I2C
+ help
+ Support for LM75 and similar devices
+
endif
diff --git a/drivers/aiodev/Makefile b/drivers/aiodev/Makefile
index 5480a8a..c3d2b08 100644
--- a/drivers/aiodev/Makefile
+++ b/drivers/aiodev/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_AIODEV) += core.o
obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o
+obj-$(CONFIG_LM75) += lm75.o
diff --git a/drivers/aiodev/lm75.c b/drivers/aiodev/lm75.c
new file mode 100644
index 0000000..b4da5a0
--- /dev/null
+++ b/drivers/aiodev/lm75.c
@@ -0,0 +1,262 @@
+/*
+ * lm75.c - Part of lm_sensors, Linux kernel modules for hardware
+ * monitoring
+ * Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <common.h>
+#include <init.h>
+#include <driver.h>
+#include <xfuncs.h>
+#include <i2c/i2c.h>
+#include <aiodev.h>
+
+/*
+ * This driver handles the LM75 and compatible digital temperature sensors.
+ */
+
+/* straight from the datasheet */
+#define LM75_TEMP_MIN (-55000)
+#define LM75_TEMP_MAX 125000
+#define LM75_SHUTDOWN 0x01
+
+enum lm75_type { /* keep sorted in alphabetical order */
+ adt75,
+ ds1775,
+ ds75,
+ ds7505,
+ g751,
+ lm75,
+ lm75a,
+ lm75b,
+ max6625,
+ max6626,
+ mcp980x,
+ stds75,
+ tcn75,
+ tmp100,
+ tmp101,
+ tmp105,
+ tmp112,
+ tmp175,
+ tmp275,
+ tmp75,
+ tmp75c,
+};
+
+/* The LM75 registers */
+#define LM75_REG_CONF 0x01
+static const u8 LM75_REG_TEMP[3] = {
+ 0x00, /* input */
+ 0x03, /* max */
+ 0x02, /* hyst */
+};
+
+/* Each client has this additional data */
+struct lm75_data {
+ struct i2c_client *client;
+ struct device_d dev;
+ u8 resolution; /* In bits, between 9 and 12 */
+ struct aiochannel aiochan;
+ struct aiodevice aiodev;
+};
+
+static int lm75_read_value(struct lm75_data *data, u8 reg)
+{
+ if (reg == LM75_REG_CONF)
+ return i2c_smbus_read_byte_data(data->client, reg);
+ else
+ return i2c_smbus_read_word_swapped(data->client, reg);
+}
+
+static int lm75_write_value(struct lm75_data *data, u8 reg, u16 value)
+{
+ if (reg == LM75_REG_CONF)
+ return i2c_smbus_write_byte_data(data->client, reg, value);
+ else
+ return i2c_smbus_write_word_swapped(data->client, reg, value);
+}
+
+static long lm75_reg_to_mc(s16 temp, u8 resolution)
+{
+ return ((temp >> (16 - resolution)) * 1000) >> (resolution - 8);
+}
+
+static int lm75_get_temp(struct aiochannel *chan, int *val)
+{
+ struct lm75_data *data = container_of(chan, struct lm75_data, aiochan);
+ int status;
+
+ status = lm75_read_value(data, LM75_REG_TEMP[0]);
+ if (status < 0) {
+ dev_err(&data->client->dev,
+ "LM75: Failed to read value: reg %d, error %d\n",
+ LM75_REG_TEMP[0], status);
+ return status;
+ }
+
+ *val = lm75_reg_to_mc(status, data->resolution);
+
+ return 0;
+}
+
+static int lm75_probe(struct device_d *dev)
+{
+ struct lm75_data *data;
+ int status;
+ u8 set_mask, clr_mask;
+ int new, ret;
+ enum lm75_type kind;
+
+ ret = dev_get_drvdata(dev, (const void **)&kind);
+ if (ret)
+ return ret;
+
+ data = xzalloc(sizeof(*data));
+
+ data->client = to_i2c_client(dev);
+
+ /* Set to LM75 resolution (9 bits, 1/2 degree C) and range.
+ * Then tweak to be more precise when appropriate.
+ */
+ set_mask = 0;
+ clr_mask = LM75_SHUTDOWN; /* continuous conversions */
+
+ switch (kind) {
+ case adt75:
+ clr_mask |= 1 << 5; /* not one-shot mode */
+ data->resolution = 12;
+ break;
+ case ds1775:
+ case ds75:
+ case stds75:
+ clr_mask |= 3 << 5;
+ set_mask |= 2 << 5; /* 11-bit mode */
+ data->resolution = 11;
+ break;
+ case ds7505:
+ set_mask |= 3 << 5; /* 12-bit mode */
+ data->resolution = 12;
+ break;
+ case g751:
+ case lm75:
+ case lm75a:
+ data->resolution = 9;
+ break;
+ case lm75b:
+ data->resolution = 11;
+ break;
+ case max6625:
+ data->resolution = 9;
+ break;
+ case max6626:
+ data->resolution = 12;
+ break;
+ case tcn75:
+ data->resolution = 9;
+ break;
+ case mcp980x:
+ /* fall through */
+ case tmp100:
+ case tmp101:
+ set_mask |= 3 << 5; /* 12-bit mode */
+ data->resolution = 12;
+ clr_mask |= 1 << 7; /* not one-shot mode */
+ break;
+ case tmp112:
+ set_mask |= 3 << 5; /* 12-bit mode */
+ clr_mask |= 1 << 7; /* not one-shot mode */
+ data->resolution = 12;
+ break;
+ case tmp105:
+ case tmp175:
+ case tmp275:
+ case tmp75:
+ set_mask |= 3 << 5; /* 12-bit mode */
+ clr_mask |= 1 << 7; /* not one-shot mode */
+ data->resolution = 12;
+ break;
+ case tmp75c:
+ clr_mask |= 1 << 5; /* not one-shot mode */
+ data->resolution = 12;
+ break;
+ }
+
+ /* configure as specified */
+ status = lm75_read_value(data, LM75_REG_CONF);
+ if (status < 0) {
+ dev_dbg(dev, "Can't read config? %d\n", status);
+ return status;
+ }
+
+ new = status & ~clr_mask;
+ new |= set_mask;
+ if (status != new)
+ lm75_write_value(data, LM75_REG_CONF, new);
+
+ data->aiodev.num_channels = 1;
+ data->aiodev.hwdev = dev;
+ data->aiodev.read = lm75_get_temp;
+ data->aiodev.channels = xmalloc(sizeof(void *));
+ data->aiodev.channels[0] = &data->aiochan;
+ data->aiochan.unit = "mC";
+
+ ret = aiodevice_register(&data->aiodev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct platform_device_id lm75_ids[] = {
+ { .name = "adt75", .driver_data = adt75, },
+ { .name = "ds1775", .driver_data = ds1775, },
+ { .name = "ds75", .driver_data = ds75, },
+ { .name = "ds7505", .driver_data = ds7505, },
+ { .name = "g751", .driver_data = g751, },
+ { .name = "lm75", .driver_data = lm75, },
+ { .name = "lm75a", .driver_data = lm75a, },
+ { .name = "lm75b", .driver_data = lm75b, },
+ { .name = "max6625", .driver_data = max6625, },
+ { .name = "max6626", .driver_data = max6626, },
+ { .name = "mcp980x", .driver_data = mcp980x, },
+ { .name = "stds75", .driver_data = stds75, },
+ { .name = "tcn75", .driver_data = tcn75, },
+ { .name = "tmp100", .driver_data = tmp100, },
+ { .name = "tmp101", .driver_data = tmp101, },
+ { .name = "tmp105", .driver_data = tmp105, },
+ { .name = "tmp112", .driver_data = tmp112, },
+ { .name = "tmp175", .driver_data = tmp175, },
+ { .name = "tmp275", .driver_data = tmp275, },
+ { .name = "tmp75", .driver_data = tmp75, },
+ { .name = "tmp75c", .driver_data = tmp75c, },
+ { /* LIST END */ }
+};
+
+static struct driver_d lm75_driver = {
+ .name = "lm75",
+ .probe = lm75_probe,
+ .id_table = lm75_ids,
+};
+
+static int lm75_init(void)
+{
+ i2c_driver_register(&lm75_driver);
+ return 0;
+}
+
+device_initcall(lm75_init);
--
2.5.5
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 0/6] AIODEV subsystem
2016-04-29 17:24 [PATCH 0/6] AIODEV subsystem Andrey Smirnov
` (5 preceding siblings ...)
2016-04-29 17:24 ` [PATCH 6/6] aiodev: Add basic LM75 temperature driver Andrey Smirnov
@ 2016-05-03 5:46 ` Sascha Hauer
2016-05-04 15:39 ` Andrey Smirnov
6 siblings, 1 reply; 12+ messages in thread
From: Sascha Hauer @ 2016-05-03 5:46 UTC (permalink / raw)
To: Andrey Smirnov; +Cc: barebox
On Fri, Apr 29, 2016 at 10:24:00AM -0700, Andrey Smirnov wrote:
> Hello everone,
>
> This series of patches is a combined version of "hwmon" and "iodev"
> proposals, submitted several months ago by me and Sascha respectively.
>
> The main purpose of this subsystem is to provde means of exposing
> different analog sensors(temperature, voltage, etc.) or, potentially,
> "actuators"(e.g. DACs) in a uniformed fashion.
>
> This series introduces the subsystem itself, a helper command to
> display values of all registersd sensors ("hwmon"), and a two drivers
> leveraging the AIODEV subsystem API (LM75 and TEMPMON).
>
> Additionaly, due to TEMPMON driver's need to obtain calibraion
> information from OCOTP, this patchset adds Steffen Trumtrar's port of
> NVMEM subsytem from Linux kernel.
>
> Sascha, you didn't like "iodev" as a name, so I changed it and I hope
> you like this one better :-)
Yes, I do. It's more unique ;)
So, the 'A' is for Analog, right?
Sascha
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 0/6] AIODEV subsystem
2016-05-03 5:46 ` [PATCH 0/6] AIODEV subsystem Sascha Hauer
@ 2016-05-04 15:39 ` Andrey Smirnov
0 siblings, 0 replies; 12+ messages in thread
From: Andrey Smirnov @ 2016-05-04 15:39 UTC (permalink / raw)
To: Sascha Hauer; +Cc: barebox
On Mon, May 2, 2016 at 10:46 PM, Sascha Hauer <s.hauer@pengutronix.de> wrote:
> On Fri, Apr 29, 2016 at 10:24:00AM -0700, Andrey Smirnov wrote:
>> Hello everone,
>>
>> This series of patches is a combined version of "hwmon" and "iodev"
>> proposals, submitted several months ago by me and Sascha respectively.
>>
>> The main purpose of this subsystem is to provde means of exposing
>> different analog sensors(temperature, voltage, etc.) or, potentially,
>> "actuators"(e.g. DACs) in a uniformed fashion.
>>
>> This series introduces the subsystem itself, a helper command to
>> display values of all registersd sensors ("hwmon"), and a two drivers
>> leveraging the AIODEV subsystem API (LM75 and TEMPMON).
>>
>> Additionaly, due to TEMPMON driver's need to obtain calibraion
>> information from OCOTP, this patchset adds Steffen Trumtrar's port of
>> NVMEM subsytem from Linux kernel.
>>
>> Sascha, you didn't like "iodev" as a name, so I changed it and I hope
>> you like this one better :-)
>
> Yes, I do. It's more unique ;)
>
> So, the 'A' is for Analog, right?
Correct, 'A' is indeed stands for "analog".
>
> Sascha
>
> --
> Pengutronix e.K. | |
> Industrial Linux Solutions | http://www.pengutronix.de/ |
> Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
^ permalink raw reply [flat|nested] 12+ messages in thread