All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/1] iio: light: add MAX30100 oximeter driver support
@ 2015-12-04  5:15 Matt Ranostay
  2015-12-04  5:15 ` [PATCH v5 1/1] " Matt Ranostay
  0 siblings, 1 reply; 4+ messages in thread
From: Matt Ranostay @ 2015-12-04  5:15 UTC (permalink / raw)
  To: linux-iio, jic23; +Cc: Matt Ranostay

Changes from v4:
* Removed whitespace from documention
* Reorder statement in max30100_read_raw to reduce un-needed statements
* Changed buffer from u8 array to __be16 array

Matt Ranostay (1):
  iio: light: add MAX30100 oximeter driver support

 .../devicetree/bindings/iio/health/max30100.txt    |  21 +
 drivers/iio/Kconfig                                |   1 +
 drivers/iio/Makefile                               |   1 +
 drivers/iio/health/Kconfig                         |  21 +
 drivers/iio/health/Makefile                        |   7 +
 drivers/iio/health/max30100.c                      | 457 +++++++++++++++++++++
 6 files changed, 508 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/max30100.txt
 create mode 100644 drivers/iio/health/Kconfig
 create mode 100644 drivers/iio/health/Makefile
 create mode 100644 drivers/iio/health/max30100.c

-- 
1.9.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v5 1/1] iio: light: add MAX30100 oximeter driver support
  2015-12-04  5:15 [PATCH v5 0/1] iio: light: add MAX30100 oximeter driver support Matt Ranostay
@ 2015-12-04  5:15 ` Matt Ranostay
  2015-12-05 17:27   ` Jonathan Cameron
  0 siblings, 1 reply; 4+ messages in thread
From: Matt Ranostay @ 2015-12-04  5:15 UTC (permalink / raw)
  To: linux-iio, jic23; +Cc: Matt Ranostay

MAX30100 is an heart rate and pulse oximeter sensor that works using
two LEDS of different wavelengths, and detecting the light reflected
back.

This patchset adds support for both IR and RED LED channels which can
be processed in userspace to determine heart rate and blood oxygen
levels.

Signed-off-by: Matt Ranostay <mranostay@gmail.com>
---
 .../devicetree/bindings/iio/health/max30100.txt    |  21 +
 drivers/iio/Kconfig                                |   1 +
 drivers/iio/Makefile                               |   1 +
 drivers/iio/health/Kconfig                         |  21 +
 drivers/iio/health/Makefile                        |   7 +
 drivers/iio/health/max30100.c                      | 457 +++++++++++++++++++++
 6 files changed, 508 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/health/max30100.txt
 create mode 100644 drivers/iio/health/Kconfig
 create mode 100644 drivers/iio/health/Makefile
 create mode 100644 drivers/iio/health/max30100.c

diff --git a/Documentation/devicetree/bindings/iio/health/max30100.txt b/Documentation/devicetree/bindings/iio/health/max30100.txt
new file mode 100644
index 0000000..f6fbac6
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/health/max30100.txt
@@ -0,0 +1,21 @@
+Maxim MAX30100 heart rate and pulse oximeter sensor
+
+* https://datasheets.maximintegrated.com/en/ds/MAX30100.pdf
+
+Required properties:
+  - compatible: must be "maxim,max30100"
+  - reg: the I2C address of the sensor
+  - interrupt-parent: should be the phandle for the interrupt controller
+  - interrupts: the sole interrupt generated by the device
+
+  Refer to interrupt-controller/interrupts.txt for generic
+  interrupt client node bindings.
+
+Example:
+
+max30100@057 {
+	compatible = "maxim,max30100";
+	reg = <57>;
+	interrupt-parent = <&gpio1>;
+	interrupts = <16 2>;
+};
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 5dfb4f1..b846873 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -68,6 +68,7 @@ source "drivers/iio/common/Kconfig"
 source "drivers/iio/dac/Kconfig"
 source "drivers/iio/frequency/Kconfig"
 source "drivers/iio/gyro/Kconfig"
+source "drivers/iio/health/Kconfig"
 source "drivers/iio/humidity/Kconfig"
 source "drivers/iio/imu/Kconfig"
 source "drivers/iio/light/Kconfig"
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index dc73c1f..7371791 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -20,6 +20,7 @@ obj-y += common/
 obj-y += dac/
 obj-y += gyro/
 obj-y += frequency/
+obj-y += health/
 obj-y += humidity/
 obj-y += imu/
 obj-y += light/
diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig
new file mode 100644
index 0000000..a647679
--- /dev/null
+++ b/drivers/iio/health/Kconfig
@@ -0,0 +1,21 @@
+#
+# Health sensors
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Health sensors"
+
+config MAX30100
+	tristate "MAX30100 heart rate and pulse oximeter sensor"
+	depends on I2C
+	select REGMAP_I2C
+	select IIO_BUFFER
+	select IIO_KFIFO_BUF
+	help
+	  Say Y here to build I2C interface support for the Maxim
+	  MAX30100 heart rate, and pulse oximeter sensor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called max30100.
+
+endmenu
diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile
new file mode 100644
index 0000000..7c475d7
--- /dev/null
+++ b/drivers/iio/health/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for IIO Health sensors
+#
+
+# When adding new entries keep the list in alphabetical order
+
+obj-$(CONFIG_MAX30100)		+= max30100.o
diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c
new file mode 100644
index 0000000..1604315
--- /dev/null
+++ b/drivers/iio/health/max30100.c
@@ -0,0 +1,457 @@
+/*
+ * max30100.c - Support for MAX30100 heart rate and pulse oximeter sensor
+ *
+ * Copyright (C) 2015 Matt Ranostay <mranostay@gmail.com>
+ *
+ * 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.
+ *
+ * TODO: allow LED current and pulse length controls via device tree properties
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/kfifo_buf.h>
+
+#define MAX30100_REGMAP_NAME	"max30100_regmap"
+#define MAX30100_DRV_NAME	"max30100"
+
+#define MAX30100_REG_INT_STATUS			0x00
+#define MAX30100_REG_INT_STATUS_PWR_RDY		BIT(0)
+#define MAX30100_REG_INT_STATUS_SPO2_RDY	BIT(4)
+#define MAX30100_REG_INT_STATUS_HR_RDY		BIT(5)
+#define MAX30100_REG_INT_STATUS_FIFO_RDY	BIT(7)
+
+#define MAX30100_REG_INT_ENABLE			0x01
+#define MAX30100_REG_INT_ENABLE_SPO2_EN		BIT(0)
+#define MAX30100_REG_INT_ENABLE_HR_EN		BIT(1)
+#define MAX30100_REG_INT_ENABLE_FIFO_EN		BIT(3)
+#define MAX30100_REG_INT_ENABLE_MASK		0xf0
+#define MAX30100_REG_INT_ENABLE_MASK_SHIFT	4
+
+#define MAX30100_REG_FIFO_WR_PTR		0x02
+#define MAX30100_REG_FIFO_OVR_CTR		0x03
+#define MAX30100_REG_FIFO_RD_PTR		0x04
+#define MAX30100_REG_FIFO_DATA			0x05
+#define MAX30100_REG_FIFO_DATA_ENTRY_LEN	4
+
+#define MAX30100_REG_MODE_CONFIG		0x06
+#define MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN	BIT(0)
+#define MAX30100_REG_MODE_CONFIG_MODE_HR_EN	BIT(1)
+#define MAX30100_REG_MODE_CONFIG_MODE_MASK	0x03
+#define MAX30100_REG_MODE_CONFIG_TEMP_EN	BIT(3)
+#define MAX30100_REG_MODE_CONFIG_PWR		BIT(7)
+
+#define MAX30100_REG_SPO2_CONFIG		0x07
+#define MAX30100_REG_SPO2_CONFIG_HI_RES_EN	BIT(6)
+
+#define MAX30100_REG_SPO2_CONFIG_100HZ		BIT(2)
+#define MAX30100_REG_SPO2_CONFIG_1600US		0x3
+
+#define MAX30100_REG_LED_CONFIG			0x09
+#define MAX30100_REG_LED_CONFIG_RED_LED_SHIFT	4
+
+#define MAX30100_REG_LED_CONFIG_24MA		0x07
+#define MAX30100_REG_LED_CONFIG_50MA		0x0f
+
+#define MAX30100_REG_TEMP_INTEGER		0x16
+#define MAX30100_REG_TEMP_FRACTION		0x17
+
+struct max30100_data {
+	struct i2c_client *client;
+	struct iio_dev *indio_dev;
+	struct mutex lock;
+	struct regmap *regmap;
+
+	__be16 buffer[2]; /* 2 16-bit channels */
+};
+
+static bool max30100_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MAX30100_REG_INT_STATUS:
+	case MAX30100_REG_MODE_CONFIG:
+	case MAX30100_REG_FIFO_WR_PTR:
+	case MAX30100_REG_FIFO_OVR_CTR:
+	case MAX30100_REG_FIFO_RD_PTR:
+	case MAX30100_REG_FIFO_DATA:
+	case MAX30100_REG_TEMP_INTEGER:
+	case MAX30100_REG_TEMP_FRACTION:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config max30100_regmap_config = {
+	.name = MAX30100_REGMAP_NAME,
+
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = MAX30100_REG_TEMP_FRACTION,
+	.cache_type = REGCACHE_FLAT,
+
+	.volatile_reg = max30100_is_volatile_reg,
+};
+
+static const unsigned long max30100_scan_masks[] = {0x3, 0};
+
+static const struct iio_chan_spec max30100_channels[] = {
+	{
+		.type = IIO_INTENSITY,
+		.channel2 = IIO_MOD_LIGHT_IR,
+		.modified = 1,
+
+		.scan_index = 0,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.storagebits = 16,
+			.endianness = IIO_BE,
+		},
+	},
+	{
+		.type = IIO_INTENSITY,
+		.channel2 = IIO_MOD_LIGHT_RED,
+		.modified = 1,
+
+		.scan_index = 1,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.storagebits = 16,
+			.endianness = IIO_BE,
+		},
+	},
+	{
+		.type = IIO_TEMP,
+		.info_mask_separate =
+			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+		.scan_index = -1,
+	},
+};
+
+static int max30100_set_powermode(struct max30100_data *data, bool state)
+{
+	return regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
+				  MAX30100_REG_MODE_CONFIG_PWR,
+				  state ? 0 : MAX30100_REG_MODE_CONFIG_PWR);
+}
+
+static int max30100_clear_fifo(struct max30100_data *data)
+{
+	int ret;
+
+	ret = regmap_write(data->regmap, MAX30100_REG_FIFO_WR_PTR, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(data->regmap, MAX30100_REG_FIFO_OVR_CTR, 0);
+	if (ret)
+		return ret;
+
+	return regmap_write(data->regmap, MAX30100_REG_FIFO_RD_PTR, 0);
+}
+
+static int max30100_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct max30100_data *data = iio_priv(indio_dev);
+	int ret;
+
+	ret = max30100_set_powermode(data, true);
+	if (ret)
+		return ret;
+
+	return max30100_clear_fifo(data);
+}
+
+static int max30100_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct max30100_data *data = iio_priv(indio_dev);
+
+	return max30100_set_powermode(data, false);
+}
+
+static const struct iio_buffer_setup_ops max30100_buffer_setup_ops = {
+	.postenable = max30100_buffer_postenable,
+	.predisable = max30100_buffer_predisable,
+};
+
+static inline int max30100_fifo_is_empty(struct max30100_data *data)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(data->regmap, MAX30100_REG_INT_STATUS, &val);
+	if (ret)
+		return ret;
+
+	if (val & MAX30100_REG_INT_STATUS_FIFO_RDY)
+		return 15;
+
+	return !!(val >> 4);
+}
+
+static int max30100_read_measurement(struct max30100_data *data)
+{
+	int ret;
+
+	ret = i2c_smbus_read_i2c_block_data(data->client,
+					    MAX30100_REG_FIFO_DATA,
+					    MAX30100_REG_FIFO_DATA_ENTRY_LEN,
+					    (u8 *) &data->buffer);
+
+	return (ret == MAX30100_REG_FIFO_DATA_ENTRY_LEN) ? 0 : ret;
+}
+
+static irqreturn_t max30100_interrupt_handler(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct max30100_data *data = iio_priv(indio_dev);
+	int ret, cnt = 0;
+
+	mutex_lock(&data->lock);
+
+	while (cnt-- || (cnt = max30100_fifo_is_empty(data) > 0)) {
+		ret = max30100_read_measurement(data);
+		if (ret)
+			break;
+
+		iio_push_to_buffers(data->indio_dev, data->buffer);
+	}
+
+	mutex_unlock(&data->lock);
+
+	return IRQ_HANDLED;
+}
+
+static int max30100_chip_init(struct max30100_data *data)
+{
+	int ret;
+
+	/* RED IR LED = 24mA, IR LED = 50mA */
+	ret = regmap_write(data->regmap, MAX30100_REG_LED_CONFIG,
+				(MAX30100_REG_LED_CONFIG_24MA <<
+				 MAX30100_REG_LED_CONFIG_RED_LED_SHIFT) |
+				 MAX30100_REG_LED_CONFIG_50MA);
+	if (ret)
+		return ret;
+
+	/* enable hi-res SPO2 readings with 100 samples a second */
+	ret = regmap_write(data->regmap, MAX30100_REG_SPO2_CONFIG,
+				 MAX30100_REG_SPO2_CONFIG_HI_RES_EN |
+				 MAX30100_REG_SPO2_CONFIG_100HZ |
+				 MAX30100_REG_SPO2_CONFIG_1600US);
+	if (ret)
+		return ret;
+
+	/* enable SPO2 mode */
+	ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
+				 MAX30100_REG_MODE_CONFIG_MODE_MASK,
+				 MAX30100_REG_MODE_CONFIG_MODE_HR_EN |
+				 MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN);
+	if (ret)
+		return ret;
+
+	/* enable SPO2 + FIFO interrupts */
+	return regmap_update_bits(data->regmap, MAX30100_REG_INT_ENABLE,
+				 MAX30100_REG_INT_ENABLE_MASK,
+				(MAX30100_REG_INT_ENABLE_FIFO_EN |
+				 MAX30100_REG_INT_ENABLE_SPO2_EN)
+				 << MAX30100_REG_INT_ENABLE_MASK_SHIFT);
+}
+
+static int max30100_read_temp(struct max30100_data *data, int *val)
+{
+	int ret;
+	unsigned int reg;
+
+	ret = regmap_read(data->regmap, MAX30100_REG_TEMP_INTEGER, &reg);
+	if (ret < 0)
+		return ret;
+	*val = reg << 4;
+
+	ret = regmap_read(data->regmap, MAX30100_REG_TEMP_FRACTION, &reg);
+	if (ret < 0)
+		return ret;
+
+	*val |= reg & 0xf;
+	*val = sign_extend32(*val, 11);
+
+	return 0;
+}
+
+static int max30100_get_temp(struct max30100_data *data, int *val)
+{
+	int ret;
+
+	/* start acquisition */
+	ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
+				 MAX30100_REG_MODE_CONFIG_TEMP_EN,
+				 MAX30100_REG_MODE_CONFIG_TEMP_EN);
+	if (ret)
+		return ret;
+
+	usleep_range(35000, 50000);
+
+	return max30100_read_temp(data, val);
+}
+
+static int max30100_read_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int *val, int *val2, long mask)
+{
+	struct max30100_data *data = iio_priv(indio_dev);
+	int ret = -EINVAL;
+
+	mutex_lock(&indio_dev->mlock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		/*
+		 * Temperature reading can only be acquired while engine
+		 * is running
+		 */
+		if (!iio_buffer_enabled(indio_dev))
+			ret = -EAGAIN;
+		else {
+			ret = max30100_get_temp(data, val);
+			if (!ret)
+				ret = IIO_VAL_INT;
+		}
+		break;
+	case IIO_CHAN_INFO_SCALE:
+		*val = 1;  /* 0.0625 */
+		*val2 = 16;
+		ret = IIO_VAL_FRACTIONAL;
+		break;
+	}
+
+	mutex_unlock(&indio_dev->mlock);
+
+	return ret;
+}
+
+static const struct iio_info max30100_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = max30100_read_raw,
+};
+
+static int max30100_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct max30100_data *data;
+	struct iio_buffer *buffer;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	buffer = devm_iio_kfifo_allocate(&client->dev);
+	if (!buffer)
+		return -ENOMEM;
+
+	iio_device_attach_buffer(indio_dev, buffer);
+
+	indio_dev->name = MAX30100_DRV_NAME;
+	indio_dev->channels = max30100_channels;
+	indio_dev->info = &max30100_info;
+	indio_dev->num_channels = ARRAY_SIZE(max30100_channels);
+	indio_dev->available_scan_masks = max30100_scan_masks;
+	indio_dev->modes = (INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE);
+	indio_dev->setup_ops = &max30100_buffer_setup_ops;
+
+	data = iio_priv(indio_dev);
+	data->indio_dev = indio_dev;
+	data->client = client;
+
+	mutex_init(&data->lock);
+	i2c_set_clientdata(client, indio_dev);
+
+	data->regmap = devm_regmap_init_i2c(client, &max30100_regmap_config);
+	if (IS_ERR(data->regmap)) {
+		dev_err(&client->dev, "regmap initialization failed.\n");
+		return PTR_ERR(data->regmap);
+	}
+	max30100_set_powermode(data, false);
+
+	ret = max30100_chip_init(data);
+	if (ret)
+		goto error_out;
+
+	if (client->irq <= 0) {
+		dev_err(&client->dev, "no valid irq defined\n");
+		ret = -EINVAL;
+		goto error_out;
+	}
+	ret = devm_request_threaded_irq(&client->dev, client->irq,
+					NULL, max30100_interrupt_handler,
+					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+					"max30100_irq", indio_dev);
+	if (ret) {
+		dev_err(&client->dev, "request irq (%d) failed\n", client->irq);
+		goto error_out;
+	}
+
+	return iio_device_register(indio_dev);
+
+error_out:
+	return ret;
+}
+
+static int max30100_remove(struct i2c_client *client)
+{
+	struct iio_dev *indio_dev = i2c_get_clientdata(client);
+	struct max30100_data *data = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+	max30100_set_powermode(data, false);
+
+	return 0;
+}
+
+static const struct i2c_device_id max30100_id[] = {
+	{ "max30100", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, max30100_id);
+
+static const struct of_device_id max30100_dt_ids[] = {
+	{ .compatible = "maxim,max30100" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, max30100_dt_ids);
+
+static struct i2c_driver max30100_driver = {
+	.driver = {
+		.name	= MAX30100_DRV_NAME,
+		.of_match_table	= of_match_ptr(max30100_dt_ids),
+	},
+	.probe		= max30100_probe,
+	.remove		= max30100_remove,
+	.id_table	= max30100_id,
+};
+module_i2c_driver(max30100_driver);
+
+MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
+MODULE_DESCRIPTION("MAX30100 heart rate and pulse oximeter sensor");
+MODULE_LICENSE("GPL");
-- 
1.9.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH v5 1/1] iio: light: add MAX30100 oximeter driver support
  2015-12-04  5:15 ` [PATCH v5 1/1] " Matt Ranostay
@ 2015-12-05 17:27   ` Jonathan Cameron
  2015-12-06  0:17     ` Matt Ranostay
  0 siblings, 1 reply; 4+ messages in thread
From: Jonathan Cameron @ 2015-12-05 17:27 UTC (permalink / raw)
  To: Matt Ranostay, linux-iio

On 04/12/15 05:15, Matt Ranostay wrote:
> MAX30100 is an heart rate and pulse oximeter sensor that works using
> two LEDS of different wavelengths, and detecting the light reflected
> back.
> 
> This patchset adds support for both IR and RED LED channels which can
> be processed in userspace to determine heart rate and blood oxygen
> levels.
> 
> Signed-off-by: Matt Ranostay <mranostay@gmail.com>
A few little bits and bobs inline. Biggest one is how confused
I got with the fifo_is_empty function which definitely needs
some docs and renaming!

Nearly there :)

Jonathan
> ---
>  .../devicetree/bindings/iio/health/max30100.txt    |  21 +
>  drivers/iio/Kconfig                                |   1 +
>  drivers/iio/Makefile                               |   1 +
>  drivers/iio/health/Kconfig                         |  21 +
>  drivers/iio/health/Makefile                        |   7 +
>  drivers/iio/health/max30100.c                      | 457 +++++++++++++++++++++
>  6 files changed, 508 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/health/max30100.txt
>  create mode 100644 drivers/iio/health/Kconfig
>  create mode 100644 drivers/iio/health/Makefile
>  create mode 100644 drivers/iio/health/max30100.c
> 
> diff --git a/Documentation/devicetree/bindings/iio/health/max30100.txt b/Documentation/devicetree/bindings/iio/health/max30100.txt
> new file mode 100644
> index 0000000..f6fbac6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/health/max30100.txt
> @@ -0,0 +1,21 @@
> +Maxim MAX30100 heart rate and pulse oximeter sensor
> +
> +* https://datasheets.maximintegrated.com/en/ds/MAX30100.pdf
> +
> +Required properties:
> +  - compatible: must be "maxim,max30100"
> +  - reg: the I2C address of the sensor
> +  - interrupt-parent: should be the phandle for the interrupt controller
> +  - interrupts: the sole interrupt generated by the device
> +
> +  Refer to interrupt-controller/interrupts.txt for generic
> +  interrupt client node bindings.
> +
> +Example:
> +
> +max30100@057 {
> +	compatible = "maxim,max30100";
> +	reg = <57>;
> +	interrupt-parent = <&gpio1>;
> +	interrupts = <16 2>;
> +};
> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> index 5dfb4f1..b846873 100644
> --- a/drivers/iio/Kconfig
> +++ b/drivers/iio/Kconfig
> @@ -68,6 +68,7 @@ source "drivers/iio/common/Kconfig"
>  source "drivers/iio/dac/Kconfig"
>  source "drivers/iio/frequency/Kconfig"
>  source "drivers/iio/gyro/Kconfig"
> +source "drivers/iio/health/Kconfig"
>  source "drivers/iio/humidity/Kconfig"
>  source "drivers/iio/imu/Kconfig"
>  source "drivers/iio/light/Kconfig"
> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> index dc73c1f..7371791 100644
> --- a/drivers/iio/Makefile
> +++ b/drivers/iio/Makefile
> @@ -20,6 +20,7 @@ obj-y += common/
>  obj-y += dac/
>  obj-y += gyro/
>  obj-y += frequency/
> +obj-y += health/
>  obj-y += humidity/
>  obj-y += imu/
>  obj-y += light/
> diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig
> new file mode 100644
> index 0000000..a647679
> --- /dev/null
> +++ b/drivers/iio/health/Kconfig
> @@ -0,0 +1,21 @@
> +#
> +# Health sensors
> +#
> +# When adding new entries keep the list in alphabetical order
> +
> +menu "Health sensors"
> +
> +config MAX30100
> +	tristate "MAX30100 heart rate and pulse oximeter sensor"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select IIO_BUFFER
> +	select IIO_KFIFO_BUF
> +	help
> +	  Say Y here to build I2C interface support for the Maxim
> +	  MAX30100 heart rate, and pulse oximeter sensor.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called max30100.
> +
> +endmenu
> diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile
> new file mode 100644
> index 0000000..7c475d7
> --- /dev/null
> +++ b/drivers/iio/health/Makefile
> @@ -0,0 +1,7 @@
> +#
> +# Makefile for IIO Health sensors
> +#
> +
> +# When adding new entries keep the list in alphabetical order
> +
> +obj-$(CONFIG_MAX30100)		+= max30100.o
> diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c
> new file mode 100644
> index 0000000..1604315
> --- /dev/null
> +++ b/drivers/iio/health/max30100.c
> @@ -0,0 +1,457 @@
> +/*
> + * max30100.c - Support for MAX30100 heart rate and pulse oximeter sensor
> + *
> + * Copyright (C) 2015 Matt Ranostay <mranostay@gmail.com>
> + *
> + * 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.
> + *
> + * TODO: allow LED current and pulse length controls via device tree properties
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/irq.h>
> +#include <linux/i2c.h>
> +#include <linux/mutex.h>
> +#include <linux/regmap.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/kfifo_buf.h>
> +
> +#define MAX30100_REGMAP_NAME	"max30100_regmap"
> +#define MAX30100_DRV_NAME	"max30100"
> +
> +#define MAX30100_REG_INT_STATUS			0x00
> +#define MAX30100_REG_INT_STATUS_PWR_RDY		BIT(0)
> +#define MAX30100_REG_INT_STATUS_SPO2_RDY	BIT(4)
> +#define MAX30100_REG_INT_STATUS_HR_RDY		BIT(5)
> +#define MAX30100_REG_INT_STATUS_FIFO_RDY	BIT(7)
> +
> +#define MAX30100_REG_INT_ENABLE			0x01
> +#define MAX30100_REG_INT_ENABLE_SPO2_EN		BIT(0)
> +#define MAX30100_REG_INT_ENABLE_HR_EN		BIT(1)
> +#define MAX30100_REG_INT_ENABLE_FIFO_EN		BIT(3)
> +#define MAX30100_REG_INT_ENABLE_MASK		0xf0
> +#define MAX30100_REG_INT_ENABLE_MASK_SHIFT	4
> +
> +#define MAX30100_REG_FIFO_WR_PTR		0x02
> +#define MAX30100_REG_FIFO_OVR_CTR		0x03
> +#define MAX30100_REG_FIFO_RD_PTR		0x04
> +#define MAX30100_REG_FIFO_DATA			0x05
> +#define MAX30100_REG_FIFO_DATA_ENTRY_LEN	4
> +
> +#define MAX30100_REG_MODE_CONFIG		0x06
> +#define MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN	BIT(0)
> +#define MAX30100_REG_MODE_CONFIG_MODE_HR_EN	BIT(1)
> +#define MAX30100_REG_MODE_CONFIG_MODE_MASK	0x03
> +#define MAX30100_REG_MODE_CONFIG_TEMP_EN	BIT(3)
> +#define MAX30100_REG_MODE_CONFIG_PWR		BIT(7)
> +
> +#define MAX30100_REG_SPO2_CONFIG		0x07
> +#define MAX30100_REG_SPO2_CONFIG_HI_RES_EN	BIT(6)
> +
> +#define MAX30100_REG_SPO2_CONFIG_100HZ		BIT(2)
> +#define MAX30100_REG_SPO2_CONFIG_1600US		0x3
> +
> +#define MAX30100_REG_LED_CONFIG			0x09
> +#define MAX30100_REG_LED_CONFIG_RED_LED_SHIFT	4
> +
> +#define MAX30100_REG_LED_CONFIG_24MA		0x07
> +#define MAX30100_REG_LED_CONFIG_50MA		0x0f
> +
> +#define MAX30100_REG_TEMP_INTEGER		0x16
> +#define MAX30100_REG_TEMP_FRACTION		0x17
> +
> +struct max30100_data {
> +	struct i2c_client *client;
> +	struct iio_dev *indio_dev;
> +	struct mutex lock;
> +	struct regmap *regmap;
> +
> +	__be16 buffer[2]; /* 2 16-bit channels */
> +};
> +
> +static bool max30100_is_volatile_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case MAX30100_REG_INT_STATUS:
> +	case MAX30100_REG_MODE_CONFIG:
> +	case MAX30100_REG_FIFO_WR_PTR:
> +	case MAX30100_REG_FIFO_OVR_CTR:
> +	case MAX30100_REG_FIFO_RD_PTR:
> +	case MAX30100_REG_FIFO_DATA:
> +	case MAX30100_REG_TEMP_INTEGER:
> +	case MAX30100_REG_TEMP_FRACTION:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +static const struct regmap_config max30100_regmap_config = {
> +	.name = MAX30100_REGMAP_NAME,
> +
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +
> +	.max_register = MAX30100_REG_TEMP_FRACTION,
> +	.cache_type = REGCACHE_FLAT,
> +
> +	.volatile_reg = max30100_is_volatile_reg,
> +};
> +
> +static const unsigned long max30100_scan_masks[] = {0x3, 0};
> +
> +static const struct iio_chan_spec max30100_channels[] = {
> +	{
> +		.type = IIO_INTENSITY,
> +		.channel2 = IIO_MOD_LIGHT_IR,
> +		.modified = 1,
> +
> +		.scan_index = 0,
> +		.scan_type = {
> +			.sign = 'u',
> +			.realbits = 16,
> +			.storagebits = 16,
> +			.endianness = IIO_BE,
> +		},
> +	},
> +	{
> +		.type = IIO_INTENSITY,
> +		.channel2 = IIO_MOD_LIGHT_RED,
> +		.modified = 1,
> +
> +		.scan_index = 1,
> +		.scan_type = {
> +			.sign = 'u',
> +			.realbits = 16,
> +			.storagebits = 16,
> +			.endianness = IIO_BE,
> +		},
> +	},
> +	{
> +		.type = IIO_TEMP,
> +		.info_mask_separate =
> +			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
> +		.scan_index = -1,
> +	},
> +};
> +
> +static int max30100_set_powermode(struct max30100_data *data, bool state)
> +{
> +	return regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
> +				  MAX30100_REG_MODE_CONFIG_PWR,
> +				  state ? 0 : MAX30100_REG_MODE_CONFIG_PWR);
> +}
> +
> +static int max30100_clear_fifo(struct max30100_data *data)
> +{
> +	int ret;
> +
> +	ret = regmap_write(data->regmap, MAX30100_REG_FIFO_WR_PTR, 0);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(data->regmap, MAX30100_REG_FIFO_OVR_CTR, 0);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(data->regmap, MAX30100_REG_FIFO_RD_PTR, 0);
> +}
> +
> +static int max30100_buffer_postenable(struct iio_dev *indio_dev)
> +{
> +	struct max30100_data *data = iio_priv(indio_dev);
> +	int ret;
> +
> +	ret = max30100_set_powermode(data, true);
> +	if (ret)
> +		return ret;
> +
> +	return max30100_clear_fifo(data);
> +}
> +
> +static int max30100_buffer_predisable(struct iio_dev *indio_dev)
> +{
> +	struct max30100_data *data = iio_priv(indio_dev);
> +
> +	return max30100_set_powermode(data, false);
> +}
> +
> +static const struct iio_buffer_setup_ops max30100_buffer_setup_ops = {
> +	.postenable = max30100_buffer_postenable,
> +	.predisable = max30100_buffer_predisable,
> +};
> +
> +static inline int max30100_fifo_is_empty(struct max30100_data *data)
As commented below, this function seems badly named.
> +{
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(data->regmap, MAX30100_REG_INT_STATUS, &val);
> +	if (ret)
> +		return ret;
> +
> +	if (val & MAX30100_REG_INT_STATUS_FIFO_RDY)
> +		return 15;
> +
> +	return !!(val >> 4);
Hmm. This needs comments as I had to datasheet dive to find out what
you are doing.

So this will be 1 if HR_RDY or SPO2_RDY are true (technically also the
temp ready but I don't think that is relevant here.)

What if only one of them is ready?  If that happens you want to wait until
they both are I think...
> +}
> +
> +static int max30100_read_measurement(struct max30100_data *data)
> +{
> +	int ret;
> +
> +	ret = i2c_smbus_read_i2c_block_data(data->client,
> +					    MAX30100_REG_FIFO_DATA,
> +					    MAX30100_REG_FIFO_DATA_ENTRY_LEN,
> +					    (u8 *) &data->buffer);
> +
> +	return (ret == MAX30100_REG_FIFO_DATA_ENTRY_LEN) ? 0 : ret;
> +}
> +
> +static irqreturn_t max30100_interrupt_handler(int irq, void *private)
> +{
> +	struct iio_dev *indio_dev = private;
> +	struct max30100_data *data = iio_priv(indio_dev);
> +	int ret, cnt = 0;
> +
> +	mutex_lock(&data->lock);
> +
> +	while (cnt-- || (cnt = max30100_fifo_is_empty(data) > 0)) {
This gave me a headache to figure out why this made sense...
The confusing bit is that max30100_fifo_is_empty doesn't return
that at all, but rather a 'known to be in fifo' count.

> +		ret = max30100_read_measurement(data);
> +		if (ret)
> +			break;
> +
> +		iio_push_to_buffers(data->indio_dev, data->buffer);
> +	}
> +
> +	mutex_unlock(&data->lock);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int max30100_chip_init(struct max30100_data *data)
> +{
> +	int ret;
> +
> +	/* RED IR LED = 24mA, IR LED = 50mA */
> +	ret = regmap_write(data->regmap, MAX30100_REG_LED_CONFIG,
> +				(MAX30100_REG_LED_CONFIG_24MA <<
> +				 MAX30100_REG_LED_CONFIG_RED_LED_SHIFT) |
> +				 MAX30100_REG_LED_CONFIG_50MA);
> +	if (ret)
> +		return ret;
> +
> +	/* enable hi-res SPO2 readings with 100 samples a second */
> +	ret = regmap_write(data->regmap, MAX30100_REG_SPO2_CONFIG,
> +				 MAX30100_REG_SPO2_CONFIG_HI_RES_EN |
> +				 MAX30100_REG_SPO2_CONFIG_100HZ |
> +				 MAX30100_REG_SPO2_CONFIG_1600US);
> +	if (ret)
> +		return ret;
> +
> +	/* enable SPO2 mode */
> +	ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
> +				 MAX30100_REG_MODE_CONFIG_MODE_MASK,
> +				 MAX30100_REG_MODE_CONFIG_MODE_HR_EN |
> +				 MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN);
> +	if (ret)
> +		return ret;
> +
> +	/* enable SPO2 + FIFO interrupts */
> +	return regmap_update_bits(data->regmap, MAX30100_REG_INT_ENABLE,
> +				 MAX30100_REG_INT_ENABLE_MASK,
> +				(MAX30100_REG_INT_ENABLE_FIFO_EN |
> +				 MAX30100_REG_INT_ENABLE_SPO2_EN)
> +				 << MAX30100_REG_INT_ENABLE_MASK_SHIFT);
> +}
> +
> +static int max30100_read_temp(struct max30100_data *data, int *val)
> +{
> +	int ret;
> +	unsigned int reg;
> +
> +	ret = regmap_read(data->regmap, MAX30100_REG_TEMP_INTEGER, &reg);
> +	if (ret < 0)
> +		return ret;
> +	*val = reg << 4;
> +
> +	ret = regmap_read(data->regmap, MAX30100_REG_TEMP_FRACTION, &reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	*val |= reg & 0xf;
> +	*val = sign_extend32(*val, 11);
> +
> +	return 0;
> +}
> +
> +static int max30100_get_temp(struct max30100_data *data, int *val)
> +{
> +	int ret;
> +
> +	/* start acquisition */
> +	ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
> +				 MAX30100_REG_MODE_CONFIG_TEMP_EN,
> +				 MAX30100_REG_MODE_CONFIG_TEMP_EN);
> +	if (ret)
> +		return ret;
> +
> +	usleep_range(35000, 50000);
> +
> +	return max30100_read_temp(data, val);
> +}
> +
> +static int max30100_read_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int *val, int *val2, long mask)
> +{
> +	struct max30100_data *data = iio_priv(indio_dev);
> +	int ret = -EINVAL;
> +
> +	mutex_lock(&indio_dev->mlock);
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
Move taking mlock into this case statement.

> +		/*
> +		 * Temperature reading can only be acquired while engine
> +		 * is running
> +		 */
> +		if (!iio_buffer_enabled(indio_dev))
> +			ret = -EAGAIN;
> +		else {
> +			ret = max30100_get_temp(data, val);
> +			if (!ret)
> +				ret = IIO_VAL_INT;
> +		}
> +		break;
> +	case IIO_CHAN_INFO_SCALE:
This does not need to be done under the lock so shouldn't be.
> +		*val = 1;  /* 0.0625 */
> +		*val2 = 16;
> +		ret = IIO_VAL_FRACTIONAL;
> +		break;
> +	}
> +
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	return ret;
> +}
> +
> +static const struct iio_info max30100_info = {
> +	.driver_module = THIS_MODULE,
> +	.read_raw = max30100_read_raw,
> +};
> +
> +static int max30100_probe(struct i2c_client *client,
> +			  const struct i2c_device_id *id)
> +{
> +	struct max30100_data *data;
> +	struct iio_buffer *buffer;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +
> +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	buffer = devm_iio_kfifo_allocate(&client->dev);
> +	if (!buffer)
> +		return -ENOMEM;
> +
> +	iio_device_attach_buffer(indio_dev, buffer);
> +
> +	indio_dev->name = MAX30100_DRV_NAME;
> +	indio_dev->channels = max30100_channels;
> +	indio_dev->info = &max30100_info;
> +	indio_dev->num_channels = ARRAY_SIZE(max30100_channels);
> +	indio_dev->available_scan_masks = max30100_scan_masks;
> +	indio_dev->modes = (INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE);
> +	indio_dev->setup_ops = &max30100_buffer_setup_ops;
> +
> +	data = iio_priv(indio_dev);
> +	data->indio_dev = indio_dev;
> +	data->client = client;
> +
> +	mutex_init(&data->lock);
> +	i2c_set_clientdata(client, indio_dev);
> +
> +	data->regmap = devm_regmap_init_i2c(client, &max30100_regmap_config);
> +	if (IS_ERR(data->regmap)) {
> +		dev_err(&client->dev, "regmap initialization failed.\n");
> +		return PTR_ERR(data->regmap);
> +	}
> +	max30100_set_powermode(data, false);
> +
> +	ret = max30100_chip_init(data);
> +	if (ret)
return directly.
> +		goto error_out;
> +
> +	if (client->irq <= 0) {
> +		dev_err(&client->dev, "no valid irq defined\n");
> +		ret = -EINVAL;
return directly here preferred.
> +		goto error_out;
> +	}
> +	ret = devm_request_threaded_irq(&client->dev, client->irq,
> +					NULL, max30100_interrupt_handler,
> +					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> +					"max30100_irq", indio_dev);
> +	if (ret) {
> +		dev_err(&client->dev, "request irq (%d) failed\n", client->irq);
> +		goto error_out;
return directly here preferred.
> +	}
> +
> +	return iio_device_register(indio_dev);
> +
> +error_out:
> +	return ret;
> +}
> +
> +static int max30100_remove(struct i2c_client *client)
> +{
> +	struct iio_dev *indio_dev = i2c_get_clientdata(client);
> +	struct max30100_data *data = iio_priv(indio_dev);
> +
> +	iio_device_unregister(indio_dev);
> +	max30100_set_powermode(data, false);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id max30100_id[] = {
> +	{ "max30100", 0 },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, max30100_id);
> +
> +static const struct of_device_id max30100_dt_ids[] = {
> +	{ .compatible = "maxim,max30100" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, max30100_dt_ids);
> +
> +static struct i2c_driver max30100_driver = {
> +	.driver = {
> +		.name	= MAX30100_DRV_NAME,
> +		.of_match_table	= of_match_ptr(max30100_dt_ids),
> +	},
> +	.probe		= max30100_probe,
> +	.remove		= max30100_remove,
> +	.id_table	= max30100_id,
> +};
> +module_i2c_driver(max30100_driver);
> +
> +MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
> +MODULE_DESCRIPTION("MAX30100 heart rate and pulse oximeter sensor");
> +MODULE_LICENSE("GPL");
> 


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v5 1/1] iio: light: add MAX30100 oximeter driver support
  2015-12-05 17:27   ` Jonathan Cameron
@ 2015-12-06  0:17     ` Matt Ranostay
  0 siblings, 0 replies; 4+ messages in thread
From: Matt Ranostay @ 2015-12-06  0:17 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: linux-iio

On Sat, Dec 5, 2015 at 9:27 AM, Jonathan Cameron <jic23@kernel.org> wrote:
> On 04/12/15 05:15, Matt Ranostay wrote:
>> MAX30100 is an heart rate and pulse oximeter sensor that works using
>> two LEDS of different wavelengths, and detecting the light reflected
>> back.
>>
>> This patchset adds support for both IR and RED LED channels which can
>> be processed in userspace to determine heart rate and blood oxygen
>> levels.
>>
>> Signed-off-by: Matt Ranostay <mranostay@gmail.com>
> A few little bits and bobs inline. Biggest one is how confused
> I got with the fifo_is_empty function which definitely needs
> some docs and renaming!
>
> Nearly there :)
>
> Jonathan
>> ---
>>  .../devicetree/bindings/iio/health/max30100.txt    |  21 +
>>  drivers/iio/Kconfig                                |   1 +
>>  drivers/iio/Makefile                               |   1 +
>>  drivers/iio/health/Kconfig                         |  21 +
>>  drivers/iio/health/Makefile                        |   7 +
>>  drivers/iio/health/max30100.c                      | 457 +++++++++++++++++++++
>>  6 files changed, 508 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/iio/health/max30100.txt
>>  create mode 100644 drivers/iio/health/Kconfig
>>  create mode 100644 drivers/iio/health/Makefile
>>  create mode 100644 drivers/iio/health/max30100.c
>>
>> diff --git a/Documentation/devicetree/bindings/iio/health/max30100.txt b/Documentation/devicetree/bindings/iio/health/max30100.txt
>> new file mode 100644
>> index 0000000..f6fbac6
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/iio/health/max30100.txt
>> @@ -0,0 +1,21 @@
>> +Maxim MAX30100 heart rate and pulse oximeter sensor
>> +
>> +* https://datasheets.maximintegrated.com/en/ds/MAX30100.pdf
>> +
>> +Required properties:
>> +  - compatible: must be "maxim,max30100"
>> +  - reg: the I2C address of the sensor
>> +  - interrupt-parent: should be the phandle for the interrupt controller
>> +  - interrupts: the sole interrupt generated by the device
>> +
>> +  Refer to interrupt-controller/interrupts.txt for generic
>> +  interrupt client node bindings.
>> +
>> +Example:
>> +
>> +max30100@057 {
>> +     compatible = "maxim,max30100";
>> +     reg = <57>;
>> +     interrupt-parent = <&gpio1>;
>> +     interrupts = <16 2>;
>> +};
>> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
>> index 5dfb4f1..b846873 100644
>> --- a/drivers/iio/Kconfig
>> +++ b/drivers/iio/Kconfig
>> @@ -68,6 +68,7 @@ source "drivers/iio/common/Kconfig"
>>  source "drivers/iio/dac/Kconfig"
>>  source "drivers/iio/frequency/Kconfig"
>>  source "drivers/iio/gyro/Kconfig"
>> +source "drivers/iio/health/Kconfig"
>>  source "drivers/iio/humidity/Kconfig"
>>  source "drivers/iio/imu/Kconfig"
>>  source "drivers/iio/light/Kconfig"
>> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
>> index dc73c1f..7371791 100644
>> --- a/drivers/iio/Makefile
>> +++ b/drivers/iio/Makefile
>> @@ -20,6 +20,7 @@ obj-y += common/
>>  obj-y += dac/
>>  obj-y += gyro/
>>  obj-y += frequency/
>> +obj-y += health/
>>  obj-y += humidity/
>>  obj-y += imu/
>>  obj-y += light/
>> diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig
>> new file mode 100644
>> index 0000000..a647679
>> --- /dev/null
>> +++ b/drivers/iio/health/Kconfig
>> @@ -0,0 +1,21 @@
>> +#
>> +# Health sensors
>> +#
>> +# When adding new entries keep the list in alphabetical order
>> +
>> +menu "Health sensors"
>> +
>> +config MAX30100
>> +     tristate "MAX30100 heart rate and pulse oximeter sensor"
>> +     depends on I2C
>> +     select REGMAP_I2C
>> +     select IIO_BUFFER
>> +     select IIO_KFIFO_BUF
>> +     help
>> +       Say Y here to build I2C interface support for the Maxim
>> +       MAX30100 heart rate, and pulse oximeter sensor.
>> +
>> +       To compile this driver as a module, choose M here: the
>> +       module will be called max30100.
>> +
>> +endmenu
>> diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile
>> new file mode 100644
>> index 0000000..7c475d7
>> --- /dev/null
>> +++ b/drivers/iio/health/Makefile
>> @@ -0,0 +1,7 @@
>> +#
>> +# Makefile for IIO Health sensors
>> +#
>> +
>> +# When adding new entries keep the list in alphabetical order
>> +
>> +obj-$(CONFIG_MAX30100)               += max30100.o
>> diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c
>> new file mode 100644
>> index 0000000..1604315
>> --- /dev/null
>> +++ b/drivers/iio/health/max30100.c
>> @@ -0,0 +1,457 @@
>> +/*
>> + * max30100.c - Support for MAX30100 heart rate and pulse oximeter sensor
>> + *
>> + * Copyright (C) 2015 Matt Ranostay <mranostay@gmail.com>
>> + *
>> + * 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.
>> + *
>> + * TODO: allow LED current and pulse length controls via device tree properties
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/init.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/delay.h>
>> +#include <linux/err.h>
>> +#include <linux/irq.h>
>> +#include <linux/i2c.h>
>> +#include <linux/mutex.h>
>> +#include <linux/regmap.h>
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/buffer.h>
>> +#include <linux/iio/kfifo_buf.h>
>> +
>> +#define MAX30100_REGMAP_NAME "max30100_regmap"
>> +#define MAX30100_DRV_NAME    "max30100"
>> +
>> +#define MAX30100_REG_INT_STATUS                      0x00
>> +#define MAX30100_REG_INT_STATUS_PWR_RDY              BIT(0)
>> +#define MAX30100_REG_INT_STATUS_SPO2_RDY     BIT(4)
>> +#define MAX30100_REG_INT_STATUS_HR_RDY               BIT(5)
>> +#define MAX30100_REG_INT_STATUS_FIFO_RDY     BIT(7)
>> +
>> +#define MAX30100_REG_INT_ENABLE                      0x01
>> +#define MAX30100_REG_INT_ENABLE_SPO2_EN              BIT(0)
>> +#define MAX30100_REG_INT_ENABLE_HR_EN                BIT(1)
>> +#define MAX30100_REG_INT_ENABLE_FIFO_EN              BIT(3)
>> +#define MAX30100_REG_INT_ENABLE_MASK         0xf0
>> +#define MAX30100_REG_INT_ENABLE_MASK_SHIFT   4
>> +
>> +#define MAX30100_REG_FIFO_WR_PTR             0x02
>> +#define MAX30100_REG_FIFO_OVR_CTR            0x03
>> +#define MAX30100_REG_FIFO_RD_PTR             0x04
>> +#define MAX30100_REG_FIFO_DATA                       0x05
>> +#define MAX30100_REG_FIFO_DATA_ENTRY_LEN     4
>> +
>> +#define MAX30100_REG_MODE_CONFIG             0x06
>> +#define MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN        BIT(0)
>> +#define MAX30100_REG_MODE_CONFIG_MODE_HR_EN  BIT(1)
>> +#define MAX30100_REG_MODE_CONFIG_MODE_MASK   0x03
>> +#define MAX30100_REG_MODE_CONFIG_TEMP_EN     BIT(3)
>> +#define MAX30100_REG_MODE_CONFIG_PWR         BIT(7)
>> +
>> +#define MAX30100_REG_SPO2_CONFIG             0x07
>> +#define MAX30100_REG_SPO2_CONFIG_HI_RES_EN   BIT(6)
>> +
>> +#define MAX30100_REG_SPO2_CONFIG_100HZ               BIT(2)
>> +#define MAX30100_REG_SPO2_CONFIG_1600US              0x3
>> +
>> +#define MAX30100_REG_LED_CONFIG                      0x09
>> +#define MAX30100_REG_LED_CONFIG_RED_LED_SHIFT        4
>> +
>> +#define MAX30100_REG_LED_CONFIG_24MA         0x07
>> +#define MAX30100_REG_LED_CONFIG_50MA         0x0f
>> +
>> +#define MAX30100_REG_TEMP_INTEGER            0x16
>> +#define MAX30100_REG_TEMP_FRACTION           0x17
>> +
>> +struct max30100_data {
>> +     struct i2c_client *client;
>> +     struct iio_dev *indio_dev;
>> +     struct mutex lock;
>> +     struct regmap *regmap;
>> +
>> +     __be16 buffer[2]; /* 2 16-bit channels */
>> +};
>> +
>> +static bool max30100_is_volatile_reg(struct device *dev, unsigned int reg)
>> +{
>> +     switch (reg) {
>> +     case MAX30100_REG_INT_STATUS:
>> +     case MAX30100_REG_MODE_CONFIG:
>> +     case MAX30100_REG_FIFO_WR_PTR:
>> +     case MAX30100_REG_FIFO_OVR_CTR:
>> +     case MAX30100_REG_FIFO_RD_PTR:
>> +     case MAX30100_REG_FIFO_DATA:
>> +     case MAX30100_REG_TEMP_INTEGER:
>> +     case MAX30100_REG_TEMP_FRACTION:
>> +             return true;
>> +     default:
>> +             return false;
>> +     }
>> +}
>> +
>> +static const struct regmap_config max30100_regmap_config = {
>> +     .name = MAX30100_REGMAP_NAME,
>> +
>> +     .reg_bits = 8,
>> +     .val_bits = 8,
>> +
>> +     .max_register = MAX30100_REG_TEMP_FRACTION,
>> +     .cache_type = REGCACHE_FLAT,
>> +
>> +     .volatile_reg = max30100_is_volatile_reg,
>> +};
>> +
>> +static const unsigned long max30100_scan_masks[] = {0x3, 0};
>> +
>> +static const struct iio_chan_spec max30100_channels[] = {
>> +     {
>> +             .type = IIO_INTENSITY,
>> +             .channel2 = IIO_MOD_LIGHT_IR,
>> +             .modified = 1,
>> +
>> +             .scan_index = 0,
>> +             .scan_type = {
>> +                     .sign = 'u',
>> +                     .realbits = 16,
>> +                     .storagebits = 16,
>> +                     .endianness = IIO_BE,
>> +             },
>> +     },
>> +     {
>> +             .type = IIO_INTENSITY,
>> +             .channel2 = IIO_MOD_LIGHT_RED,
>> +             .modified = 1,
>> +
>> +             .scan_index = 1,
>> +             .scan_type = {
>> +                     .sign = 'u',
>> +                     .realbits = 16,
>> +                     .storagebits = 16,
>> +                     .endianness = IIO_BE,
>> +             },
>> +     },
>> +     {
>> +             .type = IIO_TEMP,
>> +             .info_mask_separate =
>> +                     BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
>> +             .scan_index = -1,
>> +     },
>> +};
>> +
>> +static int max30100_set_powermode(struct max30100_data *data, bool state)
>> +{
>> +     return regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
>> +                               MAX30100_REG_MODE_CONFIG_PWR,
>> +                               state ? 0 : MAX30100_REG_MODE_CONFIG_PWR);
>> +}
>> +
>> +static int max30100_clear_fifo(struct max30100_data *data)
>> +{
>> +     int ret;
>> +
>> +     ret = regmap_write(data->regmap, MAX30100_REG_FIFO_WR_PTR, 0);
>> +     if (ret)
>> +             return ret;
>> +
>> +     ret = regmap_write(data->regmap, MAX30100_REG_FIFO_OVR_CTR, 0);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return regmap_write(data->regmap, MAX30100_REG_FIFO_RD_PTR, 0);
>> +}
>> +
>> +static int max30100_buffer_postenable(struct iio_dev *indio_dev)
>> +{
>> +     struct max30100_data *data = iio_priv(indio_dev);
>> +     int ret;
>> +
>> +     ret = max30100_set_powermode(data, true);
>> +     if (ret)
>> +             return ret;
>> +
>> +     return max30100_clear_fifo(data);
>> +}
>> +
>> +static int max30100_buffer_predisable(struct iio_dev *indio_dev)
>> +{
>> +     struct max30100_data *data = iio_priv(indio_dev);
>> +
>> +     return max30100_set_powermode(data, false);
>> +}
>> +
>> +static const struct iio_buffer_setup_ops max30100_buffer_setup_ops = {
>> +     .postenable = max30100_buffer_postenable,
>> +     .predisable = max30100_buffer_predisable,
>> +};
>> +
>> +static inline int max30100_fifo_is_empty(struct max30100_data *data)
> As commented below, this function seems badly named.
>> +{
>> +     unsigned int val;
>> +     int ret;
>> +
>> +     ret = regmap_read(data->regmap, MAX30100_REG_INT_STATUS, &val);
>> +     if (ret)
>> +             return ret;
>> +
>> +     if (val & MAX30100_REG_INT_STATUS_FIFO_RDY)
>> +             return 15;
>> +
>> +     return !!(val >> 4);
> Hmm. This needs comments as I had to datasheet dive to find out what
> you are doing.
>
> So this will be 1 if HR_RDY or SPO2_RDY are true (technically also the
> temp ready but I don't think that is relevant here.)
>
> What if only one of them is ready?  If that happens you want to wait until
> they both are I think...

Actually temperature interrupt flag will never trigger since we just
polling it.
Also the SPO2 interrupt flag is the only one we care about (actually
first version was "double sampling" aka output blank data with the HR
ready interrupt enabled) since we are in SPO2 mode, which has a IR +
RED reading.

However I will make this function more clear and rename it.


>> +}
>> +
>> +static int max30100_read_measurement(struct max30100_data *data)
>> +{
>> +     int ret;
>> +
>> +     ret = i2c_smbus_read_i2c_block_data(data->client,
>> +                                         MAX30100_REG_FIFO_DATA,
>> +                                         MAX30100_REG_FIFO_DATA_ENTRY_LEN,
>> +                                         (u8 *) &data->buffer);
>> +
>> +     return (ret == MAX30100_REG_FIFO_DATA_ENTRY_LEN) ? 0 : ret;
>> +}
>> +
>> +static irqreturn_t max30100_interrupt_handler(int irq, void *private)
>> +{
>> +     struct iio_dev *indio_dev = private;
>> +     struct max30100_data *data = iio_priv(indio_dev);
>> +     int ret, cnt = 0;
>> +
>> +     mutex_lock(&data->lock);
>> +
>> +     while (cnt-- || (cnt = max30100_fifo_is_empty(data) > 0)) {
> This gave me a headache to figure out why this made sense...
> The confusing bit is that max30100_fifo_is_empty doesn't return
> that at all, but rather a 'known to be in fifo' count.
>
>> +             ret = max30100_read_measurement(data);
>> +             if (ret)
>> +                     break;
>> +
>> +             iio_push_to_buffers(data->indio_dev, data->buffer);
>> +     }
>> +
>> +     mutex_unlock(&data->lock);
>> +
>> +     return IRQ_HANDLED;
>> +}
>> +
>> +static int max30100_chip_init(struct max30100_data *data)
>> +{
>> +     int ret;
>> +
>> +     /* RED IR LED = 24mA, IR LED = 50mA */
>> +     ret = regmap_write(data->regmap, MAX30100_REG_LED_CONFIG,
>> +                             (MAX30100_REG_LED_CONFIG_24MA <<
>> +                              MAX30100_REG_LED_CONFIG_RED_LED_SHIFT) |
>> +                              MAX30100_REG_LED_CONFIG_50MA);
>> +     if (ret)
>> +             return ret;
>> +
>> +     /* enable hi-res SPO2 readings with 100 samples a second */
>> +     ret = regmap_write(data->regmap, MAX30100_REG_SPO2_CONFIG,
>> +                              MAX30100_REG_SPO2_CONFIG_HI_RES_EN |
>> +                              MAX30100_REG_SPO2_CONFIG_100HZ |
>> +                              MAX30100_REG_SPO2_CONFIG_1600US);
>> +     if (ret)
>> +             return ret;
>> +
>> +     /* enable SPO2 mode */
>> +     ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
>> +                              MAX30100_REG_MODE_CONFIG_MODE_MASK,
>> +                              MAX30100_REG_MODE_CONFIG_MODE_HR_EN |
>> +                              MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN);
>> +     if (ret)
>> +             return ret;
>> +
>> +     /* enable SPO2 + FIFO interrupts */
>> +     return regmap_update_bits(data->regmap, MAX30100_REG_INT_ENABLE,
>> +                              MAX30100_REG_INT_ENABLE_MASK,
>> +                             (MAX30100_REG_INT_ENABLE_FIFO_EN |
>> +                              MAX30100_REG_INT_ENABLE_SPO2_EN)
>> +                              << MAX30100_REG_INT_ENABLE_MASK_SHIFT);
>> +}
>> +
>> +static int max30100_read_temp(struct max30100_data *data, int *val)
>> +{
>> +     int ret;
>> +     unsigned int reg;
>> +
>> +     ret = regmap_read(data->regmap, MAX30100_REG_TEMP_INTEGER, &reg);
>> +     if (ret < 0)
>> +             return ret;
>> +     *val = reg << 4;
>> +
>> +     ret = regmap_read(data->regmap, MAX30100_REG_TEMP_FRACTION, &reg);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     *val |= reg & 0xf;
>> +     *val = sign_extend32(*val, 11);
>> +
>> +     return 0;
>> +}
>> +
>> +static int max30100_get_temp(struct max30100_data *data, int *val)
>> +{
>> +     int ret;
>> +
>> +     /* start acquisition */
>> +     ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
>> +                              MAX30100_REG_MODE_CONFIG_TEMP_EN,
>> +                              MAX30100_REG_MODE_CONFIG_TEMP_EN);
>> +     if (ret)
>> +             return ret;
>> +
>> +     usleep_range(35000, 50000);
>> +
>> +     return max30100_read_temp(data, val);
>> +}
>> +
>> +static int max30100_read_raw(struct iio_dev *indio_dev,
>> +                          struct iio_chan_spec const *chan,
>> +                          int *val, int *val2, long mask)
>> +{
>> +     struct max30100_data *data = iio_priv(indio_dev);
>> +     int ret = -EINVAL;
>> +
>> +     mutex_lock(&indio_dev->mlock);
>> +
>> +     switch (mask) {
>> +     case IIO_CHAN_INFO_RAW:
> Move taking mlock into this case statement.
>
>> +             /*
>> +              * Temperature reading can only be acquired while engine
>> +              * is running
>> +              */
>> +             if (!iio_buffer_enabled(indio_dev))
>> +                     ret = -EAGAIN;
>> +             else {
>> +                     ret = max30100_get_temp(data, val);
>> +                     if (!ret)
>> +                             ret = IIO_VAL_INT;
>> +             }
>> +             break;
>> +     case IIO_CHAN_INFO_SCALE:
> This does not need to be done under the lock so shouldn't be.
>> +             *val = 1;  /* 0.0625 */
>> +             *val2 = 16;
>> +             ret = IIO_VAL_FRACTIONAL;
>> +             break;
>> +     }
>> +
>> +     mutex_unlock(&indio_dev->mlock);
>> +
>> +     return ret;
>> +}
>> +
>> +static const struct iio_info max30100_info = {
>> +     .driver_module = THIS_MODULE,
>> +     .read_raw = max30100_read_raw,
>> +};
>> +
>> +static int max30100_probe(struct i2c_client *client,
>> +                       const struct i2c_device_id *id)
>> +{
>> +     struct max30100_data *data;
>> +     struct iio_buffer *buffer;
>> +     struct iio_dev *indio_dev;
>> +     int ret;
>> +
>> +     indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
>> +     if (!indio_dev)
>> +             return -ENOMEM;
>> +
>> +     buffer = devm_iio_kfifo_allocate(&client->dev);
>> +     if (!buffer)
>> +             return -ENOMEM;
>> +
>> +     iio_device_attach_buffer(indio_dev, buffer);
>> +
>> +     indio_dev->name = MAX30100_DRV_NAME;
>> +     indio_dev->channels = max30100_channels;
>> +     indio_dev->info = &max30100_info;
>> +     indio_dev->num_channels = ARRAY_SIZE(max30100_channels);
>> +     indio_dev->available_scan_masks = max30100_scan_masks;
>> +     indio_dev->modes = (INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE);
>> +     indio_dev->setup_ops = &max30100_buffer_setup_ops;
>> +
>> +     data = iio_priv(indio_dev);
>> +     data->indio_dev = indio_dev;
>> +     data->client = client;
>> +
>> +     mutex_init(&data->lock);
>> +     i2c_set_clientdata(client, indio_dev);
>> +
>> +     data->regmap = devm_regmap_init_i2c(client, &max30100_regmap_config);
>> +     if (IS_ERR(data->regmap)) {
>> +             dev_err(&client->dev, "regmap initialization failed.\n");
>> +             return PTR_ERR(data->regmap);
>> +     }
>> +     max30100_set_powermode(data, false);
>> +
>> +     ret = max30100_chip_init(data);
>> +     if (ret)
> return directly.
>> +             goto error_out;
>> +
>> +     if (client->irq <= 0) {
>> +             dev_err(&client->dev, "no valid irq defined\n");
>> +             ret = -EINVAL;
> return directly here preferred.
>> +             goto error_out;
>> +     }
>> +     ret = devm_request_threaded_irq(&client->dev, client->irq,
>> +                                     NULL, max30100_interrupt_handler,
>> +                                     IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
>> +                                     "max30100_irq", indio_dev);
>> +     if (ret) {
>> +             dev_err(&client->dev, "request irq (%d) failed\n", client->irq);
>> +             goto error_out;
> return directly here preferred.
>> +     }
>> +
>> +     return iio_device_register(indio_dev);
>> +
>> +error_out:
>> +     return ret;
>> +}
>> +
>> +static int max30100_remove(struct i2c_client *client)
>> +{
>> +     struct iio_dev *indio_dev = i2c_get_clientdata(client);
>> +     struct max30100_data *data = iio_priv(indio_dev);
>> +
>> +     iio_device_unregister(indio_dev);
>> +     max30100_set_powermode(data, false);
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct i2c_device_id max30100_id[] = {
>> +     { "max30100", 0 },
>> +     {}
>> +};
>> +MODULE_DEVICE_TABLE(i2c, max30100_id);
>> +
>> +static const struct of_device_id max30100_dt_ids[] = {
>> +     { .compatible = "maxim,max30100" },
>> +     { }
>> +};
>> +MODULE_DEVICE_TABLE(of, max30100_dt_ids);
>> +
>> +static struct i2c_driver max30100_driver = {
>> +     .driver = {
>> +             .name   = MAX30100_DRV_NAME,
>> +             .of_match_table = of_match_ptr(max30100_dt_ids),
>> +     },
>> +     .probe          = max30100_probe,
>> +     .remove         = max30100_remove,
>> +     .id_table       = max30100_id,
>> +};
>> +module_i2c_driver(max30100_driver);
>> +
>> +MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
>> +MODULE_DESCRIPTION("MAX30100 heart rate and pulse oximeter sensor");
>> +MODULE_LICENSE("GPL");
>>
>

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2015-12-06  0:17 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-12-04  5:15 [PATCH v5 0/1] iio: light: add MAX30100 oximeter driver support Matt Ranostay
2015-12-04  5:15 ` [PATCH v5 1/1] " Matt Ranostay
2015-12-05 17:27   ` Jonathan Cameron
2015-12-06  0:17     ` Matt Ranostay

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.