mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH 0/6] AIODEV subsystem
@ 2016-04-29 17:24 Andrey Smirnov
  2016-04-29 17:24 ` [PATCH 1/6] drivers: add nvmem framework from kernel Andrey Smirnov
                   ` (6 more replies)
  0 siblings, 7 replies; 13+ messages in thread
From: Andrey Smirnov @ 2016-04-29 17:24 UTC (permalink / raw)
  To: barebox; +Cc: Andrey Smirnov

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 :-)

Andrey Smirnov (2):
  commands: Add 'hwmon' command
  aiodev: Add TEMPMON driver

Sascha Hauer (3):
  ocotp: Register OCOTP with 'nvmem'
  drivers: Introduce AIODEV subsystem
  aiodev: Add basic LM75 temperature driver

Steffen Trumtrar (1):
  drivers: add nvmem framework from kernel

 arch/arm/dts/imx6qdl.dtsi      |  14 +
 arch/arm/dts/imx6sx.dtsi       |  14 +
 arch/arm/mach-imx/ocotp.c      |   8 +
 commands/Kconfig               |   8 +
 commands/Makefile              |   1 +
 commands/hwmon.c               |  35 ++
 drivers/Kconfig                |   3 +-
 drivers/Makefile               |   3 +
 drivers/aiodev/Kconfig         |  22 ++
 drivers/aiodev/Makefile        |   4 +
 drivers/aiodev/core.c          | 135 ++++++++
 drivers/aiodev/imx_thermal.c   | 215 ++++++++++++
 drivers/aiodev/lm75.c          | 262 ++++++++++++++
 drivers/nvmem/Kconfig          |   7 +
 drivers/nvmem/Makefile         |   6 +
 drivers/nvmem/core.c           | 749 +++++++++++++++++++++++++++++++++++++++++
 include/aiodev.h               |  39 +++
 include/linux/nvmem-consumer.h | 157 +++++++++
 include/linux/nvmem-provider.h |  49 +++
 19 files changed, 1730 insertions(+), 1 deletion(-)
 create mode 100644 commands/hwmon.c
 create mode 100644 drivers/aiodev/Kconfig
 create mode 100644 drivers/aiodev/Makefile
 create mode 100644 drivers/aiodev/core.c
 create mode 100644 drivers/aiodev/imx_thermal.c
 create mode 100644 drivers/aiodev/lm75.c
 create mode 100644 drivers/nvmem/Kconfig
 create mode 100644 drivers/nvmem/Makefile
 create mode 100644 drivers/nvmem/core.c
 create mode 100644 include/aiodev.h
 create mode 100644 include/linux/nvmem-consumer.h
 create mode 100644 include/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] 13+ messages in thread

* [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; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ messages in thread

* [PATCH 3/6] drivers: Introduce AIODEV subsystem
@ 2016-04-29 17:31 Andrey Smirnov
  0 siblings, 0 replies; 13+ messages in thread
From: Andrey Smirnov @ 2016-04-29 17:31 UTC (permalink / raw)
  To: barebox; +Cc: Andrey Smirnov

From: Andrey Smirnov <andrew.smrinov@gmail.com>

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] 13+ messages in thread

end of thread, other threads:[~2016-05-04 15:47 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [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
2016-04-29 17:24 ` [PATCH 4/6] commands: Add 'hwmon' command Andrey Smirnov
2016-04-29 17:24 ` [PATCH 5/6] aiodev: Add TEMPMON driver 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
2016-05-04 15:39   ` Andrey Smirnov
2016-04-29 17:31 [PATCH 3/6] drivers: Introduce " Andrey Smirnov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox