linux/drivers/iio/health/max30100.c

511 lines
12 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0+
/*
* max30100.c - Support for MAX30100 heart rate and pulse oximeter sensor
*
* Copyright (C) 2015, 2018
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
*
* TODO: enable 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/property.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_COUNT 16
#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_100HZ BIT(2)
#define MAX30100_REG_SPO2_CONFIG_HI_RES_EN BIT(6)
#define MAX30100_REG_SPO2_CONFIG_1600US 0x3
#define MAX30100_REG_LED_CONFIG 0x09
#define MAX30100_REG_LED_CONFIG_LED_MASK 0x0f
#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 int max30100_led_current_mapping[] = {
4400, 7600, 11000, 14200, 17400,
20800, 24000, 27100, 30600, 33800,
37000, 40200, 43600, 46800, 50000
};
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_count(struct max30100_data *data)
{
unsigned int val;
int ret;
ret = regmap_read(data->regmap, MAX30100_REG_INT_STATUS, &val);
if (ret)
return ret;
/* FIFO is almost full */
if (val & MAX30100_REG_INT_STATUS_FIFO_RDY)
return MAX30100_REG_FIFO_DATA_ENTRY_COUNT - 1;
return 0;
}
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_count(data)) > 0) {
ret = max30100_read_measurement(data);
if (ret)
break;
iio_push_to_buffers(data->indio_dev, data->buffer);
cnt--;
}
mutex_unlock(&data->lock);
return IRQ_HANDLED;
}
static int max30100_get_current_idx(unsigned int val, int *reg)
{
int idx;
/* LED turned off */
if (val == 0) {
*reg = 0;
return 0;
}
for (idx = 0; idx < ARRAY_SIZE(max30100_led_current_mapping); idx++) {
if (max30100_led_current_mapping[idx] == val) {
*reg = idx + 1;
return 0;
}
}
return -EINVAL;
}
static int max30100_led_init(struct max30100_data *data)
{
struct device *dev = &data->client->dev;
unsigned int val[2];
int reg, ret;
ret = device_property_read_u32_array(dev, "maxim,led-current-microamp",
(unsigned int *) &val, 2);
if (ret) {
/* Default to 24 mA RED LED, 50 mA IR LED */
reg = (MAX30100_REG_LED_CONFIG_24MA <<
MAX30100_REG_LED_CONFIG_RED_LED_SHIFT) |
MAX30100_REG_LED_CONFIG_50MA;
dev_warn(dev, "no led-current-microamp set");
return regmap_write(data->regmap, MAX30100_REG_LED_CONFIG, reg);
}
/* RED LED current */
ret = max30100_get_current_idx(val[0], &reg);
if (ret) {
dev_err(dev, "invalid RED current setting %d", val[0]);
return ret;
}
ret = regmap_update_bits(data->regmap, MAX30100_REG_LED_CONFIG,
MAX30100_REG_LED_CONFIG_LED_MASK <<
MAX30100_REG_LED_CONFIG_RED_LED_SHIFT,
reg << MAX30100_REG_LED_CONFIG_RED_LED_SHIFT);
if (ret)
return ret;
/* IR LED current */
ret = max30100_get_current_idx(val[1], &reg);
if (ret) {
dev_err(dev, "invalid IR current setting %d", val[1]);
return ret;
}
return regmap_update_bits(data->regmap, MAX30100_REG_LED_CONFIG,
MAX30100_REG_LED_CONFIG_LED_MASK, reg);
}
static int max30100_chip_init(struct max30100_data *data)
{
int ret;
/* setup LED current settings */
ret = max30100_led_init(data);
if (ret)
return ret;
/* enable hi-res SPO2 readings at 100Hz */
ret = regmap_write(data->regmap, MAX30100_REG_SPO2_CONFIG,
MAX30100_REG_SPO2_CONFIG_HI_RES_EN |
MAX30100_REG_SPO2_CONFIG_100HZ);
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 FIFO interrupt */
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_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;
msleep(35);
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;
switch (mask) {
case IIO_CHAN_INFO_RAW:
/*
* Temperature reading can only be acquired while engine
* is running
*/
if (iio_device_claim_buffer_mode(indio_dev)) {
/*
* Replacing -EBUSY or other error code
* returned by iio_device_claim_buffer_mode()
* because user space may rely on the current
* one.
*/
ret = -EAGAIN;
} else {
ret = max30100_get_temp(data, val);
if (!ret)
ret = IIO_VAL_INT;
iio_device_release_buffer_mode(indio_dev);
}
break;
case IIO_CHAN_INFO_SCALE:
*val = 1; /* 0.0625 */
*val2 = 16;
ret = IIO_VAL_FRACTIONAL;
break;
}
return ret;
}
static const struct iio_info max30100_info = {
.read_raw = max30100_read_raw,
};
static int max30100_probe(struct i2c_client *client)
{
struct max30100_data *data;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
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_DIRECT_MODE;
ret = devm_iio_kfifo_buffer_setup(&client->dev, indio_dev,
&max30100_buffer_setup_ops);
if (ret)
return ret;
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 ret;
if (client->irq <= 0) {
dev_err(&client->dev, "no valid irq defined\n");
return -EINVAL;
}
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);
return ret;
}
return iio_device_register(indio_dev);
}
i2c: Make remove callback return void The value returned by an i2c driver's remove function is mostly ignored. (Only an error message is printed if the value is non-zero that the error is ignored.) So change the prototype of the remove function to return no value. This way driver authors are not tempted to assume that passing an error to the upper layer is a good idea. All drivers are adapted accordingly. There is no intended change of behaviour, all callbacks were prepared to return 0 before. Reviewed-by: Peter Senna Tschudin <peter.senna@gmail.com> Reviewed-by: Jeremy Kerr <jk@codeconstruct.com.au> Reviewed-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com> Reviewed-by: Javier Martinez Canillas <javierm@redhat.com> Reviewed-by: Crt Mori <cmo@melexis.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Acked-by: Marek Behún <kabel@kernel.org> # for leds-turris-omnia Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Reviewed-by: Petr Machata <petrm@nvidia.com> # for mlxsw Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com> # for surface3_power Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> # for bmc150-accel-i2c + kxcjk-1013 Reviewed-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> # for media/* + staging/media/* Acked-by: Miguel Ojeda <ojeda@kernel.org> # for auxdisplay/ht16k33 + auxdisplay/lcd2s Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> # for versaclock5 Reviewed-by: Ajay Gupta <ajayg@nvidia.com> # for ucsi_ccg Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> # for iio Acked-by: Peter Rosin <peda@axentia.se> # for i2c-mux-*, max9860 Acked-by: Adrien Grassein <adrien.grassein@gmail.com> # for lontium-lt8912b Reviewed-by: Jean Delvare <jdelvare@suse.de> # for hwmon, i2c-core and i2c/muxes Acked-by: Corey Minyard <cminyard@mvista.com> # for IPMI Reviewed-by: Vladimir Oltean <olteanv@gmail.com> Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com> Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com> # for drivers/power Acked-by: Krzysztof Hałasa <khalasa@piap.pl> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Signed-off-by: Wolfram Sang <wsa@kernel.org>
2022-08-15 11:02:30 +03:00
static void 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);
}
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 = max30100_dt_ids,
},
.probe = max30100_probe,
.remove = max30100_remove,
.id_table = max30100_id,
};
module_i2c_driver(max30100_driver);
MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
MODULE_DESCRIPTION("MAX30100 heart rate and pulse oximeter sensor");
MODULE_LICENSE("GPL");