diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-03-25 03:53:42 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-03-25 03:53:42 -0300 |
commit | 03dd4cb26d967f9588437b0fc9cc0e8353322bb7 (patch) | |
tree | fa581f6dc1c0596391690d1f67eceef3af8246dc /drivers/iio | |
parent | d4e493caf788ef44982e131ff9c786546904d934 (diff) |
Linux-libre 4.5-gnu
Diffstat (limited to 'drivers/iio')
72 files changed, 8367 insertions, 262 deletions
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 66792e707..505e921f0 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -22,6 +22,14 @@ if IIO_BUFFER source "drivers/iio/buffer/Kconfig" endif # IIO_BUFFER +config IIO_CONFIGFS + tristate "Enable IIO configuration via configfs" + select CONFIGFS_FS + help + This allows configuring various IIO bits through configfs + (e.g. software triggers). For more info see + Documentation/iio/iio_configfs.txt. + config IIO_TRIGGER bool "Enable triggered sampling support" help @@ -38,6 +46,14 @@ config IIO_CONSUMERS_PER_TRIGGER This value controls the maximum number of consumers that a given trigger may handle. Default is 2. +config IIO_SW_TRIGGER + tristate "Enable software triggers support" + select IIO_CONFIGFS + help + Provides IIO core support for software triggers. A software + trigger can be created via configfs or directly by a driver + using the API provided. + config IIO_TRIGGERED_EVENT tristate select IIO_TRIGGER @@ -50,8 +66,10 @@ source "drivers/iio/amplifiers/Kconfig" source "drivers/iio/chemical/Kconfig" source "drivers/iio/common/Kconfig" source "drivers/iio/dac/Kconfig" +source "drivers/iio/dummy/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 aeca7269f..20f649073 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -7,6 +7,8 @@ industrialio-y := industrialio-core.o industrialio-event.o inkern.o industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o +obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o +obj-$(CONFIG_IIO_SW_TRIGGER) += industrialio-sw-trigger.o obj-$(CONFIG_IIO_TRIGGERED_EVENT) += industrialio-triggered-event.o obj-y += accel/ @@ -16,8 +18,10 @@ obj-y += buffer/ obj-y += chemical/ obj-y += common/ obj-y += dac/ +obj-y += dummy/ obj-y += gyro/ obj-y += frequency/ +obj-y += health/ obj-y += humidity/ obj-y += imu/ obj-y += light/ diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 16cc5c691..833ea9dd4 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -64,7 +64,7 @@ config IIO_ST_ACCEL_3AXIS help Say yes here to build support for STMicroelectronics accelerometers: LSM303DLH, LSM303DLHC, LIS3DH, LSM330D, LSM330DL, LSM330DLC, - LIS331DLH, LSM303DL, LSM303DLM, LSM330. + LIS331DLH, LSM303DL, LSM303DLM, LSM330, LIS2DH12. This driver can also be built as a module. If so, these modules will be created: @@ -107,6 +107,35 @@ config KXCJK1013 To compile this driver as a module, choose M here: the module will be called kxcjk-1013. +config MMA7455 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config MMA7455_I2C + tristate "Freescale MMA7455L/MMA7456L Accelerometer I2C Driver" + depends on I2C + select MMA7455 + select REGMAP_I2C + help + Say yes here to build support for the Freescale MMA7455L and + MMA7456L 3-axis accelerometer. + + To compile this driver as a module, choose M here: the module + will be called mma7455_i2c. + +config MMA7455_SPI + tristate "Freescale MMA7455L/MMA7456L Accelerometer SPI Driver" + depends on SPI_MASTER + select MMA7455 + select REGMAP_SPI + help + Say yes here to build support for the Freescale MMA7455L and + MMA7456L 3-axis accelerometer. + + To compile this driver as a module, choose M here: the module + will be called mma7455_spi. + config MMA8452 tristate "Freescale MMA8452Q and similar Accelerometers Driver" depends on I2C @@ -158,6 +187,17 @@ config MXC4005 To compile this driver as a module, choose M. The module will be called mxc4005. +config MXC6255 + tristate "Memsic MXC6255 Orientation Sensing Accelerometer Driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for the Memsic MXC6255 Orientation + Sensing Accelerometer Driver. + + To compile this driver as a module, choose M here: the module will be + called mxc6255. + config STK8312 tristate "Sensortek STK8312 3-Axis Accelerometer Driver" depends on I2C diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index 7925f166e..71b6794de 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -10,6 +10,11 @@ obj-$(CONFIG_BMC150_ACCEL_SPI) += bmc150-accel-spi.o obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o obj-$(CONFIG_KXSD9) += kxsd9.o + +obj-$(CONFIG_MMA7455) += mma7455_core.o +obj-$(CONFIG_MMA7455_I2C) += mma7455_i2c.o +obj-$(CONFIG_MMA7455_SPI) += mma7455_spi.o + obj-$(CONFIG_MMA8452) += mma8452.o obj-$(CONFIG_MMA9551_CORE) += mma9551_core.o @@ -17,6 +22,7 @@ obj-$(CONFIG_MMA9551) += mma9551.o obj-$(CONFIG_MMA9553) += mma9553.o obj-$(CONFIG_MXC4005) += mxc4005.o +obj-$(CONFIG_MXC6255) += mxc6255.o obj-$(CONFIG_STK8312) += stk8312.o obj-$(CONFIG_STK8BA50) += stk8ba50.o diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c index 2d33f1e82..c73331f77 100644 --- a/drivers/iio/accel/bmc150-accel-core.c +++ b/drivers/iio/accel/bmc150-accel-core.c @@ -1623,24 +1623,22 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, } } - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(dev, "Unable to register iio device\n"); - goto err_trigger_unregister; - } - ret = pm_runtime_set_active(dev); if (ret) - goto err_iio_unregister; + goto err_trigger_unregister; pm_runtime_enable(dev); pm_runtime_set_autosuspend_delay(dev, BMC150_AUTO_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "Unable to register iio device\n"); + goto err_trigger_unregister; + } + return 0; -err_iio_unregister: - iio_device_unregister(indio_dev); err_trigger_unregister: bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1); err_buffer_cleanup: @@ -1655,12 +1653,12 @@ int bmc150_accel_core_remove(struct device *dev) struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmc150_accel_data *data = iio_priv(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(data->dev); pm_runtime_set_suspended(data->dev); pm_runtime_put_noidle(data->dev); - iio_device_unregister(indio_dev); - bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1); iio_triggered_buffer_cleanup(indio_dev); diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c index 18c1b0668..edec1d099 100644 --- a/drivers/iio/accel/kxcjk-1013.c +++ b/drivers/iio/accel/kxcjk-1013.c @@ -1264,25 +1264,23 @@ static int kxcjk1013_probe(struct i2c_client *client, goto err_trigger_unregister; } - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(&client->dev, "unable to register iio device\n"); - goto err_buffer_cleanup; - } - ret = pm_runtime_set_active(&client->dev); if (ret) - goto err_iio_unregister; + goto err_buffer_cleanup; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, KXCJK1013_SLEEP_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + return 0; -err_iio_unregister: - iio_device_unregister(indio_dev); err_buffer_cleanup: if (data->dready_trig) iio_triggered_buffer_cleanup(indio_dev); @@ -1302,12 +1300,12 @@ static int kxcjk1013_remove(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct kxcjk1013_data *data = iio_priv(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(indio_dev); - if (data->dready_trig) { iio_triggered_buffer_cleanup(indio_dev); iio_trigger_unregister(data->dready_trig); diff --git a/drivers/iio/accel/mma7455.h b/drivers/iio/accel/mma7455.h new file mode 100644 index 000000000..2b1152c53 --- /dev/null +++ b/drivers/iio/accel/mma7455.h @@ -0,0 +1,19 @@ +/* + * IIO accel driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood <manabian@gmail.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 as + * published by the Free Software Foundation. + */ + +#ifndef __MMA7455_H +#define __MMA7455_H + +extern const struct regmap_config mma7455_core_regmap; + +int mma7455_core_probe(struct device *dev, struct regmap *regmap, + const char *name); +int mma7455_core_remove(struct device *dev); + +#endif diff --git a/drivers/iio/accel/mma7455_core.c b/drivers/iio/accel/mma7455_core.c new file mode 100644 index 000000000..c633cc2c0 --- /dev/null +++ b/drivers/iio/accel/mma7455_core.c @@ -0,0 +1,311 @@ +/* + * IIO accel core driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood <manabian@gmail.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 as + * published by the Free Software Foundation. + * + * UNSUPPORTED hardware features: + * - 8-bit mode with different scales + * - INT1/INT2 interrupts + * - Offset calibration + * - Events + */ + +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "mma7455.h" + +#define MMA7455_REG_XOUTL 0x00 +#define MMA7455_REG_XOUTH 0x01 +#define MMA7455_REG_YOUTL 0x02 +#define MMA7455_REG_YOUTH 0x03 +#define MMA7455_REG_ZOUTL 0x04 +#define MMA7455_REG_ZOUTH 0x05 +#define MMA7455_REG_STATUS 0x09 +#define MMA7455_STATUS_DRDY BIT(0) +#define MMA7455_REG_WHOAMI 0x0f +#define MMA7455_WHOAMI_ID 0x55 +#define MMA7455_REG_MCTL 0x16 +#define MMA7455_MCTL_MODE_STANDBY 0x00 +#define MMA7455_MCTL_MODE_MEASURE 0x01 +#define MMA7455_REG_CTL1 0x18 +#define MMA7455_CTL1_DFBW_MASK BIT(7) +#define MMA7455_CTL1_DFBW_125HZ BIT(7) +#define MMA7455_CTL1_DFBW_62_5HZ 0 +#define MMA7455_REG_TW 0x1e + +/* + * When MMA7455 is used in 10-bit it has a fullscale of -8g + * corresponding to raw value -512. The userspace interface + * uses m/s^2 and we declare micro units. + * So scale factor is given by: + * g * 8 * 1e6 / 512 = 153228.90625, with g = 9.80665 + */ +#define MMA7455_10BIT_SCALE 153229 + +struct mma7455_data { + struct regmap *regmap; + struct device *dev; +}; + +static int mma7455_drdy(struct mma7455_data *mma7455) +{ + unsigned int reg; + int tries = 3; + int ret; + + while (tries-- > 0) { + ret = regmap_read(mma7455->regmap, MMA7455_REG_STATUS, ®); + if (ret) + return ret; + + if (reg & MMA7455_STATUS_DRDY) + return 0; + + msleep(20); + } + + dev_warn(mma7455->dev, "data not ready\n"); + + return -EIO; +} + +static irqreturn_t mma7455_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mma7455_data *mma7455 = iio_priv(indio_dev); + u8 buf[16]; /* 3 x 16-bit channels + padding + ts */ + int ret; + + ret = mma7455_drdy(mma7455); + if (ret) + goto done; + + ret = regmap_bulk_read(mma7455->regmap, MMA7455_REG_XOUTL, buf, + sizeof(__le16) * 3); + if (ret) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int mma7455_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma7455_data *mma7455 = iio_priv(indio_dev); + unsigned int reg; + __le16 data; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + ret = mma7455_drdy(mma7455); + if (ret) + return ret; + + ret = regmap_bulk_read(mma7455->regmap, chan->address, &data, + sizeof(data)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(data), 9); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = MMA7455_10BIT_SCALE; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(mma7455->regmap, MMA7455_REG_CTL1, ®); + if (ret) + return ret; + + if (reg & MMA7455_CTL1_DFBW_MASK) + *val = 250; + else + *val = 125; + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int mma7455_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mma7455_data *mma7455 = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val == 250 && val2 == 0) + i = MMA7455_CTL1_DFBW_125HZ; + else if (val == 125 && val2 == 0) + i = MMA7455_CTL1_DFBW_62_5HZ; + else + return -EINVAL; + + return regmap_update_bits(mma7455->regmap, MMA7455_REG_CTL1, + MMA7455_CTL1_DFBW_MASK, i); + + case IIO_CHAN_INFO_SCALE: + /* In 10-bit mode there is only one scale available */ + if (val == 0 && val2 == MMA7455_10BIT_SCALE) + return 0; + break; + } + + return -EINVAL; +} + +static IIO_CONST_ATTR(sampling_frequency_available, "125 250"); + +static struct attribute *mma7455_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mma7455_group = { + .attrs = mma7455_attributes, +}; + +static const struct iio_info mma7455_info = { + .attrs = &mma7455_group, + .read_raw = mma7455_read_raw, + .write_raw = mma7455_write_raw, + .driver_module = THIS_MODULE, +}; + +#define MMA7455_CHANNEL(axis, idx) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .address = MMA7455_REG_##axis##OUTL,\ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 10, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec mma7455_channels[] = { + MMA7455_CHANNEL(X, 0), + MMA7455_CHANNEL(Y, 1), + MMA7455_CHANNEL(Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const unsigned long mma7455_scan_masks[] = {0x7, 0}; + +const struct regmap_config mma7455_core_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MMA7455_REG_TW, +}; +EXPORT_SYMBOL_GPL(mma7455_core_regmap); + +int mma7455_core_probe(struct device *dev, struct regmap *regmap, + const char *name) +{ + struct mma7455_data *mma7455; + struct iio_dev *indio_dev; + unsigned int reg; + int ret; + + ret = regmap_read(regmap, MMA7455_REG_WHOAMI, ®); + if (ret) { + dev_err(dev, "unable to read reg\n"); + return ret; + } + + if (reg != MMA7455_WHOAMI_ID) { + dev_err(dev, "device id mismatch\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*mma7455)); + if (!indio_dev) + return -ENOMEM; + + dev_set_drvdata(dev, indio_dev); + mma7455 = iio_priv(indio_dev); + mma7455->regmap = regmap; + mma7455->dev = dev; + + indio_dev->info = &mma7455_info; + indio_dev->name = name; + indio_dev->dev.parent = dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mma7455_channels; + indio_dev->num_channels = ARRAY_SIZE(mma7455_channels); + indio_dev->available_scan_masks = mma7455_scan_masks; + + regmap_write(mma7455->regmap, MMA7455_REG_MCTL, + MMA7455_MCTL_MODE_MEASURE); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + mma7455_trigger_handler, NULL); + if (ret) { + dev_err(dev, "unable to setup triggered buffer\n"); + return ret; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "unable to register device\n"); + iio_triggered_buffer_cleanup(indio_dev); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mma7455_core_probe); + +int mma7455_core_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mma7455_data *mma7455 = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + regmap_write(mma7455->regmap, MMA7455_REG_MCTL, + MMA7455_MCTL_MODE_STANDBY); + + return 0; +} +EXPORT_SYMBOL_GPL(mma7455_core_remove); + +MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>"); +MODULE_DESCRIPTION("Freescale MMA7455L core accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/mma7455_i2c.c b/drivers/iio/accel/mma7455_i2c.c new file mode 100644 index 000000000..3cab5fb4a --- /dev/null +++ b/drivers/iio/accel/mma7455_i2c.c @@ -0,0 +1,56 @@ +/* + * IIO accel I2C driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood <manabian@gmail.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 as + * published by the Free Software Foundation. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "mma7455.h" + +static int mma7455_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + const char *name = NULL; + + regmap = devm_regmap_init_i2c(i2c, &mma7455_core_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + if (id) + name = id->name; + + return mma7455_core_probe(&i2c->dev, regmap, name); +} + +static int mma7455_i2c_remove(struct i2c_client *i2c) +{ + return mma7455_core_remove(&i2c->dev); +} + +static const struct i2c_device_id mma7455_i2c_ids[] = { + { "mma7455", 0 }, + { "mma7456", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mma7455_i2c_ids); + +static struct i2c_driver mma7455_i2c_driver = { + .probe = mma7455_i2c_probe, + .remove = mma7455_i2c_remove, + .id_table = mma7455_i2c_ids, + .driver = { + .name = "mma7455-i2c", + }, +}; +module_i2c_driver(mma7455_i2c_driver); + +MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>"); +MODULE_DESCRIPTION("Freescale MMA7455L I2C accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/mma7455_spi.c b/drivers/iio/accel/mma7455_spi.c new file mode 100644 index 000000000..79df8f27c --- /dev/null +++ b/drivers/iio/accel/mma7455_spi.c @@ -0,0 +1,52 @@ +/* + * IIO accel SPI driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood <manabian@gmail.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 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> + +#include "mma7455.h" + +static int mma7455_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &mma7455_core_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return mma7455_core_probe(&spi->dev, regmap, id->name); +} + +static int mma7455_spi_remove(struct spi_device *spi) +{ + return mma7455_core_remove(&spi->dev); +} + +static const struct spi_device_id mma7455_spi_ids[] = { + { "mma7455", 0 }, + { "mma7456", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, mma7455_spi_ids); + +static struct spi_driver mma7455_spi_driver = { + .probe = mma7455_spi_probe, + .remove = mma7455_spi_remove, + .id_table = mma7455_spi_ids, + .driver = { + .name = "mma7455-spi", + }, +}; +module_spi_driver(mma7455_spi_driver); + +MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>"); +MODULE_DESCRIPTION("Freescale MMA7455L SPI accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index 1eccc2dcf..ccc632a7c 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -29,6 +29,7 @@ #include <linux/iio/events.h> #include <linux/delay.h> #include <linux/of_device.h> +#include <linux/of_irq.h> #define MMA8452_STATUS 0x00 #define MMA8452_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0)) @@ -57,7 +58,6 @@ #define MMA8452_FF_MT_COUNT 0x18 #define MMA8452_TRANSIENT_CFG 0x1d #define MMA8452_TRANSIENT_CFG_HPF_BYP BIT(0) -#define MMA8452_TRANSIENT_CFG_CHAN(chan) BIT(chan + 1) #define MMA8452_TRANSIENT_CFG_ELE BIT(4) #define MMA8452_TRANSIENT_SRC 0x1e #define MMA8452_TRANSIENT_SRC_XTRANSE BIT(1) @@ -143,6 +143,13 @@ struct mma_chip_info { u8 ev_count; }; +enum { + idx_x, + idx_y, + idx_z, + idx_ts, +}; + static int mma8452_drdy(struct mma8452_data *data) { int tries = 150; @@ -816,31 +823,31 @@ static struct attribute_group mma8452_event_attribute_group = { } static const struct iio_chan_spec mma8452_channels[] = { - MMA8452_CHANNEL(X, 0, 12), - MMA8452_CHANNEL(Y, 1, 12), - MMA8452_CHANNEL(Z, 2, 12), - IIO_CHAN_SOFT_TIMESTAMP(3), + MMA8452_CHANNEL(X, idx_x, 12), + MMA8452_CHANNEL(Y, idx_y, 12), + MMA8452_CHANNEL(Z, idx_z, 12), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), }; static const struct iio_chan_spec mma8453_channels[] = { - MMA8452_CHANNEL(X, 0, 10), - MMA8452_CHANNEL(Y, 1, 10), - MMA8452_CHANNEL(Z, 2, 10), - IIO_CHAN_SOFT_TIMESTAMP(3), + MMA8452_CHANNEL(X, idx_x, 10), + MMA8452_CHANNEL(Y, idx_y, 10), + MMA8452_CHANNEL(Z, idx_z, 10), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), }; static const struct iio_chan_spec mma8652_channels[] = { - MMA8652_CHANNEL(X, 0, 12), - MMA8652_CHANNEL(Y, 1, 12), - MMA8652_CHANNEL(Z, 2, 12), - IIO_CHAN_SOFT_TIMESTAMP(3), + MMA8652_CHANNEL(X, idx_x, 12), + MMA8652_CHANNEL(Y, idx_y, 12), + MMA8652_CHANNEL(Z, idx_z, 12), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), }; static const struct iio_chan_spec mma8653_channels[] = { - MMA8652_CHANNEL(X, 0, 10), - MMA8652_CHANNEL(Y, 1, 10), - MMA8652_CHANNEL(Z, 2, 10), - IIO_CHAN_SOFT_TIMESTAMP(3), + MMA8652_CHANNEL(X, idx_x, 10), + MMA8652_CHANNEL(Y, idx_y, 10), + MMA8652_CHANNEL(Z, idx_z, 10), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), }; enum { @@ -1130,13 +1137,21 @@ static int mma8452_probe(struct i2c_client *client, MMA8452_INT_FF_MT; int enabled_interrupts = MMA8452_INT_TRANS | MMA8452_INT_FF_MT; + int irq2; - /* Assume wired to INT1 pin */ - ret = i2c_smbus_write_byte_data(client, - MMA8452_CTRL_REG5, - supported_interrupts); - if (ret < 0) - return ret; + irq2 = of_irq_get_byname(client->dev.of_node, "INT2"); + + if (irq2 == client->irq) { + dev_dbg(&client->dev, "using interrupt line INT2\n"); + } else { + ret = i2c_smbus_write_byte_data(client, + MMA8452_CTRL_REG5, + supported_interrupts); + if (ret < 0) + return ret; + + dev_dbg(&client->dev, "using interrupt line INT1\n"); + } ret = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG4, diff --git a/drivers/iio/accel/mma9551.c b/drivers/iio/accel/mma9551.c index 7db7cc0bf..d899a4d43 100644 --- a/drivers/iio/accel/mma9551.c +++ b/drivers/iio/accel/mma9551.c @@ -495,25 +495,23 @@ static int mma9551_probe(struct i2c_client *client, if (ret < 0) goto out_poweroff; - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(&client->dev, "unable to register iio device\n"); - goto out_poweroff; - } - ret = pm_runtime_set_active(&client->dev); if (ret < 0) - goto out_iio_unregister; + goto out_poweroff; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, MMA9551_AUTO_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto out_poweroff; + } + return 0; -out_iio_unregister: - iio_device_unregister(indio_dev); out_poweroff: mma9551_set_device_state(client, false); @@ -525,11 +523,12 @@ static int mma9551_remove(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct mma9551_data *data = iio_priv(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(indio_dev); mutex_lock(&data->mutex); mma9551_set_device_state(data->client, false); mutex_unlock(&data->mutex); diff --git a/drivers/iio/accel/mma9553.c b/drivers/iio/accel/mma9553.c index 9408ef3ad..fa7d36217 100644 --- a/drivers/iio/accel/mma9553.c +++ b/drivers/iio/accel/mma9553.c @@ -1133,27 +1133,24 @@ static int mma9553_probe(struct i2c_client *client, } } - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(&client->dev, "unable to register iio device\n"); - goto out_poweroff; - } - ret = pm_runtime_set_active(&client->dev); if (ret < 0) - goto out_iio_unregister; + goto out_poweroff; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, MMA9551_AUTO_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); - dev_dbg(&indio_dev->dev, "Registered device %s\n", name); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto out_poweroff; + } + dev_dbg(&indio_dev->dev, "Registered device %s\n", name); return 0; -out_iio_unregister: - iio_device_unregister(indio_dev); out_poweroff: mma9551_set_device_state(client, false); return ret; @@ -1164,11 +1161,12 @@ static int mma9553_remove(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct mma9553_data *data = iio_priv(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(indio_dev); mutex_lock(&data->mutex); mma9551_set_device_state(data->client, false); mutex_unlock(&data->mutex); diff --git a/drivers/iio/accel/mxc6255.c b/drivers/iio/accel/mxc6255.c new file mode 100644 index 000000000..97ccde722 --- /dev/null +++ b/drivers/iio/accel/mxc6255.c @@ -0,0 +1,198 @@ +/* + * MXC6255 - MEMSIC orientation sensing accelerometer + * + * Copyright (c) 2015, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * IIO driver for MXC6255 (7-bit I2C slave address 0x15). + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/iio/iio.h> +#include <linux/delay.h> +#include <linux/acpi.h> +#include <linux/regmap.h> +#include <linux/iio/sysfs.h> + +#define MXC6255_DRV_NAME "mxc6255" +#define MXC6255_REGMAP_NAME "mxc6255_regmap" + +#define MXC6255_REG_XOUT 0x00 +#define MXC6255_REG_YOUT 0x01 +#define MXC6255_REG_CHIP_ID 0x08 + +#define MXC6255_CHIP_ID 0x05 + +/* + * MXC6255 has only one measurement range: +/- 2G. + * The acceleration output is an 8-bit value. + * + * Scale is calculated as follows: + * (2 + 2) * 9.80665 / (2^8 - 1) = 0.153829 + * + * Scale value for +/- 2G measurement range + */ +#define MXC6255_SCALE 153829 + +enum mxc6255_axis { + AXIS_X, + AXIS_Y, +}; + +struct mxc6255_data { + struct i2c_client *client; + struct regmap *regmap; +}; + +static int mxc6255_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mxc6255_data *data = iio_priv(indio_dev); + unsigned int reg; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->regmap, chan->address, ®); + if (ret < 0) { + dev_err(&data->client->dev, + "Error reading reg %lu\n", chan->address); + return ret; + } + + *val = sign_extend32(reg, 7); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = MXC6255_SCALE; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info mxc6255_info = { + .driver_module = THIS_MODULE, + .read_raw = mxc6255_read_raw, +}; + +#define MXC6255_CHANNEL(_axis, reg) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = reg, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mxc6255_channels[] = { + MXC6255_CHANNEL(X, MXC6255_REG_XOUT), + MXC6255_CHANNEL(Y, MXC6255_REG_YOUT), +}; + +static bool mxc6255_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MXC6255_REG_XOUT: + case MXC6255_REG_YOUT: + case MXC6255_REG_CHIP_ID: + return true; + default: + return false; + } +} + +static const struct regmap_config mxc6255_regmap_config = { + .name = MXC6255_REGMAP_NAME, + + .reg_bits = 8, + .val_bits = 8, + + .readable_reg = mxc6255_is_readable_reg, +}; + +static int mxc6255_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mxc6255_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + unsigned int chip_id; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &mxc6255_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Error initializing regmap\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + indio_dev->name = MXC6255_DRV_NAME; + indio_dev->dev.parent = &client->dev; + indio_dev->channels = mxc6255_channels; + indio_dev->num_channels = ARRAY_SIZE(mxc6255_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mxc6255_info; + + ret = regmap_read(data->regmap, MXC6255_REG_CHIP_ID, &chip_id); + if (ret < 0) { + dev_err(&client->dev, "Error reading chip id %d\n", ret); + return ret; + } + + if (chip_id != MXC6255_CHIP_ID) { + dev_err(&client->dev, "Invalid chip id %x\n", chip_id); + return -ENODEV; + } + + dev_dbg(&client->dev, "Chip id %x\n", chip_id); + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret < 0) { + dev_err(&client->dev, "Could not register IIO device\n"); + return ret; + } + + return 0; +} + +static const struct acpi_device_id mxc6255_acpi_match[] = { + {"MXC6255", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, mxc6255_acpi_match); + +static const struct i2c_device_id mxc6255_id[] = { + {"mxc6255", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, mxc6255_id); + +static struct i2c_driver mxc6255_driver = { + .driver = { + .name = MXC6255_DRV_NAME, + .acpi_match_table = ACPI_PTR(mxc6255_acpi_match), + }, + .probe = mxc6255_probe, + .id_table = mxc6255_id, +}; + +module_i2c_driver(mxc6255_driver); + +MODULE_AUTHOR("Teodora Baluta <teodora.baluta@intel.com>"); +MODULE_DESCRIPTION("MEMSIC MXC6255 orientation sensing accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/st_accel.h b/drivers/iio/accel/st_accel.h index 468f21fa2..5d4a1897b 100644 --- a/drivers/iio/accel/st_accel.h +++ b/drivers/iio/accel/st_accel.h @@ -27,6 +27,7 @@ #define LSM303DLM_ACCEL_DEV_NAME "lsm303dlm_accel" #define LSM330_ACCEL_DEV_NAME "lsm330_accel" #define LSM303AGR_ACCEL_DEV_NAME "lsm303agr_accel" +#define LIS2DH12_ACCEL_DEV_NAME "lis2dh12_accel" /** * struct st_sensors_platform_data - default accel platform data diff --git a/drivers/iio/accel/st_accel_core.c b/drivers/iio/accel/st_accel_core.c index 197a08b4e..70f042797 100644 --- a/drivers/iio/accel/st_accel_core.c +++ b/drivers/iio/accel/st_accel_core.c @@ -232,6 +232,7 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = { [3] = LSM330DL_ACCEL_DEV_NAME, [4] = LSM330DLC_ACCEL_DEV_NAME, [5] = LSM303AGR_ACCEL_DEV_NAME, + [6] = LIS2DH12_ACCEL_DEV_NAME, }, .ch = (struct iio_chan_spec *)st_accel_12bit_channels, .odr = { diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c index 8b9cc84fd..294a32f89 100644 --- a/drivers/iio/accel/st_accel_i2c.c +++ b/drivers/iio/accel/st_accel_i2c.c @@ -72,6 +72,10 @@ static const struct of_device_id st_accel_of_match[] = { .compatible = "st,lsm303agr-accel", .data = LSM303AGR_ACCEL_DEV_NAME, }, + { + .compatible = "st,lis2dh12-accel", + .data = LIS2DH12_ACCEL_DEV_NAME, + }, {}, }; MODULE_DEVICE_TABLE(of, st_accel_of_match); @@ -121,6 +125,7 @@ static const struct i2c_device_id st_accel_id_table[] = { { LSM303DLM_ACCEL_DEV_NAME }, { LSM330_ACCEL_DEV_NAME }, { LSM303AGR_ACCEL_DEV_NAME }, + { LIS2DH12_ACCEL_DEV_NAME }, {}, }; MODULE_DEVICE_TABLE(i2c, st_accel_id_table); diff --git a/drivers/iio/accel/st_accel_spi.c b/drivers/iio/accel/st_accel_spi.c index f71b0d391..fcd5847a3 100644 --- a/drivers/iio/accel/st_accel_spi.c +++ b/drivers/iio/accel/st_accel_spi.c @@ -58,6 +58,7 @@ static const struct spi_device_id st_accel_id_table[] = { { LSM303DLM_ACCEL_DEV_NAME }, { LSM330_ACCEL_DEV_NAME }, { LSM303AGR_ACCEL_DEV_NAME }, + { LIS2DH12_ACCEL_DEV_NAME }, {}, }; MODULE_DEVICE_TABLE(spi, st_accel_id_table); diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 1e7aded53..283ded774 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -175,6 +175,7 @@ config DA9150_GPADC config EXYNOS_ADC tristate "Exynos ADC driver support" depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST) + depends on HAS_IOMEM help Core support for the ADC block found in the Samsung EXYNOS series of SoCs for drivers such as the touchscreen and hwmon to use to share @@ -194,6 +195,26 @@ config HI8435 This driver can also be built as a module. If so, the module will be called hi8435. +config INA2XX_ADC + tristate "Texas Instruments INA2xx Power Monitors IIO driver" + depends on I2C && !SENSORS_INA2XX + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for TI INA2xx family of Power Monitors. + This driver is mutually exclusive with the HWMON version. + +config IMX7D_ADC + tristate "IMX7D ADC driver" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for IMX7D ADC. + + This driver can also be built as a module. If so, the module will be + called imx7d_adc. + config LP8788_ADC tristate "LP8788 ADC driver" depends on MFD_LP8788 @@ -275,6 +296,14 @@ config NAU7802 To compile this driver as a module, choose M here: the module will be called nau7802. +config PALMAS_GPADC + tristate "TI Palmas General Purpose ADC" + depends on MFD_PALMAS + help + Palmas series pmic chip by Texas Instruments (twl6035/6037) + is used in smartphones and tablets and supports a 16 channel + general purpose ADC. + config QCOM_SPMI_IADC tristate "Qualcomm SPMI PMIC current ADC" depends on SPMI @@ -324,15 +353,25 @@ config TI_ADC081C called ti-adc081c. config TI_ADC128S052 - tristate "Texas Instruments ADC128S052/ADC122S021" + tristate "Texas Instruments ADC128S052/ADC122S021/ADC124S021" depends on SPI help - If you say yes here you get support for Texas Instruments ADC128S052 - and ADC122S021 chips. + If you say yes here you get support for Texas Instruments ADC128S052, + ADC122S021 and ADC124S021 chips. This driver can also be built as a module. If so, the module will be called ti-adc128s052. +config TI_ADS8688 + tristate "Texas Instruments ADS8688" + depends on SPI && OF + help + If you say yes here you get support for Texas Instruments ADS8684 and + and ADS8688 ADC chips + + This driver can also be built as a module. If so, the module will be + called ti-ads8688. + config TI_AM335X_ADC tristate "TI's AM335X ADC driver" depends on MFD_TI_AM335X_TSCADC diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 99b37a963..6435780e9 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -20,6 +20,8 @@ obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o obj-$(CONFIG_HI8435) += hi8435.o +obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o +obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o obj-$(CONFIG_MAX1027) += max1027.o obj-$(CONFIG_MAX1363) += max1363.o @@ -27,11 +29,13 @@ obj-$(CONFIG_MCP320X) += mcp320x.o obj-$(CONFIG_MCP3422) += mcp3422.o obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o obj-$(CONFIG_NAU7802) += nau7802.o +obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o +obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o diff --git a/drivers/iio/adc/ad7793.c b/drivers/iio/adc/ad7793.c index 4d960d3b9..7b07bb651 100644 --- a/drivers/iio/adc/ad7793.c +++ b/drivers/iio/adc/ad7793.c @@ -478,10 +478,9 @@ static int ad7793_read_raw(struct iio_dev *indio_dev, *val2 = st-> scale_avail[(st->conf >> 8) & 0x7][1]; return IIO_VAL_INT_PLUS_NANO; - } else { - /* 1170mV / 2^23 * 6 */ - scale_uv = (1170ULL * 1000000000ULL * 6ULL); } + /* 1170mV / 2^23 * 6 */ + scale_uv = (1170ULL * 1000000000ULL * 6ULL); break; case IIO_TEMP: /* 1170mV / 0.81 mV/C / 2^23 */ diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c index 7b40925dd..f284cd6a9 100644 --- a/drivers/iio/adc/at91_adc.c +++ b/drivers/iio/adc/at91_adc.c @@ -742,7 +742,7 @@ static int at91_adc_of_get_resolution(struct at91_adc_state *st, return count; } - resolutions = kmalloc(count * sizeof(*resolutions), GFP_KERNEL); + resolutions = kmalloc_array(count, sizeof(*resolutions), GFP_KERNEL); if (!resolutions) return -ENOMEM; diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index 3a2dbb3b4..c15756d7b 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -35,6 +35,7 @@ #include <linux/regulator/consumer.h> #include <linux/of_platform.h> #include <linux/err.h> +#include <linux/input.h> #include <linux/iio/iio.h> #include <linux/iio/machine.h> @@ -42,12 +43,18 @@ #include <linux/mfd/syscon.h> #include <linux/regmap.h> +#include <linux/platform_data/touchscreen-s3c2410.h> + /* S3C/EXYNOS4412/5250 ADC_V1 registers definitions */ #define ADC_V1_CON(x) ((x) + 0x00) +#define ADC_V1_TSC(x) ((x) + 0x04) #define ADC_V1_DLY(x) ((x) + 0x08) #define ADC_V1_DATX(x) ((x) + 0x0C) +#define ADC_V1_DATY(x) ((x) + 0x10) +#define ADC_V1_UPDN(x) ((x) + 0x14) #define ADC_V1_INTCLR(x) ((x) + 0x18) #define ADC_V1_MUX(x) ((x) + 0x1c) +#define ADC_V1_CLRINTPNDNUP(x) ((x) + 0x20) /* S3C2410 ADC registers definitions */ #define ADC_S3C2410_MUX(x) ((x) + 0x18) @@ -71,6 +78,30 @@ #define ADC_S3C2410_DATX_MASK 0x3FF #define ADC_S3C2416_CON_RES_SEL (1u << 3) +/* touch screen always uses channel 0 */ +#define ADC_S3C2410_MUX_TS 0 + +/* ADCTSC Register Bits */ +#define ADC_S3C2443_TSC_UD_SEN (1u << 8) +#define ADC_S3C2410_TSC_YM_SEN (1u << 7) +#define ADC_S3C2410_TSC_YP_SEN (1u << 6) +#define ADC_S3C2410_TSC_XM_SEN (1u << 5) +#define ADC_S3C2410_TSC_XP_SEN (1u << 4) +#define ADC_S3C2410_TSC_PULL_UP_DISABLE (1u << 3) +#define ADC_S3C2410_TSC_AUTO_PST (1u << 2) +#define ADC_S3C2410_TSC_XY_PST(x) (((x) & 0x3) << 0) + +#define ADC_TSC_WAIT4INT (ADC_S3C2410_TSC_YM_SEN | \ + ADC_S3C2410_TSC_YP_SEN | \ + ADC_S3C2410_TSC_XP_SEN | \ + ADC_S3C2410_TSC_XY_PST(3)) + +#define ADC_TSC_AUTOPST (ADC_S3C2410_TSC_YM_SEN | \ + ADC_S3C2410_TSC_YP_SEN | \ + ADC_S3C2410_TSC_XP_SEN | \ + ADC_S3C2410_TSC_AUTO_PST | \ + ADC_S3C2410_TSC_XY_PST(0)) + /* Bit definitions for ADC_V2 */ #define ADC_V2_CON1_SOFT_RESET (1u << 2) @@ -88,7 +119,9 @@ /* Bit definitions common for ADC_V1 and ADC_V2 */ #define ADC_CON_EN_START (1u << 0) #define ADC_CON_EN_START_MASK (0x3 << 0) +#define ADC_DATX_PRESSED (1u << 15) #define ADC_DATX_MASK 0xFFF +#define ADC_DATY_MASK 0xFFF #define EXYNOS_ADC_TIMEOUT (msecs_to_jiffies(100)) @@ -98,17 +131,24 @@ struct exynos_adc { struct exynos_adc_data *data; struct device *dev; + struct input_dev *input; void __iomem *regs; struct regmap *pmu_map; struct clk *clk; struct clk *sclk; unsigned int irq; + unsigned int tsirq; + unsigned int delay; struct regulator *vdd; struct completion completion; u32 value; unsigned int version; + + bool read_ts; + u32 ts_x; + u32 ts_y; }; struct exynos_adc_data { @@ -197,6 +237,9 @@ static void exynos_adc_v1_init_hw(struct exynos_adc *info) /* Enable 12-bit ADC resolution */ con1 |= ADC_V1_CON_RES; writel(con1, ADC_V1_CON(info->regs)); + + /* set touchscreen delay */ + writel(info->delay, ADC_V1_DLY(info->regs)); } static void exynos_adc_v1_exit_hw(struct exynos_adc *info) @@ -480,8 +523,8 @@ static int exynos_read_raw(struct iio_dev *indio_dev, if (info->data->start_conv) info->data->start_conv(info, chan->address); - timeout = wait_for_completion_timeout - (&info->completion, EXYNOS_ADC_TIMEOUT); + timeout = wait_for_completion_timeout(&info->completion, + EXYNOS_ADC_TIMEOUT); if (timeout == 0) { dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); if (info->data->init_hw) @@ -498,13 +541,55 @@ static int exynos_read_raw(struct iio_dev *indio_dev, return ret; } +static int exynos_read_s3c64xx_ts(struct iio_dev *indio_dev, int *x, int *y) +{ + struct exynos_adc *info = iio_priv(indio_dev); + unsigned long timeout; + int ret; + + mutex_lock(&indio_dev->mlock); + info->read_ts = true; + + reinit_completion(&info->completion); + + writel(ADC_S3C2410_TSC_PULL_UP_DISABLE | ADC_TSC_AUTOPST, + ADC_V1_TSC(info->regs)); + + /* Select the ts channel to be used and Trigger conversion */ + info->data->start_conv(info, ADC_S3C2410_MUX_TS); + + timeout = wait_for_completion_timeout(&info->completion, + EXYNOS_ADC_TIMEOUT); + if (timeout == 0) { + dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); + if (info->data->init_hw) + info->data->init_hw(info); + ret = -ETIMEDOUT; + } else { + *x = info->ts_x; + *y = info->ts_y; + ret = 0; + } + + info->read_ts = false; + mutex_unlock(&indio_dev->mlock); + + return ret; +} + static irqreturn_t exynos_adc_isr(int irq, void *dev_id) { struct exynos_adc *info = (struct exynos_adc *)dev_id; u32 mask = info->data->mask; /* Read value */ - info->value = readl(ADC_V1_DATX(info->regs)) & mask; + if (info->read_ts) { + info->ts_x = readl(ADC_V1_DATX(info->regs)); + info->ts_y = readl(ADC_V1_DATY(info->regs)); + writel(ADC_TSC_WAIT4INT | ADC_S3C2443_TSC_UD_SEN, ADC_V1_TSC(info->regs)); + } else { + info->value = readl(ADC_V1_DATX(info->regs)) & mask; + } /* clear irq */ if (info->data->clear_irq) @@ -515,6 +600,46 @@ static irqreturn_t exynos_adc_isr(int irq, void *dev_id) return IRQ_HANDLED; } +/* + * Here we (ab)use a threaded interrupt handler to stay running + * for as long as the touchscreen remains pressed, we report + * a new event with the latest data and then sleep until the + * next timer tick. This mirrors the behavior of the old + * driver, with much less code. + */ +static irqreturn_t exynos_ts_isr(int irq, void *dev_id) +{ + struct exynos_adc *info = dev_id; + struct iio_dev *dev = dev_get_drvdata(info->dev); + u32 x, y; + bool pressed; + int ret; + + while (info->input->users) { + ret = exynos_read_s3c64xx_ts(dev, &x, &y); + if (ret == -ETIMEDOUT) + break; + + pressed = x & y & ADC_DATX_PRESSED; + if (!pressed) { + input_report_key(info->input, BTN_TOUCH, 0); + input_sync(info->input); + break; + } + + input_report_abs(info->input, ABS_X, x & ADC_DATX_MASK); + input_report_abs(info->input, ABS_Y, y & ADC_DATY_MASK); + input_report_key(info->input, BTN_TOUCH, 1); + input_sync(info->input); + + msleep(1); + }; + + writel(0, ADC_V1_CLRINTPNDNUP(info->regs)); + + return IRQ_HANDLED; +} + static int exynos_adc_reg_access(struct iio_dev *indio_dev, unsigned reg, unsigned writeval, unsigned *readval) @@ -566,18 +691,72 @@ static int exynos_adc_remove_devices(struct device *dev, void *c) return 0; } +static int exynos_adc_ts_open(struct input_dev *dev) +{ + struct exynos_adc *info = input_get_drvdata(dev); + + enable_irq(info->tsirq); + + return 0; +} + +static void exynos_adc_ts_close(struct input_dev *dev) +{ + struct exynos_adc *info = input_get_drvdata(dev); + + disable_irq(info->tsirq); +} + +static int exynos_adc_ts_init(struct exynos_adc *info) +{ + int ret; + + if (info->tsirq <= 0) + return -ENODEV; + + info->input = input_allocate_device(); + if (!info->input) + return -ENOMEM; + + info->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + info->input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(info->input, ABS_X, 0, 0x3FF, 0, 0); + input_set_abs_params(info->input, ABS_Y, 0, 0x3FF, 0, 0); + + info->input->name = "S3C24xx TouchScreen"; + info->input->id.bustype = BUS_HOST; + info->input->open = exynos_adc_ts_open; + info->input->close = exynos_adc_ts_close; + + input_set_drvdata(info->input, info); + + ret = input_register_device(info->input); + if (ret) { + input_free_device(info->input); + return ret; + } + + disable_irq(info->tsirq); + ret = request_threaded_irq(info->tsirq, NULL, exynos_ts_isr, + IRQF_ONESHOT, "touchscreen", info); + if (ret) + input_unregister_device(info->input); + + return ret; +} + static int exynos_adc_probe(struct platform_device *pdev) { struct exynos_adc *info = NULL; struct device_node *np = pdev->dev.of_node; + struct s3c2410_ts_mach_info *pdata = dev_get_platdata(&pdev->dev); struct iio_dev *indio_dev = NULL; struct resource *mem; + bool has_ts = false; int ret = -ENODEV; int irq; - if (!np) - return ret; - indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct exynos_adc)); if (!indio_dev) { dev_err(&pdev->dev, "failed allocating iio device\n"); @@ -613,8 +792,14 @@ static int exynos_adc_probe(struct platform_device *pdev) dev_err(&pdev->dev, "no irq resource?\n"); return irq; } - info->irq = irq; + + irq = platform_get_irq(pdev, 1); + if (irq == -EPROBE_DEFER) + return irq; + + info->tsirq = irq; + info->dev = &pdev->dev; init_completion(&info->completion); @@ -680,6 +865,22 @@ static int exynos_adc_probe(struct platform_device *pdev) if (info->data->init_hw) info->data->init_hw(info); + /* leave out any TS related code if unreachable */ + if (IS_REACHABLE(CONFIG_INPUT)) { + has_ts = of_property_read_bool(pdev->dev.of_node, + "has-touchscreen") || pdata; + } + + if (pdata) + info->delay = pdata->delay; + else + info->delay = 10000; + + if (has_ts) + ret = exynos_adc_ts_init(info); + if (ret) + goto err_iio; + ret = of_platform_populate(np, exynos_adc_match, NULL, &indio_dev->dev); if (ret < 0) { dev_err(&pdev->dev, "failed adding child nodes\n"); @@ -691,6 +892,11 @@ static int exynos_adc_probe(struct platform_device *pdev) err_of_populate: device_for_each_child(&indio_dev->dev, NULL, exynos_adc_remove_devices); + if (has_ts) { + input_unregister_device(info->input); + free_irq(info->tsirq, info); + } +err_iio: iio_device_unregister(indio_dev); err_irq: free_irq(info->irq, info); @@ -710,6 +916,10 @@ static int exynos_adc_remove(struct platform_device *pdev) struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct exynos_adc *info = iio_priv(indio_dev); + if (IS_REACHABLE(CONFIG_INPUT)) { + free_irq(info->tsirq, info); + input_unregister_device(info->input); + } device_for_each_child(&indio_dev->dev, NULL, exynos_adc_remove_devices); iio_device_unregister(indio_dev); diff --git a/drivers/iio/adc/imx7d_adc.c b/drivers/iio/adc/imx7d_adc.c new file mode 100644 index 000000000..e2241ee94 --- /dev/null +++ b/drivers/iio/adc/imx7d_adc.c @@ -0,0 +1,609 @@ +/* + * Freescale i.MX7D ADC driver + * + * Copyright (C) 2015 Freescale Semiconductor, Inc. + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/driver.h> +#include <linux/iio/sysfs.h> + +/* ADC register */ +#define IMX7D_REG_ADC_CH_A_CFG1 0x00 +#define IMX7D_REG_ADC_CH_A_CFG2 0x10 +#define IMX7D_REG_ADC_CH_B_CFG1 0x20 +#define IMX7D_REG_ADC_CH_B_CFG2 0x30 +#define IMX7D_REG_ADC_CH_C_CFG1 0x40 +#define IMX7D_REG_ADC_CH_C_CFG2 0x50 +#define IMX7D_REG_ADC_CH_D_CFG1 0x60 +#define IMX7D_REG_ADC_CH_D_CFG2 0x70 +#define IMX7D_REG_ADC_CH_SW_CFG 0x80 +#define IMX7D_REG_ADC_TIMER_UNIT 0x90 +#define IMX7D_REG_ADC_DMA_FIFO 0xa0 +#define IMX7D_REG_ADC_FIFO_STATUS 0xb0 +#define IMX7D_REG_ADC_INT_SIG_EN 0xc0 +#define IMX7D_REG_ADC_INT_EN 0xd0 +#define IMX7D_REG_ADC_INT_STATUS 0xe0 +#define IMX7D_REG_ADC_CHA_B_CNV_RSLT 0xf0 +#define IMX7D_REG_ADC_CHC_D_CNV_RSLT 0x100 +#define IMX7D_REG_ADC_CH_SW_CNV_RSLT 0x110 +#define IMX7D_REG_ADC_DMA_FIFO_DAT 0x120 +#define IMX7D_REG_ADC_ADC_CFG 0x130 + +#define IMX7D_REG_ADC_CHANNEL_CFG2_BASE 0x10 +#define IMX7D_EACH_CHANNEL_REG_OFFSET 0x20 + +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN (0x1 << 31) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE BIT(30) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN BIT(29) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(x) ((x) << 24) + +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4 (0x0 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8 (0x1 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16 (0x2 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32 (0x3 << 12) + +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4 (0x0 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8 (0x1 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16 (0x2 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32 (0x3 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64 (0x4 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128 (0x5 << 29) + +#define IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN BIT(31) +#define IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN BIT(1) +#define IMX7D_REG_ADC_ADC_CFG_ADC_EN BIT(0) + +#define IMX7D_REG_ADC_INT_CHA_COV_INT_EN BIT(8) +#define IMX7D_REG_ADC_INT_CHB_COV_INT_EN BIT(9) +#define IMX7D_REG_ADC_INT_CHC_COV_INT_EN BIT(10) +#define IMX7D_REG_ADC_INT_CHD_COV_INT_EN BIT(11) +#define IMX7D_REG_ADC_INT_CHANNEL_INT_EN \ + (IMX7D_REG_ADC_INT_CHA_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHB_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHC_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHD_COV_INT_EN) +#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS 0xf00 +#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT 0xf0000 + +#define IMX7D_ADC_TIMEOUT msecs_to_jiffies(100) + +enum imx7d_adc_clk_pre_div { + IMX7D_ADC_ANALOG_CLK_PRE_DIV_4, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_8, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_16, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_32, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_64, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_128, +}; + +enum imx7d_adc_average_num { + IMX7D_ADC_AVERAGE_NUM_4, + IMX7D_ADC_AVERAGE_NUM_8, + IMX7D_ADC_AVERAGE_NUM_16, + IMX7D_ADC_AVERAGE_NUM_32, +}; + +struct imx7d_adc_feature { + enum imx7d_adc_clk_pre_div clk_pre_div; + enum imx7d_adc_average_num avg_num; + + u32 core_time_unit; /* impact the sample rate */ + + bool average_en; +}; + +struct imx7d_adc { + struct device *dev; + void __iomem *regs; + struct clk *clk; + + u32 vref_uv; + u32 value; + u32 channel; + u32 pre_div_num; + + struct regulator *vref; + struct imx7d_adc_feature adc_feature; + + struct completion completion; +}; + +struct imx7d_adc_analogue_core_clk { + u32 pre_div; + u32 reg_config; +}; + +#define IMX7D_ADC_ANALOGUE_CLK_CONFIG(_pre_div, _reg_conf) { \ + .pre_div = (_pre_div), \ + .reg_config = (_reg_conf), \ +} + +static const struct imx7d_adc_analogue_core_clk imx7d_adc_analogue_clk[] = { + IMX7D_ADC_ANALOGUE_CLK_CONFIG(4, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(8, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(16, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(32, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(64, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(128, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128), +}; + +#define IMX7D_ADC_CHAN(_idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec imx7d_adc_iio_channels[] = { + IMX7D_ADC_CHAN(0), + IMX7D_ADC_CHAN(1), + IMX7D_ADC_CHAN(2), + IMX7D_ADC_CHAN(3), + IMX7D_ADC_CHAN(4), + IMX7D_ADC_CHAN(5), + IMX7D_ADC_CHAN(6), + IMX7D_ADC_CHAN(7), + IMX7D_ADC_CHAN(8), + IMX7D_ADC_CHAN(9), + IMX7D_ADC_CHAN(10), + IMX7D_ADC_CHAN(11), + IMX7D_ADC_CHAN(12), + IMX7D_ADC_CHAN(13), + IMX7D_ADC_CHAN(14), + IMX7D_ADC_CHAN(15), +}; + +static const u32 imx7d_adc_average_num[] = { + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32, +}; + +static void imx7d_adc_feature_config(struct imx7d_adc *info) +{ + info->adc_feature.clk_pre_div = IMX7D_ADC_ANALOG_CLK_PRE_DIV_4; + info->adc_feature.avg_num = IMX7D_ADC_AVERAGE_NUM_32; + info->adc_feature.core_time_unit = 1; + info->adc_feature.average_en = true; +} + +static void imx7d_adc_sample_rate_set(struct imx7d_adc *info) +{ + struct imx7d_adc_feature *adc_feature = &info->adc_feature; + struct imx7d_adc_analogue_core_clk adc_analogure_clk; + u32 i; + u32 tmp_cfg1; + u32 sample_rate = 0; + + /* + * Before sample set, disable channel A,B,C,D. Here we + * clear the bit 31 of register REG_ADC_CH_A\B\C\D_CFG1. + */ + for (i = 0; i < 4; i++) { + tmp_cfg1 = + readl(info->regs + i * IMX7D_EACH_CHANNEL_REG_OFFSET); + tmp_cfg1 &= ~IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN; + writel(tmp_cfg1, + info->regs + i * IMX7D_EACH_CHANNEL_REG_OFFSET); + } + + adc_analogure_clk = imx7d_adc_analogue_clk[adc_feature->clk_pre_div]; + sample_rate |= adc_analogure_clk.reg_config; + info->pre_div_num = adc_analogure_clk.pre_div; + + sample_rate |= adc_feature->core_time_unit; + writel(sample_rate, info->regs + IMX7D_REG_ADC_TIMER_UNIT); +} + +static void imx7d_adc_hw_init(struct imx7d_adc *info) +{ + u32 cfg; + + /* power up and enable adc analogue core */ + cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG); + cfg &= ~(IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN | + IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN); + cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_EN; + writel(cfg, info->regs + IMX7D_REG_ADC_ADC_CFG); + + /* enable channel A,B,C,D interrupt */ + writel(IMX7D_REG_ADC_INT_CHANNEL_INT_EN, + info->regs + IMX7D_REG_ADC_INT_SIG_EN); + writel(IMX7D_REG_ADC_INT_CHANNEL_INT_EN, + info->regs + IMX7D_REG_ADC_INT_EN); + + imx7d_adc_sample_rate_set(info); +} + +static void imx7d_adc_channel_set(struct imx7d_adc *info) +{ + u32 cfg1 = 0; + u32 cfg2; + u32 channel; + + channel = info->channel; + + /* the channel choose single conversion, and enable average mode */ + cfg1 |= (IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN | + IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE); + if (info->adc_feature.average_en) + cfg1 |= IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN; + + /* + * physical channel 0 chose logical channel A + * physical channel 1 chose logical channel B + * physical channel 2 chose logical channel C + * physical channel 3 chose logical channel D + */ + cfg1 |= IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(channel); + + /* + * read register REG_ADC_CH_A\B\C\D_CFG2, according to the + * channel chosen + */ + cfg2 = readl(info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel + + IMX7D_REG_ADC_CHANNEL_CFG2_BASE); + + cfg2 |= imx7d_adc_average_num[info->adc_feature.avg_num]; + + /* + * write the register REG_ADC_CH_A\B\C\D_CFG2, according to + * the channel chosen + */ + writel(cfg2, info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel + + IMX7D_REG_ADC_CHANNEL_CFG2_BASE); + writel(cfg1, info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel); +} + +static u32 imx7d_adc_get_sample_rate(struct imx7d_adc *info) +{ + /* input clock is always 24MHz */ + u32 input_clk = 24000000; + u32 analogue_core_clk; + u32 core_time_unit = info->adc_feature.core_time_unit; + u32 tmp; + + analogue_core_clk = input_clk / info->pre_div_num; + tmp = (core_time_unit + 1) * 6; + + return analogue_core_clk / tmp; +} + +static int imx7d_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct imx7d_adc *info = iio_priv(indio_dev); + + u32 channel; + long ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + reinit_completion(&info->completion); + + channel = chan->channel & 0x03; + info->channel = channel; + imx7d_adc_channel_set(info); + + ret = wait_for_completion_interruptible_timeout + (&info->completion, IMX7D_ADC_TIMEOUT); + if (ret == 0) { + mutex_unlock(&indio_dev->mlock); + return -ETIMEDOUT; + } + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + *val = info->value; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + info->vref_uv = regulator_get_voltage(info->vref); + *val = info->vref_uv / 1000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = imx7d_adc_get_sample_rate(info); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int imx7d_adc_read_data(struct imx7d_adc *info) +{ + u32 channel; + u32 value; + + channel = info->channel & 0x03; + + /* + * channel A and B conversion result share one register, + * bit[27~16] is the channel B conversion result, + * bit[11~0] is the channel A conversion result. + * channel C and D is the same. + */ + if (channel < 2) + value = readl(info->regs + IMX7D_REG_ADC_CHA_B_CNV_RSLT); + else + value = readl(info->regs + IMX7D_REG_ADC_CHC_D_CNV_RSLT); + if (channel & 0x1) /* channel B or D */ + value = (value >> 16) & 0xFFF; + else /* channel A or C */ + value &= 0xFFF; + + return value; +} + +static irqreturn_t imx7d_adc_isr(int irq, void *dev_id) +{ + struct imx7d_adc *info = (struct imx7d_adc *)dev_id; + int status; + + status = readl(info->regs + IMX7D_REG_ADC_INT_STATUS); + if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS) { + info->value = imx7d_adc_read_data(info); + complete(&info->completion); + + /* + * The register IMX7D_REG_ADC_INT_STATUS can't clear + * itself after read operation, need software to write + * 0 to the related bit. Here we clear the channel A/B/C/D + * conversion finished flag. + */ + status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS; + writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS); + } + + /* + * If the channel A/B/C/D conversion timeout, report it and clear these + * timeout flags. + */ + if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT) { + pr_err("%s: ADC got conversion time out interrupt: 0x%08x\n", + dev_name(info->dev), status); + status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT; + writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS); + } + + return IRQ_HANDLED; +} + +static int imx7d_adc_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct imx7d_adc *info = iio_priv(indio_dev); + + if (!readval || reg % 4 || reg > IMX7D_REG_ADC_ADC_CFG) + return -EINVAL; + + *readval = readl(info->regs + reg); + + return 0; +} + +static const struct iio_info imx7d_adc_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = &imx7d_adc_read_raw, + .debugfs_reg_access = &imx7d_adc_reg_access, +}; + +static const struct of_device_id imx7d_adc_match[] = { + { .compatible = "fsl,imx7d-adc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx7d_adc_match); + +static void imx7d_adc_power_down(struct imx7d_adc *info) +{ + u32 adc_cfg; + + adc_cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG); + adc_cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN | + IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN; + adc_cfg &= ~IMX7D_REG_ADC_ADC_CFG_ADC_EN; + writel(adc_cfg, info->regs + IMX7D_REG_ADC_ADC_CFG); +} + +static int imx7d_adc_probe(struct platform_device *pdev) +{ + struct imx7d_adc *info; + struct iio_dev *indio_dev; + struct resource *mem; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) { + dev_err(&pdev->dev, "Failed allocating iio device\n"); + return -ENOMEM; + } + + info = iio_priv(indio_dev); + info->dev = &pdev->dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->regs)) { + ret = PTR_ERR(info->regs); + dev_err(&pdev->dev, + "Failed to remap adc memory, err = %d\n", ret); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "No irq resource?\n"); + return irq; + } + + info->clk = devm_clk_get(&pdev->dev, "adc"); + if (IS_ERR(info->clk)) { + ret = PTR_ERR(info->clk); + dev_err(&pdev->dev, "Failed getting clock, err = %d\n", ret); + return ret; + } + + info->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(info->vref)) { + ret = PTR_ERR(info->vref); + dev_err(&pdev->dev, + "Failed getting reference voltage, err = %d\n", ret); + return ret; + } + + ret = regulator_enable(info->vref); + if (ret) { + dev_err(&pdev->dev, + "Can't enable adc reference top voltage, err = %d\n", + ret); + return ret; + } + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&info->completion); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &imx7d_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = imx7d_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(imx7d_adc_iio_channels); + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(&pdev->dev, + "Could not prepare or enable the clock.\n"); + goto error_adc_clk_enable; + } + + ret = devm_request_irq(info->dev, irq, + imx7d_adc_isr, 0, + dev_name(&pdev->dev), info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed requesting irq, irq = %d\n", irq); + goto error_iio_device_register; + } + + imx7d_adc_feature_config(info); + imx7d_adc_hw_init(info); + + ret = iio_device_register(indio_dev); + if (ret) { + imx7d_adc_power_down(info); + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto error_iio_device_register; + } + + return 0; + +error_iio_device_register: + clk_disable_unprepare(info->clk); +error_adc_clk_enable: + regulator_disable(info->vref); + + return ret; +} + +static int imx7d_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct imx7d_adc *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + imx7d_adc_power_down(info); + + clk_disable_unprepare(info->clk); + regulator_disable(info->vref); + + return 0; +} + +static int __maybe_unused imx7d_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx7d_adc *info = iio_priv(indio_dev); + + imx7d_adc_power_down(info); + + clk_disable_unprepare(info->clk); + regulator_disable(info->vref); + + return 0; +} + +static int __maybe_unused imx7d_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx7d_adc *info = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(info->vref); + if (ret) { + dev_err(info->dev, + "Can't enable adc reference top voltage, err = %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(info->dev, + "Could not prepare or enable clock.\n"); + regulator_disable(info->vref); + return ret; + } + + imx7d_adc_hw_init(info); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(imx7d_adc_pm_ops, imx7d_adc_suspend, imx7d_adc_resume); + +static struct platform_driver imx7d_adc_driver = { + .probe = imx7d_adc_probe, + .remove = imx7d_adc_remove, + .driver = { + .name = "imx7d_adc", + .of_match_table = imx7d_adc_match, + .pm = &imx7d_adc_pm_ops, + }, +}; + +module_platform_driver(imx7d_adc_driver); + +MODULE_AUTHOR("Haibo Chen <haibo.chen@freescale.com>"); +MODULE_DESCRIPTION("Freeacale IMX7D ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c new file mode 100644 index 000000000..d803e5018 --- /dev/null +++ b/drivers/iio/adc/ina2xx-adc.c @@ -0,0 +1,745 @@ +/* + * INA2XX Current and Power Monitors + * + * Copyright 2015 Baylibre SAS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Based on linux/drivers/iio/adc/ad7291.c + * Copyright 2010-2011 Analog Devices Inc. + * + * Based on linux/drivers/hwmon/ina2xx.c + * Copyright 2012 Lothar Felten <l-felten@ti.com> + * + * Licensed under the GPL-2 or later. + * + * IIO driver for INA219-220-226-230-231 + * + * Configurable 7-bit I2C slave address from 0x40 to 0x4F + */ +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/delay.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/sysfs.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/platform_data/ina2xx.h> + +#include <linux/util_macros.h> + +/* INA2XX registers definition */ +#define INA2XX_CONFIG 0x00 +#define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ +#define INA2XX_BUS_VOLTAGE 0x02 /* readonly */ +#define INA2XX_POWER 0x03 /* readonly */ +#define INA2XX_CURRENT 0x04 /* readonly */ +#define INA2XX_CALIBRATION 0x05 + +#define INA226_ALERT_MASK 0x06 +#define INA266_CVRF BIT(3) + +#define INA2XX_MAX_REGISTERS 8 + +/* settings - depend on use case */ +#define INA219_CONFIG_DEFAULT 0x399F /* PGA=8 */ +#define INA226_CONFIG_DEFAULT 0x4327 +#define INA226_DEFAULT_AVG 4 +#define INA226_DEFAULT_IT 1110 + +#define INA2XX_RSHUNT_DEFAULT 10000 + +/* + * bit mask for reading the averaging setting in the configuration register + * FIXME: use regmap_fields. + */ +#define INA2XX_MODE_MASK GENMASK(3, 0) + +#define INA226_AVG_MASK GENMASK(11, 9) +#define INA226_SHIFT_AVG(val) ((val) << 9) + +/* Integration time for VBus */ +#define INA226_ITB_MASK GENMASK(8, 6) +#define INA226_SHIFT_ITB(val) ((val) << 6) + +/* Integration time for VShunt */ +#define INA226_ITS_MASK GENMASK(5, 3) +#define INA226_SHIFT_ITS(val) ((val) << 3) + +/* Cosmetic macro giving the sampling period for a full P=UxI cycle */ +#define SAMPLING_PERIOD(c) ((c->int_time_vbus + c->int_time_vshunt) \ + * c->avg) + +static bool ina2xx_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return (reg == INA2XX_CONFIG) || (reg > INA2XX_CURRENT); +} + +static bool ina2xx_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (reg != INA2XX_CONFIG); +} + +static inline bool is_signed_reg(unsigned int reg) +{ + return (reg == INA2XX_SHUNT_VOLTAGE) || (reg == INA2XX_CURRENT); +} + +static const struct regmap_config ina2xx_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = INA2XX_MAX_REGISTERS, + .writeable_reg = ina2xx_is_writeable_reg, + .volatile_reg = ina2xx_is_volatile_reg, +}; + +enum ina2xx_ids { ina219, ina226 }; + +struct ina2xx_config { + u16 config_default; + int calibration_factor; + int shunt_div; + int bus_voltage_shift; + int bus_voltage_lsb; /* uV */ + int power_lsb; /* uW */ +}; + +struct ina2xx_chip_info { + struct regmap *regmap; + struct task_struct *task; + const struct ina2xx_config *config; + struct mutex state_lock; + unsigned int shunt_resistor; + int avg; + s64 prev_ns; /* track buffer capture time, check for underruns*/ + int int_time_vbus; /* Bus voltage integration time uS */ + int int_time_vshunt; /* Shunt voltage integration time uS */ + bool allow_async_readout; +}; + +static const struct ina2xx_config ina2xx_config[] = { + [ina219] = { + .config_default = INA219_CONFIG_DEFAULT, + .calibration_factor = 40960000, + .shunt_div = 100, + .bus_voltage_shift = 3, + .bus_voltage_lsb = 4000, + .power_lsb = 20000, + }, + [ina226] = { + .config_default = INA226_CONFIG_DEFAULT, + .calibration_factor = 5120000, + .shunt_div = 400, + .bus_voltage_shift = 0, + .bus_voltage_lsb = 1250, + .power_lsb = 25000, + }, +}; + +static int ina2xx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int regval; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(chip->regmap, chan->address, ®val); + if (ret < 0) + return ret; + + if (is_signed_reg(chan->address)) + *val = (s16) regval; + else + *val = regval; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = chip->avg; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + if (chan->address == INA2XX_SHUNT_VOLTAGE) + *val2 = chip->int_time_vshunt; + else + *val2 = chip->int_time_vbus; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * Sample freq is read only, it is a consequence of + * 1/AVG*(CT_bus+CT_shunt). + */ + *val = DIV_ROUND_CLOSEST(1000000, SAMPLING_PERIOD(chip)); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case INA2XX_SHUNT_VOLTAGE: + /* processed (mV) = raw*1000/shunt_div */ + *val2 = chip->config->shunt_div; + *val = 1000; + return IIO_VAL_FRACTIONAL; + + case INA2XX_BUS_VOLTAGE: + /* processed (mV) = raw*lsb (uV) / (1000 << shift) */ + *val = chip->config->bus_voltage_lsb; + *val2 = 1000 << chip->config->bus_voltage_shift; + return IIO_VAL_FRACTIONAL; + + case INA2XX_POWER: + /* processed (mW) = raw*lsb (uW) / 1000 */ + *val = chip->config->power_lsb; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + + case INA2XX_CURRENT: + /* processed (mA) = raw (mA) */ + *val = 1; + return IIO_VAL_INT; + } + } + + return -EINVAL; +} + +/* + * Available averaging rates for ina226. The indices correspond with + * the bit values expected by the chip (according to the ina226 datasheet, + * table 3 AVG bit settings, found at + * http://www.ti.com/lit/ds/symlink/ina226.pdf. + */ +static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 }; + +static int ina226_set_average(struct ina2xx_chip_info *chip, unsigned int val, + unsigned int *config) +{ + int bits; + + if (val > 1024 || val < 1) + return -EINVAL; + + bits = find_closest(val, ina226_avg_tab, + ARRAY_SIZE(ina226_avg_tab)); + + chip->avg = ina226_avg_tab[bits]; + + *config &= ~INA226_AVG_MASK; + *config |= INA226_SHIFT_AVG(bits) & INA226_AVG_MASK; + + return 0; +} + +/* Conversion times in uS */ +static const int ina226_conv_time_tab[] = { 140, 204, 332, 588, 1100, + 2116, 4156, 8244 }; + +static int ina226_set_int_time_vbus(struct ina2xx_chip_info *chip, + unsigned int val_us, unsigned int *config) +{ + int bits; + + if (val_us > 8244 || val_us < 140) + return -EINVAL; + + bits = find_closest(val_us, ina226_conv_time_tab, + ARRAY_SIZE(ina226_conv_time_tab)); + + chip->int_time_vbus = ina226_conv_time_tab[bits]; + + *config &= ~INA226_ITB_MASK; + *config |= INA226_SHIFT_ITB(bits) & INA226_ITB_MASK; + + return 0; +} + +static int ina226_set_int_time_vshunt(struct ina2xx_chip_info *chip, + unsigned int val_us, unsigned int *config) +{ + int bits; + + if (val_us > 8244 || val_us < 140) + return -EINVAL; + + bits = find_closest(val_us, ina226_conv_time_tab, + ARRAY_SIZE(ina226_conv_time_tab)); + + chip->int_time_vshunt = ina226_conv_time_tab[bits]; + + *config &= ~INA226_ITS_MASK; + *config |= INA226_SHIFT_ITS(bits) & INA226_ITS_MASK; + + return 0; +} + +static int ina2xx_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + int ret; + unsigned int config, tmp; + + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + mutex_lock(&chip->state_lock); + + ret = regmap_read(chip->regmap, INA2XX_CONFIG, &config); + if (ret < 0) + goto _err; + + tmp = config; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = ina226_set_average(chip, val, &tmp); + break; + + case IIO_CHAN_INFO_INT_TIME: + if (chan->address == INA2XX_SHUNT_VOLTAGE) + ret = ina226_set_int_time_vshunt(chip, val2, &tmp); + else + ret = ina226_set_int_time_vbus(chip, val2, &tmp); + break; + default: + ret = -EINVAL; + } + + if (!ret && (tmp != config)) + ret = regmap_write(chip->regmap, INA2XX_CONFIG, tmp); +_err: + mutex_unlock(&chip->state_lock); + + return ret; +} + + +static ssize_t ina2xx_allow_async_readout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%d\n", chip->allow_async_readout); +} + +static ssize_t ina2xx_allow_async_readout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + bool val; + int ret; + + ret = strtobool((const char *) buf, &val); + if (ret) + return ret; + + chip->allow_async_readout = val; + + return len; +} + +static int set_shunt_resistor(struct ina2xx_chip_info *chip, unsigned int val) +{ + if (val <= 0 || val > chip->config->calibration_factor) + return -EINVAL; + + chip->shunt_resistor = val; + return 0; +} + +static ssize_t ina2xx_shunt_resistor_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%d\n", chip->shunt_resistor); +} + +static ssize_t ina2xx_shunt_resistor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + unsigned long val; + int ret; + + ret = kstrtoul((const char *) buf, 10, &val); + if (ret) + return ret; + + ret = set_shunt_resistor(chip, val); + if (ret) + return ret; + + return len; +} + +#define INA2XX_CHAN(_type, _index, _address) { \ + .type = (_type), \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +/* + * Sampling Freq is a consequence of the integration times of + * the Voltage channels. + */ +#define INA2XX_CHAN_VOLTAGE(_index, _address) { \ + .type = IIO_VOLTAGE, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + } \ +} + +static const struct iio_chan_spec ina2xx_channels[] = { + INA2XX_CHAN_VOLTAGE(0, INA2XX_SHUNT_VOLTAGE), + INA2XX_CHAN_VOLTAGE(1, INA2XX_BUS_VOLTAGE), + INA2XX_CHAN(IIO_POWER, 2, INA2XX_POWER), + INA2XX_CHAN(IIO_CURRENT, 3, INA2XX_CURRENT), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int ina2xx_work_buffer(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned short data[8]; + int bit, ret, i = 0; + unsigned long buffer_us, elapsed_us; + s64 time_a, time_b; + unsigned int alert; + + time_a = iio_get_time_ns(); + + /* + * Because the timer thread and the chip conversion clock + * are asynchronous, the period difference will eventually + * result in reading V[k-1] again, or skip V[k] at time Tk. + * In order to resync the timer with the conversion process + * we check the ConVersionReadyFlag. + * On hardware that supports using the ALERT pin to toggle a + * GPIO a triggered buffer could be used instead. + * For now, we pay for that extra read of the ALERT register + */ + if (!chip->allow_async_readout) + do { + ret = regmap_read(chip->regmap, INA226_ALERT_MASK, + &alert); + if (ret < 0) + return ret; + + alert &= INA266_CVRF; + trace_printk("Conversion ready: %d\n", !!alert); + + } while (!alert); + + /* + * Single register reads: bulk_read will not work with ina226 + * as there is no auto-increment of the address register for + * data length longer than 16bits. + */ + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + unsigned int val; + + ret = regmap_read(chip->regmap, + INA2XX_SHUNT_VOLTAGE + bit, &val); + if (ret < 0) + return ret; + + data[i++] = val; + } + + time_b = iio_get_time_ns(); + + iio_push_to_buffers_with_timestamp(indio_dev, + (unsigned int *)data, time_a); + + buffer_us = (unsigned long)(time_b - time_a) / 1000; + elapsed_us = (unsigned long)(time_a - chip->prev_ns) / 1000; + + trace_printk("uS: elapsed: %lu, buf: %lu\n", elapsed_us, buffer_us); + + chip->prev_ns = time_a; + + return buffer_us; +}; + +static int ina2xx_capture_thread(void *data) +{ + struct iio_dev *indio_dev = (struct iio_dev *)data; + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int sampling_us = SAMPLING_PERIOD(chip); + int buffer_us; + + /* + * Poll a bit faster than the chip internal Fs, in case + * we wish to sync with the conversion ready flag. + */ + if (!chip->allow_async_readout) + sampling_us -= 200; + + do { + buffer_us = ina2xx_work_buffer(indio_dev); + if (buffer_us < 0) + return buffer_us; + + if (sampling_us > buffer_us) + udelay(sampling_us - buffer_us); + + } while (!kthread_should_stop()); + + return 0; +} + +static int ina2xx_buffer_enable(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int sampling_us = SAMPLING_PERIOD(chip); + + trace_printk("Enabling buffer w/ scan_mask %02x, freq = %d, avg =%u\n", + (unsigned int)(*indio_dev->active_scan_mask), + 1000000/sampling_us, chip->avg); + + trace_printk("Expected work period: %u us\n", sampling_us); + trace_printk("Async readout mode: %d\n", chip->allow_async_readout); + + chip->prev_ns = iio_get_time_ns(); + + chip->task = kthread_run(ina2xx_capture_thread, (void *)indio_dev, + "%s:%d-%uus", indio_dev->name, indio_dev->id, + sampling_us); + + return PTR_ERR_OR_ZERO(chip->task); +} + +static int ina2xx_buffer_disable(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + if (chip->task) { + kthread_stop(chip->task); + chip->task = NULL; + } + + return 0; +} + +static const struct iio_buffer_setup_ops ina2xx_setup_ops = { + .postenable = &ina2xx_buffer_enable, + .predisable = &ina2xx_buffer_disable, +}; + +static int ina2xx_debug_reg(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, unsigned *readval) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + if (!readval) + return regmap_write(chip->regmap, reg, writeval); + + return regmap_read(chip->regmap, reg, readval); +} + +/* Possible integration times for vshunt and vbus */ +static IIO_CONST_ATTR_INT_TIME_AVAIL \ + ("0.000140 0.000204 0.000332 0.000588 0.001100 0.002116 0.004156 0.008244"); + +static IIO_DEVICE_ATTR(in_allow_async_readout, S_IRUGO | S_IWUSR, + ina2xx_allow_async_readout_show, + ina2xx_allow_async_readout_store, 0); + +static IIO_DEVICE_ATTR(in_shunt_resistor, S_IRUGO | S_IWUSR, + ina2xx_shunt_resistor_show, + ina2xx_shunt_resistor_store, 0); + +static struct attribute *ina2xx_attributes[] = { + &iio_dev_attr_in_allow_async_readout.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_dev_attr_in_shunt_resistor.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ina2xx_attribute_group = { + .attrs = ina2xx_attributes, +}; + +static const struct iio_info ina2xx_info = { + .debugfs_reg_access = &ina2xx_debug_reg, + .read_raw = &ina2xx_read_raw, + .write_raw = &ina2xx_write_raw, + .attrs = &ina2xx_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* Initialize the configuration and calibration registers. */ +static int ina2xx_init(struct ina2xx_chip_info *chip, unsigned int config) +{ + u16 regval; + int ret = regmap_write(chip->regmap, INA2XX_CONFIG, config); + + if (ret < 0) + return ret; + /* + * Set current LSB to 1mA, shunt is in uOhms + * (equation 13 in datasheet). We hardcode a Current_LSB + * of 1.0 x10-6. The only remaining parameter is RShunt. + * There is no need to expose the CALIBRATION register + * to the user for now. + */ + regval = DIV_ROUND_CLOSEST(chip->config->calibration_factor, + chip->shunt_resistor); + + return regmap_write(chip->regmap, INA2XX_CALIBRATION, regval); +} + +static int ina2xx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ina2xx_chip_info *chip; + struct iio_dev *indio_dev; + struct iio_buffer *buffer; + int ret; + unsigned int val; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + chip->config = &ina2xx_config[id->driver_data]; + + if (of_property_read_u32(client->dev.of_node, + "shunt-resistor", &val) < 0) { + struct ina2xx_platform_data *pdata = + dev_get_platdata(&client->dev); + + if (pdata) + val = pdata->shunt_uohms; + else + val = INA2XX_RSHUNT_DEFAULT; + } + + ret = set_shunt_resistor(chip, val); + if (ret) + return ret; + + mutex_init(&chip->state_lock); + + /* This is only used for device removal purposes. */ + i2c_set_clientdata(client, indio_dev); + + indio_dev->name = id->name; + indio_dev->channels = ina2xx_channels; + indio_dev->num_channels = ARRAY_SIZE(ina2xx_channels); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ina2xx_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + + chip->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config); + if (IS_ERR(chip->regmap)) { + dev_err(&client->dev, "failed to allocate register map\n"); + return PTR_ERR(chip->regmap); + } + + /* Patch the current config register with default. */ + val = chip->config->config_default; + + if (id->driver_data == ina226) { + ina226_set_average(chip, INA226_DEFAULT_AVG, &val); + ina226_set_int_time_vbus(chip, INA226_DEFAULT_IT, &val); + ina226_set_int_time_vshunt(chip, INA226_DEFAULT_IT, &val); + } + + ret = ina2xx_init(chip, val); + if (ret < 0) { + dev_err(&client->dev, "error configuring the device: %d\n", + ret); + return -ENODEV; + } + + buffer = devm_iio_kfifo_allocate(&indio_dev->dev); + if (!buffer) + return -ENOMEM; + + indio_dev->setup_ops = &ina2xx_setup_ops; + + iio_device_attach_buffer(indio_dev, buffer); + + return iio_device_register(indio_dev); +} + + +static int ina2xx_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + /* Powerdown */ + return regmap_update_bits(chip->regmap, INA2XX_CONFIG, + INA2XX_MODE_MASK, 0); +} + + +static const struct i2c_device_id ina2xx_id[] = { + {"ina219", ina219}, + {"ina220", ina219}, + {"ina226", ina226}, + {"ina230", ina226}, + {"ina231", ina226}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ina2xx_id); + +static struct i2c_driver ina2xx_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = ina2xx_probe, + .remove = ina2xx_remove, + .id_table = ina2xx_id, +}; + +module_i2c_driver(ina2xx_driver); + +MODULE_AUTHOR("Marc Titinger <marc.titinger@baylibre.com>"); +MODULE_DESCRIPTION("Texas Instruments INA2XX ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mcp320x.c b/drivers/iio/adc/mcp320x.c index 8569c8e1f..d1c05f6ee 100644 --- a/drivers/iio/adc/mcp320x.c +++ b/drivers/iio/adc/mcp320x.c @@ -354,6 +354,7 @@ static int mcp320x_remove(struct spi_device *spi) #if defined(CONFIG_OF) static const struct of_device_id mcp320x_dt_ids[] = { + /* NOTE: The use of compatibles with no vendor prefix is deprecated. */ { .compatible = "mcp3001", .data = &mcp320x_chip_infos[mcp3001], @@ -382,6 +383,33 @@ static const struct of_device_id mcp320x_dt_ids[] = { .compatible = "mcp3301", .data = &mcp320x_chip_infos[mcp3301], }, { + .compatible = "microchip,mcp3001", + .data = &mcp320x_chip_infos[mcp3001], + }, { + .compatible = "microchip,mcp3002", + .data = &mcp320x_chip_infos[mcp3002], + }, { + .compatible = "microchip,mcp3004", + .data = &mcp320x_chip_infos[mcp3004], + }, { + .compatible = "microchip,mcp3008", + .data = &mcp320x_chip_infos[mcp3008], + }, { + .compatible = "microchip,mcp3201", + .data = &mcp320x_chip_infos[mcp3201], + }, { + .compatible = "microchip,mcp3202", + .data = &mcp320x_chip_infos[mcp3202], + }, { + .compatible = "microchip,mcp3204", + .data = &mcp320x_chip_infos[mcp3204], + }, { + .compatible = "microchip,mcp3208", + .data = &mcp320x_chip_infos[mcp3208], + }, { + .compatible = "microchip,mcp3301", + .data = &mcp320x_chip_infos[mcp3301], + }, { } }; MODULE_DEVICE_TABLE(of, mcp320x_dt_ids); diff --git a/drivers/iio/adc/mcp3422.c b/drivers/iio/adc/mcp3422.c index 355512200..6eca7aea8 100644 --- a/drivers/iio/adc/mcp3422.c +++ b/drivers/iio/adc/mcp3422.c @@ -305,6 +305,10 @@ static const struct attribute_group mcp3422_attribute_group = { .attrs = mcp3422_attributes, }; +static const struct iio_chan_spec mcp3421_channels[] = { + MCP3422_CHAN(0), +}; + static const struct iio_chan_spec mcp3422_channels[] = { MCP3422_CHAN(0), MCP3422_CHAN(1), @@ -352,6 +356,10 @@ static int mcp3422_probe(struct i2c_client *client, indio_dev->info = &mcp3422_info; switch (adc->id) { + case 1: + indio_dev->channels = mcp3421_channels; + indio_dev->num_channels = ARRAY_SIZE(mcp3421_channels); + break; case 2: case 3: case 6: @@ -383,6 +391,7 @@ static int mcp3422_probe(struct i2c_client *client, } static const struct i2c_device_id mcp3422_id[] = { + { "mcp3421", 1 }, { "mcp3422", 2 }, { "mcp3423", 3 }, { "mcp3424", 4 }, diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c new file mode 100644 index 000000000..f42eb8a7d --- /dev/null +++ b/drivers/iio/adc/palmas_gpadc.c @@ -0,0 +1,859 @@ +/* + * palmas-adc.c -- TI PALMAS GPADC. + * + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved. + * + * Author: Pradeep Goudagunta <pgoudagunta@nvidia.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 version 2. + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/pm.h> +#include <linux/mfd/palmas.h> +#include <linux/completion.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/iio/iio.h> +#include <linux/iio/machine.h> +#include <linux/iio/driver.h> + +#define MOD_NAME "palmas-gpadc" +#define PALMAS_ADC_CONVERSION_TIMEOUT (msecs_to_jiffies(5000)) +#define PALMAS_TO_BE_CALCULATED 0 +#define PALMAS_GPADC_TRIMINVALID -1 + +struct palmas_gpadc_info { +/* calibration codes and regs */ + int x1; /* lower ideal code */ + int x2; /* higher ideal code */ + int v1; /* expected lower volt reading */ + int v2; /* expected higher volt reading */ + u8 trim1_reg; /* register number for lower trim */ + u8 trim2_reg; /* register number for upper trim */ + int gain; /* calculated from above (after reading trim regs) */ + int offset; /* calculated from above (after reading trim regs) */ + int gain_error; /* calculated from above (after reading trim regs) */ + bool is_uncalibrated; /* if channel has calibration data */ +}; + +#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_uncalibrated) \ + [PALMAS_ADC_CH_##_chan] = { \ + .x1 = _x1, \ + .x2 = _x2, \ + .v1 = _v1, \ + .v2 = _v2, \ + .gain = PALMAS_TO_BE_CALCULATED, \ + .offset = PALMAS_TO_BE_CALCULATED, \ + .gain_error = PALMAS_TO_BE_CALCULATED, \ + .trim1_reg = PALMAS_GPADC_TRIM##_t1, \ + .trim2_reg = PALMAS_GPADC_TRIM##_t2, \ + .is_uncalibrated = _is_uncalibrated \ + } + +static struct palmas_gpadc_info palmas_gpadc_info[] = { + PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false), + PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false), + PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false), + PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false), + PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false), + PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false), + PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true), + PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true), + PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true), + PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false), + PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true), +}; + +/** + * struct palmas_gpadc - the palmas_gpadc structure + * @ch0_current: channel 0 current source setting + * 0: 0 uA + * 1: 5 uA + * 2: 15 uA + * 3: 20 uA + * @ch3_current: channel 0 current source setting + * 0: 0 uA + * 1: 10 uA + * 2: 400 uA + * 3: 800 uA + * @extended_delay: enable the gpadc extended delay mode + * @auto_conversion_period: define the auto_conversion_period + * + * This is the palmas_gpadc structure to store run-time information + * and pointers for this driver instance. + */ + +struct palmas_gpadc { + struct device *dev; + struct palmas *palmas; + u8 ch0_current; + u8 ch3_current; + bool extended_delay; + int irq; + int irq_auto_0; + int irq_auto_1; + struct palmas_gpadc_info *adc_info; + struct completion conv_completion; + struct palmas_adc_wakeup_property wakeup1_data; + struct palmas_adc_wakeup_property wakeup2_data; + bool wakeup1_enable; + bool wakeup2_enable; + int auto_conversion_period; +}; + +/* + * GPADC lock issue in AUTO mode. + * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO + * mode feature. + * Details: + * When the AUTO mode is the only conversion mode enabled, if the AUTO + * mode feature is disabled with bit GPADC_AUTO_CTRL. AUTO_CONV1_EN = 0 + * or bit GPADC_AUTO_CTRL. AUTO_CONV0_EN = 0 during a conversion, the + * conversion mechanism can be seen as locked meaning that all following + * conversion will give 0 as a result. Bit GPADC_STATUS.GPADC_AVAILABLE + * will stay at 0 meaning that GPADC is busy. An RT conversion can unlock + * the GPADC. + * + * Workaround(s): + * To avoid the lock mechanism, the workaround to follow before any stop + * conversion request is: + * Force the GPADC state machine to be ON by using the GPADC_CTRL1. + * GPADC_FORCE bit = 1 + * Shutdown the GPADC AUTO conversion using + * GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0. + * After 100us, force the GPADC state machine to be OFF by using the + * GPADC_CTRL1. GPADC_FORCE bit = 0 + */ + +static int palmas_disable_auto_conversion(struct palmas_gpadc *adc) +{ + int ret; + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, + PALMAS_GPADC_CTRL1_GPADC_FORCE, + PALMAS_GPADC_CTRL1_GPADC_FORCE); + if (ret < 0) { + dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret); + return ret; + } + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_CTRL, + PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 | + PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0, + 0); + if (ret < 0) { + dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret); + return ret; + } + + udelay(100); + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, + PALMAS_GPADC_CTRL1_GPADC_FORCE, 0); + if (ret < 0) + dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret); + + return ret; +} + +static irqreturn_t palmas_gpadc_irq(int irq, void *data) +{ + struct palmas_gpadc *adc = data; + + complete(&adc->conv_completion); + + return IRQ_HANDLED; +} + +static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data) +{ + struct palmas_gpadc *adc = data; + + dev_dbg(adc->dev, "Threshold interrupt %d occurs\n", irq); + palmas_disable_auto_conversion(adc); + + return IRQ_HANDLED; +} + +static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc, + bool mask) +{ + int ret; + + if (!mask) + ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE, + PALMAS_INT3_MASK, + PALMAS_INT3_MASK_GPADC_EOC_SW, 0); + else + ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE, + PALMAS_INT3_MASK, + PALMAS_INT3_MASK_GPADC_EOC_SW, + PALMAS_INT3_MASK_GPADC_EOC_SW); + if (ret < 0) + dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret); + + return ret; +} + +static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan, + int enable) +{ + unsigned int mask, val; + int ret; + + if (enable) { + val = (adc->extended_delay + << PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT); + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_RT_CTRL, + PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val); + if (ret < 0) { + dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret); + return ret; + } + + mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK | + PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK | + PALMAS_GPADC_CTRL1_GPADC_FORCE); + val = (adc->ch0_current + << PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT); + val |= (adc->ch3_current + << PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT); + val |= PALMAS_GPADC_CTRL1_GPADC_FORCE; + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, mask, val); + if (ret < 0) { + dev_err(adc->dev, + "Failed to update current setting: %d\n", ret); + return ret; + } + + mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK | + PALMAS_GPADC_SW_SELECT_SW_CONV_EN); + val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN); + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_SELECT, mask, val); + if (ret < 0) { + dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret); + return ret; + } + } else { + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_SELECT, 0); + if (ret < 0) + dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret); + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, + PALMAS_GPADC_CTRL1_GPADC_FORCE, 0); + if (ret < 0) { + dev_err(adc->dev, "CTRL1 update failed: %d\n", ret); + return ret; + } + } + + return ret; +} + +static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan) +{ + int ret; + + ret = palmas_gpadc_enable(adc, adc_chan, true); + if (ret < 0) + return ret; + + return palmas_gpadc_start_mask_interrupt(adc, 0); +} + +static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan) +{ + palmas_gpadc_start_mask_interrupt(adc, 1); + palmas_gpadc_enable(adc, adc_chan, false); +} + +static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan) +{ + int k; + int d1; + int d2; + int ret; + int gain; + int x1 = adc->adc_info[adc_chan].x1; + int x2 = adc->adc_info[adc_chan].x2; + int v1 = adc->adc_info[adc_chan].v1; + int v2 = adc->adc_info[adc_chan].v2; + + ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE, + adc->adc_info[adc_chan].trim1_reg, &d1); + if (ret < 0) { + dev_err(adc->dev, "TRIM read failed: %d\n", ret); + goto scrub; + } + + ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE, + adc->adc_info[adc_chan].trim2_reg, &d2); + if (ret < 0) { + dev_err(adc->dev, "TRIM read failed: %d\n", ret); + goto scrub; + } + + /* gain error calculation */ + k = (1000 + (1000 * (d2 - d1)) / (x2 - x1)); + + /* gain calculation */ + gain = ((v2 - v1) * 1000) / (x2 - x1); + + adc->adc_info[adc_chan].gain_error = k; + adc->adc_info[adc_chan].gain = gain; + /* offset Calculation */ + adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1); + +scrub: + return ret; +} + +static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan) +{ + unsigned int val; + int ret; + + init_completion(&adc->conv_completion); + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_SELECT, + PALMAS_GPADC_SW_SELECT_SW_START_CONV0, + PALMAS_GPADC_SW_SELECT_SW_START_CONV0); + if (ret < 0) { + dev_err(adc->dev, "SELECT_SW_START write failed: %d\n", ret); + return ret; + } + + ret = wait_for_completion_timeout(&adc->conv_completion, + PALMAS_ADC_CONVERSION_TIMEOUT); + if (ret == 0) { + dev_err(adc->dev, "conversion not completed\n"); + return -ETIMEDOUT; + } + + ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_CONV0_LSB, &val, 2); + if (ret < 0) { + dev_err(adc->dev, "SW_CONV0_LSB read failed: %d\n", ret); + return ret; + } + + ret = val & 0xFFF; + + return ret; +} + +static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc, + int adc_chan, int val) +{ + if (!adc->adc_info[adc_chan].is_uncalibrated) + val = (val*1000 - adc->adc_info[adc_chan].offset) / + adc->adc_info[adc_chan].gain_error; + + if (val < 0) { + dev_err(adc->dev, "Mismatch with calibration\n"); + return 0; + } + + val = (val * adc->adc_info[adc_chan].gain) / 1000; + + return val; +} + +static int palmas_gpadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct palmas_gpadc *adc = iio_priv(indio_dev); + int adc_chan = chan->channel; + int ret = 0; + + if (adc_chan > PALMAS_ADC_CH_MAX) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + ret = palmas_gpadc_read_prepare(adc, adc_chan); + if (ret < 0) + goto out; + + ret = palmas_gpadc_start_conversion(adc, adc_chan); + if (ret < 0) { + dev_err(adc->dev, + "ADC start conversion failed\n"); + goto out; + } + + if (mask == IIO_CHAN_INFO_PROCESSED) + ret = palmas_gpadc_get_calibrated_code( + adc, adc_chan, ret); + + *val = ret; + + ret = IIO_VAL_INT; + goto out; + } + + mutex_unlock(&indio_dev->mlock); + return ret; + +out: + palmas_gpadc_read_done(adc, adc_chan); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static const struct iio_info palmas_gpadc_iio_info = { + .read_raw = palmas_gpadc_read_raw, + .driver_module = THIS_MODULE, +}; + +#define PALMAS_ADC_CHAN_IIO(chan, _type, chan_info) \ +{ \ + .datasheet_name = PALMAS_DATASHEET_NAME(chan), \ + .type = _type, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(chan_info), \ + .indexed = 1, \ + .channel = PALMAS_ADC_CH_##chan, \ +} + +static const struct iio_chan_spec palmas_gpadc_iio_channel[] = { + PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN1, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN3, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN12, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN13, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), +}; + +static int palmas_gpadc_get_adc_dt_data(struct platform_device *pdev, + struct palmas_gpadc_platform_data **gpadc_pdata) +{ + struct device_node *np = pdev->dev.of_node; + struct palmas_gpadc_platform_data *gp_data; + int ret; + u32 pval; + + gp_data = devm_kzalloc(&pdev->dev, sizeof(*gp_data), GFP_KERNEL); + if (!gp_data) + return -ENOMEM; + + ret = of_property_read_u32(np, "ti,channel0-current-microamp", &pval); + if (!ret) + gp_data->ch0_current = pval; + + ret = of_property_read_u32(np, "ti,channel3-current-microamp", &pval); + if (!ret) + gp_data->ch3_current = pval; + + gp_data->extended_delay = of_property_read_bool(np, + "ti,enable-extended-delay"); + + *gpadc_pdata = gp_data; + + return 0; +} + +static int palmas_gpadc_probe(struct platform_device *pdev) +{ + struct palmas_gpadc *adc; + struct palmas_platform_data *pdata; + struct palmas_gpadc_platform_data *gpadc_pdata = NULL; + struct iio_dev *indio_dev; + int ret, i; + + pdata = dev_get_platdata(pdev->dev.parent); + + if (pdata && pdata->gpadc_pdata) + gpadc_pdata = pdata->gpadc_pdata; + + if (!gpadc_pdata && pdev->dev.of_node) { + ret = palmas_gpadc_get_adc_dt_data(pdev, &gpadc_pdata); + if (ret < 0) + return ret; + } + if (!gpadc_pdata) + return -EINVAL; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!indio_dev) { + dev_err(&pdev->dev, "iio_device_alloc failed\n"); + return -ENOMEM; + } + + adc = iio_priv(indio_dev); + adc->dev = &pdev->dev; + adc->palmas = dev_get_drvdata(pdev->dev.parent); + adc->adc_info = palmas_gpadc_info; + init_completion(&adc->conv_completion); + dev_set_drvdata(&pdev->dev, indio_dev); + + adc->auto_conversion_period = gpadc_pdata->auto_conversion_period_ms; + adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ); + if (adc->irq < 0) { + dev_err(adc->dev, + "get virq failed: %d\n", adc->irq); + ret = adc->irq; + goto out; + } + ret = request_threaded_irq(adc->irq, NULL, + palmas_gpadc_irq, + IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev), + adc); + if (ret < 0) { + dev_err(adc->dev, + "request irq %d failed: %d\n", adc->irq, ret); + goto out; + } + + if (gpadc_pdata->adc_wakeup1_data) { + memcpy(&adc->wakeup1_data, gpadc_pdata->adc_wakeup1_data, + sizeof(adc->wakeup1_data)); + adc->wakeup1_enable = true; + adc->irq_auto_0 = platform_get_irq(pdev, 1); + ret = request_threaded_irq(adc->irq_auto_0, NULL, + palmas_gpadc_irq_auto, + IRQF_ONESHOT | IRQF_EARLY_RESUME, + "palmas-adc-auto-0", adc); + if (ret < 0) { + dev_err(adc->dev, "request auto0 irq %d failed: %d\n", + adc->irq_auto_0, ret); + goto out_irq_free; + } + } + + if (gpadc_pdata->adc_wakeup2_data) { + memcpy(&adc->wakeup2_data, gpadc_pdata->adc_wakeup2_data, + sizeof(adc->wakeup2_data)); + adc->wakeup2_enable = true; + adc->irq_auto_1 = platform_get_irq(pdev, 2); + ret = request_threaded_irq(adc->irq_auto_1, NULL, + palmas_gpadc_irq_auto, + IRQF_ONESHOT | IRQF_EARLY_RESUME, + "palmas-adc-auto-1", adc); + if (ret < 0) { + dev_err(adc->dev, "request auto1 irq %d failed: %d\n", + adc->irq_auto_1, ret); + goto out_irq_auto0_free; + } + } + + /* set the current source 0 (value 0/5/15/20 uA => 0..3) */ + if (gpadc_pdata->ch0_current <= 1) + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0; + else if (gpadc_pdata->ch0_current <= 5) + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5; + else if (gpadc_pdata->ch0_current <= 15) + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15; + else + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20; + + /* set the current source 3 (value 0/10/400/800 uA => 0..3) */ + if (gpadc_pdata->ch3_current <= 1) + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0; + else if (gpadc_pdata->ch3_current <= 10) + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10; + else if (gpadc_pdata->ch3_current <= 400) + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400; + else + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800; + + adc->extended_delay = gpadc_pdata->extended_delay; + + indio_dev->name = MOD_NAME; + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &palmas_gpadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = palmas_gpadc_iio_channel; + indio_dev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(adc->dev, "iio_device_register() failed: %d\n", ret); + goto out_irq_auto1_free; + } + + device_set_wakeup_capable(&pdev->dev, 1); + for (i = 0; i < PALMAS_ADC_CH_MAX; i++) { + if (!(adc->adc_info[i].is_uncalibrated)) + palmas_gpadc_calibrate(adc, i); + } + + if (adc->wakeup1_enable || adc->wakeup2_enable) + device_wakeup_enable(&pdev->dev); + + return 0; + +out_irq_auto1_free: + if (gpadc_pdata->adc_wakeup2_data) + free_irq(adc->irq_auto_1, adc); +out_irq_auto0_free: + if (gpadc_pdata->adc_wakeup1_data) + free_irq(adc->irq_auto_0, adc); +out_irq_free: + free_irq(adc->irq, adc); +out: + return ret; +} + +static int palmas_gpadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(&pdev->dev); + struct palmas_gpadc *adc = iio_priv(indio_dev); + + if (adc->wakeup1_enable || adc->wakeup2_enable) + device_wakeup_disable(&pdev->dev); + iio_device_unregister(indio_dev); + free_irq(adc->irq, adc); + if (adc->wakeup1_enable) + free_irq(adc->irq_auto_0, adc); + if (adc->wakeup2_enable) + free_irq(adc->irq_auto_1, adc); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc) +{ + int adc_period, conv; + int i; + int ch0 = 0, ch1 = 0; + int thres; + int ret; + + adc_period = adc->auto_conversion_period; + for (i = 0; i < 16; ++i) { + if (((1000 * (1 << i)) / 32) < adc_period) + continue; + } + if (i > 0) + i--; + adc_period = i; + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_CTRL, + PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK, + adc_period); + if (ret < 0) { + dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret); + return ret; + } + + conv = 0; + if (adc->wakeup1_enable) { + int polarity; + + ch0 = adc->wakeup1_data.adc_channel_number; + conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN; + if (adc->wakeup1_data.adc_high_threshold > 0) { + thres = adc->wakeup1_data.adc_high_threshold; + polarity = 0; + } else { + thres = adc->wakeup1_data.adc_low_threshold; + polarity = PALMAS_GPADC_THRES_CONV0_MSB_THRES_CONV0_POL; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV0_LSB write failed: %d\n", ret); + return ret; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV0_MSB, + ((thres >> 8) & 0xF) | polarity); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV0_MSB write failed: %d\n", ret); + return ret; + } + } + + if (adc->wakeup2_enable) { + int polarity; + + ch1 = adc->wakeup2_data.adc_channel_number; + conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN; + if (adc->wakeup2_data.adc_high_threshold > 0) { + thres = adc->wakeup2_data.adc_high_threshold; + polarity = 0; + } else { + thres = adc->wakeup2_data.adc_low_threshold; + polarity = PALMAS_GPADC_THRES_CONV1_MSB_THRES_CONV1_POL; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV1_LSB write failed: %d\n", ret); + return ret; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV1_MSB, + ((thres >> 8) & 0xF) | polarity); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV1_MSB write failed: %d\n", ret); + return ret; + } + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0); + if (ret < 0) { + dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret); + return ret; + } + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_CTRL, + PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN | + PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv); + if (ret < 0) + dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret); + + return ret; +} + +static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc) +{ + int ret; + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_SELECT, 0); + if (ret < 0) { + dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret); + return ret; + } + + ret = palmas_disable_auto_conversion(adc); + if (ret < 0) + dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret); + + return ret; +} + +static int palmas_gpadc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct palmas_gpadc *adc = iio_priv(indio_dev); + int wakeup = adc->wakeup1_enable || adc->wakeup2_enable; + int ret; + + if (!device_may_wakeup(dev) || !wakeup) + return 0; + + ret = palmas_adc_wakeup_configure(adc); + if (ret < 0) + return ret; + + if (adc->wakeup1_enable) + enable_irq_wake(adc->irq_auto_0); + + if (adc->wakeup2_enable) + enable_irq_wake(adc->irq_auto_1); + + return 0; +} + +static int palmas_gpadc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct palmas_gpadc *adc = iio_priv(indio_dev); + int wakeup = adc->wakeup1_enable || adc->wakeup2_enable; + int ret; + + if (!device_may_wakeup(dev) || !wakeup) + return 0; + + ret = palmas_adc_wakeup_reset(adc); + if (ret < 0) + return ret; + + if (adc->wakeup1_enable) + disable_irq_wake(adc->irq_auto_0); + + if (adc->wakeup2_enable) + disable_irq_wake(adc->irq_auto_1); + + return 0; +}; +#endif + +static const struct dev_pm_ops palmas_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend, + palmas_gpadc_resume) +}; + +static const struct of_device_id of_palmas_gpadc_match_tbl[] = { + { .compatible = "ti,palmas-gpadc", }, + { /* end */ } +}; +MODULE_DEVICE_TABLE(of, of_palmas_gpadc_match_tbl); + +static struct platform_driver palmas_gpadc_driver = { + .probe = palmas_gpadc_probe, + .remove = palmas_gpadc_remove, + .driver = { + .name = MOD_NAME, + .pm = &palmas_pm_ops, + .of_match_table = of_palmas_gpadc_match_tbl, + }, +}; + +static int __init palmas_gpadc_init(void) +{ + return platform_driver_register(&palmas_gpadc_driver); +} +module_init(palmas_gpadc_init); + +static void __exit palmas_gpadc_exit(void) +{ + platform_driver_unregister(&palmas_gpadc_driver); +} +module_exit(palmas_gpadc_exit); + +MODULE_DESCRIPTION("palmas GPADC driver"); +MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>"); +MODULE_ALIAS("platform:palmas-gpadc"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c index ff6f7f63c..bc58867d6 100644 --- a/drivers/iio/adc/ti-adc128s052.c +++ b/drivers/iio/adc/ti-adc128s052.c @@ -1,10 +1,11 @@ /* * Copyright (C) 2014 Angelo Compagnucci <angelo.compagnucci@gmail.com> * - * Driver for Texas Instruments' ADC128S052 and ADC122S021 ADC chip. + * Driver for Texas Instruments' ADC128S052, ADC122S021 and ADC124S021 ADC chip. * Datasheets can be found here: * http://www.ti.com/lit/ds/symlink/adc128s052.pdf * http://www.ti.com/lit/ds/symlink/adc122s021.pdf + * http://www.ti.com/lit/ds/symlink/adc124s021.pdf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -114,9 +115,17 @@ static const struct iio_chan_spec adc122s021_channels[] = { ADC128_VOLTAGE_CHANNEL(1), }; +static const struct iio_chan_spec adc124s021_channels[] = { + ADC128_VOLTAGE_CHANNEL(0), + ADC128_VOLTAGE_CHANNEL(1), + ADC128_VOLTAGE_CHANNEL(2), + ADC128_VOLTAGE_CHANNEL(3), +}; + static const struct adc128_configuration adc128_config[] = { { adc128s052_channels, ARRAY_SIZE(adc128s052_channels) }, { adc122s021_channels, ARRAY_SIZE(adc122s021_channels) }, + { adc124s021_channels, ARRAY_SIZE(adc124s021_channels) }, }; static const struct iio_info adc128_info = { @@ -177,6 +186,7 @@ static int adc128_remove(struct spi_device *spi) static const struct of_device_id adc128_of_match[] = { { .compatible = "ti,adc128s052", }, { .compatible = "ti,adc122s021", }, + { .compatible = "ti,adc124s021", }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, adc128_of_match); @@ -184,6 +194,7 @@ MODULE_DEVICE_TABLE(of, adc128_of_match); static const struct spi_device_id adc128_id[] = { { "adc128s052", 0}, /* index into adc128_config */ { "adc122s021", 1}, + { "adc124s021", 2}, { } }; MODULE_DEVICE_TABLE(spi, adc128_id); diff --git a/drivers/iio/adc/ti-ads8688.c b/drivers/iio/adc/ti-ads8688.c new file mode 100644 index 000000000..03e907028 --- /dev/null +++ b/drivers/iio/adc/ti-ads8688.c @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2015 Prevas A/S + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define ADS8688_CMD_REG(x) (x << 8) +#define ADS8688_CMD_REG_NOOP 0x00 +#define ADS8688_CMD_REG_RST 0x85 +#define ADS8688_CMD_REG_MAN_CH(chan) (0xC0 | (4 * chan)) +#define ADS8688_CMD_DONT_CARE_BITS 16 + +#define ADS8688_PROG_REG(x) (x << 9) +#define ADS8688_PROG_REG_RANGE_CH(chan) (0x05 + chan) +#define ADS8688_PROG_WR_BIT BIT(8) +#define ADS8688_PROG_DONT_CARE_BITS 8 + +#define ADS8688_REG_PLUSMINUS25VREF 0 +#define ADS8688_REG_PLUSMINUS125VREF 1 +#define ADS8688_REG_PLUSMINUS0625VREF 2 +#define ADS8688_REG_PLUS25VREF 5 +#define ADS8688_REG_PLUS125VREF 6 + +#define ADS8688_VREF_MV 4096 +#define ADS8688_REALBITS 16 + +/* + * enum ads8688_range - ADS8688 reference voltage range + * @ADS8688_PLUSMINUS25VREF: Device is configured for input range ±2.5 * VREF + * @ADS8688_PLUSMINUS125VREF: Device is configured for input range ±1.25 * VREF + * @ADS8688_PLUSMINUS0625VREF: Device is configured for input range ±0.625 * VREF + * @ADS8688_PLUS25VREF: Device is configured for input range 0 - 2.5 * VREF + * @ADS8688_PLUS125VREF: Device is configured for input range 0 - 1.25 * VREF + */ +enum ads8688_range { + ADS8688_PLUSMINUS25VREF, + ADS8688_PLUSMINUS125VREF, + ADS8688_PLUSMINUS0625VREF, + ADS8688_PLUS25VREF, + ADS8688_PLUS125VREF, +}; + +struct ads8688_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct ads8688_state { + struct mutex lock; + const struct ads8688_chip_info *chip_info; + struct spi_device *spi; + struct regulator *reg; + unsigned int vref_mv; + enum ads8688_range range[8]; + union { + __be32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +enum ads8688_id { + ID_ADS8684, + ID_ADS8688, +}; + +struct ads8688_ranges { + enum ads8688_range range; + unsigned int scale; + int offset; + u8 reg; +}; + +static const struct ads8688_ranges ads8688_range_def[5] = { + { + .range = ADS8688_PLUSMINUS25VREF, + .scale = 76295, + .offset = -(1 << (ADS8688_REALBITS - 1)), + .reg = ADS8688_REG_PLUSMINUS25VREF, + }, { + .range = ADS8688_PLUSMINUS125VREF, + .scale = 38148, + .offset = -(1 << (ADS8688_REALBITS - 1)), + .reg = ADS8688_REG_PLUSMINUS125VREF, + }, { + .range = ADS8688_PLUSMINUS0625VREF, + .scale = 19074, + .offset = -(1 << (ADS8688_REALBITS - 1)), + .reg = ADS8688_REG_PLUSMINUS0625VREF, + }, { + .range = ADS8688_PLUS25VREF, + .scale = 38148, + .offset = 0, + .reg = ADS8688_REG_PLUS25VREF, + }, { + .range = ADS8688_PLUS125VREF, + .scale = 19074, + .offset = 0, + .reg = ADS8688_REG_PLUS125VREF, + } +}; + +static ssize_t ads8688_show_scales(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ads8688_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "0.%09u 0.%09u 0.%09u\n", + ads8688_range_def[0].scale * st->vref_mv, + ads8688_range_def[1].scale * st->vref_mv, + ads8688_range_def[2].scale * st->vref_mv); +} + +static ssize_t ads8688_show_offsets(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d %d\n", ads8688_range_def[0].offset, + ads8688_range_def[3].offset); +} + +static IIO_DEVICE_ATTR(in_voltage_scale_available, S_IRUGO, + ads8688_show_scales, NULL, 0); +static IIO_DEVICE_ATTR(in_voltage_offset_available, S_IRUGO, + ads8688_show_offsets, NULL, 0); + +static struct attribute *ads8688_attributes[] = { + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage_offset_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ads8688_attribute_group = { + .attrs = ads8688_attributes, +}; + +#define ADS8688_CHAN(index) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ +} + +static const struct iio_chan_spec ads8684_channels[] = { + ADS8688_CHAN(0), + ADS8688_CHAN(1), + ADS8688_CHAN(2), + ADS8688_CHAN(3), +}; + +static const struct iio_chan_spec ads8688_channels[] = { + ADS8688_CHAN(0), + ADS8688_CHAN(1), + ADS8688_CHAN(2), + ADS8688_CHAN(3), + ADS8688_CHAN(4), + ADS8688_CHAN(5), + ADS8688_CHAN(6), + ADS8688_CHAN(7), +}; + +static int ads8688_prog_write(struct iio_dev *indio_dev, unsigned int addr, + unsigned int val) +{ + struct ads8688_state *st = iio_priv(indio_dev); + u32 tmp; + + tmp = ADS8688_PROG_REG(addr) | ADS8688_PROG_WR_BIT | val; + tmp <<= ADS8688_PROG_DONT_CARE_BITS; + st->data[0].d32 = cpu_to_be32(tmp); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ads8688_reset(struct iio_dev *indio_dev) +{ + struct ads8688_state *st = iio_priv(indio_dev); + u32 tmp; + + tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_RST); + tmp <<= ADS8688_CMD_DONT_CARE_BITS; + st->data[0].d32 = cpu_to_be32(tmp); + + return spi_write(st->spi, &st->data[0].d8[0], 4); +} + +static int ads8688_read(struct iio_dev *indio_dev, unsigned int chan) +{ + struct ads8688_state *st = iio_priv(indio_dev); + int ret; + u32 tmp; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[0], + .len = 4, + .cs_change = 1, + }, { + .tx_buf = &st->data[1].d8[0], + .rx_buf = &st->data[1].d8[0], + .len = 4, + }, + }; + + tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_MAN_CH(chan)); + tmp <<= ADS8688_CMD_DONT_CARE_BITS; + st->data[0].d32 = cpu_to_be32(tmp); + + tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_NOOP); + tmp <<= ADS8688_CMD_DONT_CARE_BITS; + st->data[1].d32 = cpu_to_be32(tmp); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + return be32_to_cpu(st->data[1].d32) & 0xffff; +} + +static int ads8688_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + int ret, offset; + unsigned long scale_mv; + + struct ads8688_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ads8688_read(indio_dev, chan->channel); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_mv = st->vref_mv; + scale_mv *= ads8688_range_def[st->range[chan->channel]].scale; + *val = 0; + *val2 = scale_mv; + mutex_unlock(&st->lock); + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + offset = ads8688_range_def[st->range[chan->channel]].offset; + *val = offset; + mutex_unlock(&st->lock); + return IIO_VAL_INT; + } + mutex_unlock(&st->lock); + + return -EINVAL; +} + +static int ads8688_write_reg_range(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + enum ads8688_range range) +{ + unsigned int tmp; + int ret; + + tmp = ADS8688_PROG_REG_RANGE_CH(chan->channel); + ret = ads8688_prog_write(indio_dev, tmp, range); + + return ret; +} + +static int ads8688_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ads8688_state *st = iio_priv(indio_dev); + unsigned int scale = 0; + int ret = -EINVAL, i, offset = 0; + + mutex_lock(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + /* If the offset is 0 the ±2.5 * VREF mode is not available */ + offset = ads8688_range_def[st->range[chan->channel]].offset; + if (offset == 0 && val2 == ads8688_range_def[0].scale * st->vref_mv) { + mutex_unlock(&st->lock); + return -EINVAL; + } + + /* Lookup new mode */ + for (i = 0; i < ARRAY_SIZE(ads8688_range_def); i++) + if (val2 == ads8688_range_def[i].scale * st->vref_mv && + offset == ads8688_range_def[i].offset) { + ret = ads8688_write_reg_range(indio_dev, chan, + ads8688_range_def[i].reg); + break; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* + * There are only two available offsets: + * 0 and -(1 << (ADS8688_REALBITS - 1)) + */ + if (!(ads8688_range_def[0].offset == val || + ads8688_range_def[3].offset == val)) { + mutex_unlock(&st->lock); + return -EINVAL; + } + + /* + * If the device are in ±2.5 * VREF mode, it's not allowed to + * switch to a mode where the offset is 0 + */ + if (val == 0 && + st->range[chan->channel] == ADS8688_PLUSMINUS25VREF) { + mutex_unlock(&st->lock); + return -EINVAL; + } + + scale = ads8688_range_def[st->range[chan->channel]].scale; + + /* Lookup new mode */ + for (i = 0; i < ARRAY_SIZE(ads8688_range_def); i++) + if (val == ads8688_range_def[i].offset && + scale == ads8688_range_def[i].scale) { + ret = ads8688_write_reg_range(indio_dev, chan, + ads8688_range_def[i].reg); + break; + } + break; + } + + if (!ret) + st->range[chan->channel] = ads8688_range_def[i].range; + + mutex_unlock(&st->lock); + + return ret; +} + +static int ads8688_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info ads8688_info = { + .read_raw = &ads8688_read_raw, + .write_raw = &ads8688_write_raw, + .write_raw_get_fmt = &ads8688_write_raw_get_fmt, + .attrs = &ads8688_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const struct ads8688_chip_info ads8688_chip_info_tbl[] = { + [ID_ADS8684] = { + .channels = ads8684_channels, + .num_channels = ARRAY_SIZE(ads8684_channels), + }, + [ID_ADS8688] = { + .channels = ads8688_channels, + .num_channels = ARRAY_SIZE(ads8688_channels), + }, +}; + +static int ads8688_probe(struct spi_device *spi) +{ + struct ads8688_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get_optional(&spi->dev, "vref"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = regulator_get_voltage(st->reg); + if (ret < 0) + goto error_out; + + st->vref_mv = ret / 1000; + } else { + /* Use internal reference */ + st->vref_mv = ADS8688_VREF_MV; + } + + st->chip_info = &ads8688_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + spi->mode = SPI_MODE_1; + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = &ads8688_info; + + ads8688_reset(indio_dev); + + mutex_init(&st->lock); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_out; + + return 0; + +error_out: + if (!IS_ERR_OR_NULL(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ads8688_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ads8688_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (!IS_ERR_OR_NULL(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ads8688_id[] = { + {"ads8684", ID_ADS8684}, + {"ads8688", ID_ADS8688}, + {} +}; +MODULE_DEVICE_TABLE(spi, ads8688_id); + +static const struct of_device_id ads8688_of_match[] = { + { .compatible = "ti,ads8684" }, + { .compatible = "ti,ads8688" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads8688_of_match); + +static struct spi_driver ads8688_driver = { + .driver = { + .name = "ads8688", + .owner = THIS_MODULE, + }, + .probe = ads8688_probe, + .remove = ads8688_remove, + .id_table = ads8688_id, +}; +module_spi_driver(ads8688_driver); + +MODULE_AUTHOR("Sean Nyekjaer <sean.nyekjaer@prevas.dk>"); +MODULE_DESCRIPTION("Texas Instruments ADS8688 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/xilinx-xadc-core.c b/drivers/iio/adc/xilinx-xadc-core.c index 02e636a1c..0a6beb3d9 100644 --- a/drivers/iio/adc/xilinx-xadc-core.c +++ b/drivers/iio/adc/xilinx-xadc-core.c @@ -803,7 +803,7 @@ err: return ret; } -static struct iio_buffer_setup_ops xadc_buffer_ops = { +static const struct iio_buffer_setup_ops xadc_buffer_ops = { .preenable = &xadc_preenable, .postenable = &iio_triggered_buffer_postenable, .predisable = &iio_triggered_buffer_predisable, diff --git a/drivers/iio/buffer/Kconfig b/drivers/iio/buffer/Kconfig index 0a7b2fd36..4ffd3db78 100644 --- a/drivers/iio/buffer/Kconfig +++ b/drivers/iio/buffer/Kconfig @@ -9,6 +9,26 @@ config IIO_BUFFER_CB Should be selected by any drivers that do in-kernel push usage. That is, those where the data is pushed to the consumer. +config IIO_BUFFER_DMA + tristate + help + Provides the generic IIO DMA buffer infrastructure that can be used by + drivers for devices with DMA support to implement the IIO buffer. + + Should be selected by drivers that want to use the generic DMA buffer + infrastructure. + +config IIO_BUFFER_DMAENGINE + tristate + select IIO_BUFFER_DMA + help + Provides a bonding of the generic IIO DMA buffer infrastructure with the + DMAengine framework. This can be used by converter drivers with a DMA port + connected to an external DMA controller which is supported by the + DMAengine framework. + + Should be selected by drivers that want to use this functionality. + config IIO_KFIFO_BUF tristate "Industrial I/O buffering based on kfifo" help diff --git a/drivers/iio/buffer/Makefile b/drivers/iio/buffer/Makefile index 4d193b9a9..85beaae83 100644 --- a/drivers/iio/buffer/Makefile +++ b/drivers/iio/buffer/Makefile @@ -4,5 +4,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_IIO_BUFFER_CB) += industrialio-buffer-cb.o +obj-$(CONFIG_IIO_BUFFER_DMA) += industrialio-buffer-dma.o +obj-$(CONFIG_IIO_BUFFER_DMAENGINE) += industrialio-buffer-dmaengine.o obj-$(CONFIG_IIO_TRIGGERED_BUFFER) += industrialio-triggered-buffer.o obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c new file mode 100644 index 000000000..212cbedc7 --- /dev/null +++ b/drivers/iio/buffer/industrialio-buffer-dma.c @@ -0,0 +1,683 @@ +/* + * Copyright 2013-2015 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2. + */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/poll.h> +#include <linux/iio/buffer.h> +#include <linux/iio/buffer-dma.h> +#include <linux/dma-mapping.h> +#include <linux/sizes.h> + +/* + * For DMA buffers the storage is sub-divided into so called blocks. Each block + * has its own memory buffer. The size of the block is the granularity at which + * memory is exchanged between the hardware and the application. Increasing the + * basic unit of data exchange from one sample to one block decreases the + * management overhead that is associated with each sample. E.g. if we say the + * management overhead for one exchange is x and the unit of exchange is one + * sample the overhead will be x for each sample. Whereas when using a block + * which contains n samples the overhead per sample is reduced to x/n. This + * allows to achieve much higher samplerates than what can be sustained with + * the one sample approach. + * + * Blocks are exchanged between the DMA controller and the application via the + * means of two queues. The incoming queue and the outgoing queue. Blocks on the + * incoming queue are waiting for the DMA controller to pick them up and fill + * them with data. Block on the outgoing queue have been filled with data and + * are waiting for the application to dequeue them and read the data. + * + * A block can be in one of the following states: + * * Owned by the application. In this state the application can read data from + * the block. + * * On the incoming list: Blocks on the incoming list are queued up to be + * processed by the DMA controller. + * * Owned by the DMA controller: The DMA controller is processing the block + * and filling it with data. + * * On the outgoing list: Blocks on the outgoing list have been successfully + * processed by the DMA controller and contain data. They can be dequeued by + * the application. + * * Dead: A block that is dead has been marked as to be freed. It might still + * be owned by either the application or the DMA controller at the moment. + * But once they are done processing it instead of going to either the + * incoming or outgoing queue the block will be freed. + * + * In addition to this blocks are reference counted and the memory associated + * with both the block structure as well as the storage memory for the block + * will be freed when the last reference to the block is dropped. This means a + * block must not be accessed without holding a reference. + * + * The iio_dma_buffer implementation provides a generic infrastructure for + * managing the blocks. + * + * A driver for a specific piece of hardware that has DMA capabilities need to + * implement the submit() callback from the iio_dma_buffer_ops structure. This + * callback is supposed to initiate the DMA transfer copying data from the + * converter to the memory region of the block. Once the DMA transfer has been + * completed the driver must call iio_dma_buffer_block_done() for the completed + * block. + * + * Prior to this it must set the bytes_used field of the block contains + * the actual number of bytes in the buffer. Typically this will be equal to the + * size of the block, but if the DMA hardware has certain alignment requirements + * for the transfer length it might choose to use less than the full size. In + * either case it is expected that bytes_used is a multiple of the bytes per + * datum, i.e. the block must not contain partial samples. + * + * The driver must call iio_dma_buffer_block_done() for each block it has + * received through its submit_block() callback, even if it does not actually + * perform a DMA transfer for the block, e.g. because the buffer was disabled + * before the block transfer was started. In this case it should set bytes_used + * to 0. + * + * In addition it is recommended that a driver implements the abort() callback. + * It will be called when the buffer is disabled and can be used to cancel + * pending and stop active transfers. + * + * The specific driver implementation should use the default callback + * implementations provided by this module for the iio_buffer_access_funcs + * struct. It may overload some callbacks with custom variants if the hardware + * has special requirements that are not handled by the generic functions. If a + * driver chooses to overload a callback it has to ensure that the generic + * callback is called from within the custom callback. + */ + +static void iio_buffer_block_release(struct kref *kref) +{ + struct iio_dma_buffer_block *block = container_of(kref, + struct iio_dma_buffer_block, kref); + + WARN_ON(block->state != IIO_BLOCK_STATE_DEAD); + + dma_free_coherent(block->queue->dev, PAGE_ALIGN(block->size), + block->vaddr, block->phys_addr); + + iio_buffer_put(&block->queue->buffer); + kfree(block); +} + +static void iio_buffer_block_get(struct iio_dma_buffer_block *block) +{ + kref_get(&block->kref); +} + +static void iio_buffer_block_put(struct iio_dma_buffer_block *block) +{ + kref_put(&block->kref, iio_buffer_block_release); +} + +/* + * dma_free_coherent can sleep, hence we need to take some special care to be + * able to drop a reference from an atomic context. + */ +static LIST_HEAD(iio_dma_buffer_dead_blocks); +static DEFINE_SPINLOCK(iio_dma_buffer_dead_blocks_lock); + +static void iio_dma_buffer_cleanup_worker(struct work_struct *work) +{ + struct iio_dma_buffer_block *block, *_block; + LIST_HEAD(block_list); + + spin_lock_irq(&iio_dma_buffer_dead_blocks_lock); + list_splice_tail_init(&iio_dma_buffer_dead_blocks, &block_list); + spin_unlock_irq(&iio_dma_buffer_dead_blocks_lock); + + list_for_each_entry_safe(block, _block, &block_list, head) + iio_buffer_block_release(&block->kref); +} +static DECLARE_WORK(iio_dma_buffer_cleanup_work, iio_dma_buffer_cleanup_worker); + +static void iio_buffer_block_release_atomic(struct kref *kref) +{ + struct iio_dma_buffer_block *block; + unsigned long flags; + + block = container_of(kref, struct iio_dma_buffer_block, kref); + + spin_lock_irqsave(&iio_dma_buffer_dead_blocks_lock, flags); + list_add_tail(&block->head, &iio_dma_buffer_dead_blocks); + spin_unlock_irqrestore(&iio_dma_buffer_dead_blocks_lock, flags); + + schedule_work(&iio_dma_buffer_cleanup_work); +} + +/* + * Version of iio_buffer_block_put() that can be called from atomic context + */ +static void iio_buffer_block_put_atomic(struct iio_dma_buffer_block *block) +{ + kref_put(&block->kref, iio_buffer_block_release_atomic); +} + +static struct iio_dma_buffer_queue *iio_buffer_to_queue(struct iio_buffer *buf) +{ + return container_of(buf, struct iio_dma_buffer_queue, buffer); +} + +static struct iio_dma_buffer_block *iio_dma_buffer_alloc_block( + struct iio_dma_buffer_queue *queue, size_t size) +{ + struct iio_dma_buffer_block *block; + + block = kzalloc(sizeof(*block), GFP_KERNEL); + if (!block) + return NULL; + + block->vaddr = dma_alloc_coherent(queue->dev, PAGE_ALIGN(size), + &block->phys_addr, GFP_KERNEL); + if (!block->vaddr) { + kfree(block); + return NULL; + } + + block->size = size; + block->state = IIO_BLOCK_STATE_DEQUEUED; + block->queue = queue; + INIT_LIST_HEAD(&block->head); + kref_init(&block->kref); + + iio_buffer_get(&queue->buffer); + + return block; +} + +static void _iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) +{ + struct iio_dma_buffer_queue *queue = block->queue; + + /* + * The buffer has already been freed by the application, just drop the + * reference. + */ + if (block->state != IIO_BLOCK_STATE_DEAD) { + block->state = IIO_BLOCK_STATE_DONE; + list_add_tail(&block->head, &queue->outgoing); + } +} + +/** + * iio_dma_buffer_block_done() - Indicate that a block has been completed + * @block: The completed block + * + * Should be called when the DMA controller has finished handling the block to + * pass back ownership of the block to the queue. + */ +void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) +{ + struct iio_dma_buffer_queue *queue = block->queue; + unsigned long flags; + + spin_lock_irqsave(&queue->list_lock, flags); + _iio_dma_buffer_block_done(block); + spin_unlock_irqrestore(&queue->list_lock, flags); + + iio_buffer_block_put_atomic(block); + wake_up_interruptible_poll(&queue->buffer.pollq, POLLIN | POLLRDNORM); +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_block_done); + +/** + * iio_dma_buffer_block_list_abort() - Indicate that a list block has been + * aborted + * @queue: Queue for which to complete blocks. + * @list: List of aborted blocks. All blocks in this list must be from @queue. + * + * Typically called from the abort() callback after the DMA controller has been + * stopped. This will set bytes_used to 0 for each block in the list and then + * hand the blocks back to the queue. + */ +void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue, + struct list_head *list) +{ + struct iio_dma_buffer_block *block, *_block; + unsigned long flags; + + spin_lock_irqsave(&queue->list_lock, flags); + list_for_each_entry_safe(block, _block, list, head) { + list_del(&block->head); + block->bytes_used = 0; + _iio_dma_buffer_block_done(block); + iio_buffer_block_put_atomic(block); + } + spin_unlock_irqrestore(&queue->list_lock, flags); + + wake_up_interruptible_poll(&queue->buffer.pollq, POLLIN | POLLRDNORM); +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_block_list_abort); + +static bool iio_dma_block_reusable(struct iio_dma_buffer_block *block) +{ + /* + * If the core owns the block it can be re-used. This should be the + * default case when enabling the buffer, unless the DMA controller does + * not support abort and has not given back the block yet. + */ + switch (block->state) { + case IIO_BLOCK_STATE_DEQUEUED: + case IIO_BLOCK_STATE_QUEUED: + case IIO_BLOCK_STATE_DONE: + return true; + default: + return false; + } +} + +/** + * iio_dma_buffer_request_update() - DMA buffer request_update callback + * @buffer: The buffer which to request an update + * + * Should be used as the iio_dma_buffer_request_update() callback for + * iio_buffer_access_ops struct for DMA buffers. + */ +int iio_dma_buffer_request_update(struct iio_buffer *buffer) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + struct iio_dma_buffer_block *block; + bool try_reuse = false; + size_t size; + int ret = 0; + int i; + + /* + * Split the buffer into two even parts. This is used as a double + * buffering scheme with usually one block at a time being used by the + * DMA and the other one by the application. + */ + size = DIV_ROUND_UP(queue->buffer.bytes_per_datum * + queue->buffer.length, 2); + + mutex_lock(&queue->lock); + + /* Allocations are page aligned */ + if (PAGE_ALIGN(queue->fileio.block_size) == PAGE_ALIGN(size)) + try_reuse = true; + + queue->fileio.block_size = size; + queue->fileio.active_block = NULL; + + spin_lock_irq(&queue->list_lock); + for (i = 0; i < 2; i++) { + block = queue->fileio.blocks[i]; + + /* If we can't re-use it free it */ + if (block && (!iio_dma_block_reusable(block) || !try_reuse)) + block->state = IIO_BLOCK_STATE_DEAD; + } + + /* + * At this point all blocks are either owned by the core or marked as + * dead. This means we can reset the lists without having to fear + * corrution. + */ + INIT_LIST_HEAD(&queue->outgoing); + spin_unlock_irq(&queue->list_lock); + + INIT_LIST_HEAD(&queue->incoming); + + for (i = 0; i < 2; i++) { + if (queue->fileio.blocks[i]) { + block = queue->fileio.blocks[i]; + if (block->state == IIO_BLOCK_STATE_DEAD) { + /* Could not reuse it */ + iio_buffer_block_put(block); + block = NULL; + } else { + block->size = size; + } + } else { + block = NULL; + } + + if (!block) { + block = iio_dma_buffer_alloc_block(queue, size); + if (!block) { + ret = -ENOMEM; + goto out_unlock; + } + queue->fileio.blocks[i] = block; + } + + block->state = IIO_BLOCK_STATE_QUEUED; + list_add_tail(&block->head, &queue->incoming); + } + +out_unlock: + mutex_unlock(&queue->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_request_update); + +static void iio_dma_buffer_submit_block(struct iio_dma_buffer_queue *queue, + struct iio_dma_buffer_block *block) +{ + int ret; + + /* + * If the hardware has already been removed we put the block into + * limbo. It will neither be on the incoming nor outgoing list, nor will + * it ever complete. It will just wait to be freed eventually. + */ + if (!queue->ops) + return; + + block->state = IIO_BLOCK_STATE_ACTIVE; + iio_buffer_block_get(block); + ret = queue->ops->submit(queue, block); + if (ret) { + /* + * This is a bit of a problem and there is not much we can do + * other then wait for the buffer to be disabled and re-enabled + * and try again. But it should not really happen unless we run + * out of memory or something similar. + * + * TODO: Implement support in the IIO core to allow buffers to + * notify consumers that something went wrong and the buffer + * should be disabled. + */ + iio_buffer_block_put(block); + } +} + +/** + * iio_dma_buffer_enable() - Enable DMA buffer + * @buffer: IIO buffer to enable + * @indio_dev: IIO device the buffer is attached to + * + * Needs to be called when the device that the buffer is attached to starts + * sampling. Typically should be the iio_buffer_access_ops enable callback. + * + * This will allocate the DMA buffers and start the DMA transfers. + */ +int iio_dma_buffer_enable(struct iio_buffer *buffer, + struct iio_dev *indio_dev) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + struct iio_dma_buffer_block *block, *_block; + + mutex_lock(&queue->lock); + queue->active = true; + list_for_each_entry_safe(block, _block, &queue->incoming, head) { + list_del(&block->head); + iio_dma_buffer_submit_block(queue, block); + } + mutex_unlock(&queue->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_enable); + +/** + * iio_dma_buffer_disable() - Disable DMA buffer + * @buffer: IIO DMA buffer to disable + * @indio_dev: IIO device the buffer is attached to + * + * Needs to be called when the device that the buffer is attached to stops + * sampling. Typically should be the iio_buffer_access_ops disable callback. + */ +int iio_dma_buffer_disable(struct iio_buffer *buffer, + struct iio_dev *indio_dev) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + + mutex_lock(&queue->lock); + queue->active = false; + + if (queue->ops && queue->ops->abort) + queue->ops->abort(queue); + mutex_unlock(&queue->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_disable); + +static void iio_dma_buffer_enqueue(struct iio_dma_buffer_queue *queue, + struct iio_dma_buffer_block *block) +{ + if (block->state == IIO_BLOCK_STATE_DEAD) { + iio_buffer_block_put(block); + } else if (queue->active) { + iio_dma_buffer_submit_block(queue, block); + } else { + block->state = IIO_BLOCK_STATE_QUEUED; + list_add_tail(&block->head, &queue->incoming); + } +} + +static struct iio_dma_buffer_block *iio_dma_buffer_dequeue( + struct iio_dma_buffer_queue *queue) +{ + struct iio_dma_buffer_block *block; + + spin_lock_irq(&queue->list_lock); + block = list_first_entry_or_null(&queue->outgoing, struct + iio_dma_buffer_block, head); + if (block != NULL) { + list_del(&block->head); + block->state = IIO_BLOCK_STATE_DEQUEUED; + } + spin_unlock_irq(&queue->list_lock); + + return block; +} + +/** + * iio_dma_buffer_read() - DMA buffer read callback + * @buffer: Buffer to read form + * @n: Number of bytes to read + * @user_buffer: Userspace buffer to copy the data to + * + * Should be used as the read_first_n callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, + char __user *user_buffer) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + struct iio_dma_buffer_block *block; + int ret; + + if (n < buffer->bytes_per_datum) + return -EINVAL; + + mutex_lock(&queue->lock); + + if (!queue->fileio.active_block) { + block = iio_dma_buffer_dequeue(queue); + if (block == NULL) { + ret = 0; + goto out_unlock; + } + queue->fileio.pos = 0; + queue->fileio.active_block = block; + } else { + block = queue->fileio.active_block; + } + + n = rounddown(n, buffer->bytes_per_datum); + if (n > block->bytes_used - queue->fileio.pos) + n = block->bytes_used - queue->fileio.pos; + + if (copy_to_user(user_buffer, block->vaddr + queue->fileio.pos, n)) { + ret = -EFAULT; + goto out_unlock; + } + + queue->fileio.pos += n; + + if (queue->fileio.pos == block->bytes_used) { + queue->fileio.active_block = NULL; + iio_dma_buffer_enqueue(queue, block); + } + + ret = n; + +out_unlock: + mutex_unlock(&queue->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_read); + +/** + * iio_dma_buffer_data_available() - DMA buffer data_available callback + * @buf: Buffer to check for data availability + * + * Should be used as the data_available callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +size_t iio_dma_buffer_data_available(struct iio_buffer *buf) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buf); + struct iio_dma_buffer_block *block; + size_t data_available = 0; + + /* + * For counting the available bytes we'll use the size of the block not + * the number of actual bytes available in the block. Otherwise it is + * possible that we end up with a value that is lower than the watermark + * but won't increase since all blocks are in use. + */ + + mutex_lock(&queue->lock); + if (queue->fileio.active_block) + data_available += queue->fileio.active_block->size; + + spin_lock_irq(&queue->list_lock); + list_for_each_entry(block, &queue->outgoing, head) + data_available += block->size; + spin_unlock_irq(&queue->list_lock); + mutex_unlock(&queue->lock); + + return data_available; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_data_available); + +/** + * iio_dma_buffer_set_bytes_per_datum() - DMA buffer set_bytes_per_datum callback + * @buffer: Buffer to set the bytes-per-datum for + * @bpd: The new bytes-per-datum value + * + * Should be used as the set_bytes_per_datum callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd) +{ + buffer->bytes_per_datum = bpd; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_set_bytes_per_datum); + +/** + * iio_dma_buffer_set_length - DMA buffer set_length callback + * @buffer: Buffer to set the length for + * @length: The new buffer length + * + * Should be used as the set_length callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_set_length(struct iio_buffer *buffer, int length) +{ + /* Avoid an invalid state */ + if (length < 2) + length = 2; + buffer->length = length; + buffer->watermark = length / 2; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_set_length); + +/** + * iio_dma_buffer_init() - Initialize DMA buffer queue + * @queue: Buffer to initialize + * @dev: DMA device + * @ops: DMA buffer queue callback operations + * + * The DMA device will be used by the queue to do DMA memory allocations. So it + * should refer to the device that will perform the DMA to ensure that + * allocations are done from a memory region that can be accessed by the device. + */ +int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, + struct device *dev, const struct iio_dma_buffer_ops *ops) +{ + iio_buffer_init(&queue->buffer); + queue->buffer.length = PAGE_SIZE; + queue->buffer.watermark = queue->buffer.length / 2; + queue->dev = dev; + queue->ops = ops; + + INIT_LIST_HEAD(&queue->incoming); + INIT_LIST_HEAD(&queue->outgoing); + + mutex_init(&queue->lock); + spin_lock_init(&queue->list_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_init); + +/** + * iio_dma_buffer_exit() - Cleanup DMA buffer queue + * @queue: Buffer to cleanup + * + * After this function has completed it is safe to free any resources that are + * associated with the buffer and are accessed inside the callback operations. + */ +void iio_dma_buffer_exit(struct iio_dma_buffer_queue *queue) +{ + unsigned int i; + + mutex_lock(&queue->lock); + + spin_lock_irq(&queue->list_lock); + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + if (!queue->fileio.blocks[i]) + continue; + queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD; + } + INIT_LIST_HEAD(&queue->outgoing); + spin_unlock_irq(&queue->list_lock); + + INIT_LIST_HEAD(&queue->incoming); + + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + if (!queue->fileio.blocks[i]) + continue; + iio_buffer_block_put(queue->fileio.blocks[i]); + queue->fileio.blocks[i] = NULL; + } + queue->fileio.active_block = NULL; + queue->ops = NULL; + + mutex_unlock(&queue->lock); +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_exit); + +/** + * iio_dma_buffer_release() - Release final buffer resources + * @queue: Buffer to release + * + * Frees resources that can't yet be freed in iio_dma_buffer_exit(). Should be + * called in the buffers release callback implementation right before freeing + * the memory associated with the buffer. + */ +void iio_dma_buffer_release(struct iio_dma_buffer_queue *queue) +{ + mutex_destroy(&queue->lock); +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_release); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("DMA buffer for the IIO framework"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c new file mode 100644 index 000000000..ebdb838d3 --- /dev/null +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -0,0 +1,213 @@ +/* + * Copyright 2014-2015 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/spinlock.h> +#include <linux/err.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/buffer-dma.h> +#include <linux/iio/buffer-dmaengine.h> + +/* + * The IIO DMAengine buffer combines the generic IIO DMA buffer infrastructure + * with the DMAengine framework. The generic IIO DMA buffer infrastructure is + * used to manage the buffer memory and implement the IIO buffer operations + * while the DMAengine framework is used to perform the DMA transfers. Combined + * this results in a device independent fully functional DMA buffer + * implementation that can be used by device drivers for peripherals which are + * connected to a DMA controller which has a DMAengine driver implementation. + */ + +struct dmaengine_buffer { + struct iio_dma_buffer_queue queue; + + struct dma_chan *chan; + struct list_head active; + + size_t align; + size_t max_size; +}; + +static struct dmaengine_buffer *iio_buffer_to_dmaengine_buffer( + struct iio_buffer *buffer) +{ + return container_of(buffer, struct dmaengine_buffer, queue.buffer); +} + +static void iio_dmaengine_buffer_block_done(void *data) +{ + struct iio_dma_buffer_block *block = data; + unsigned long flags; + + spin_lock_irqsave(&block->queue->list_lock, flags); + list_del(&block->head); + spin_unlock_irqrestore(&block->queue->list_lock, flags); + iio_dma_buffer_block_done(block); +} + +static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, + struct iio_dma_buffer_block *block) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(&queue->buffer); + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + + block->bytes_used = min(block->size, dmaengine_buffer->max_size); + block->bytes_used = rounddown(block->bytes_used, + dmaengine_buffer->align); + + desc = dmaengine_prep_slave_single(dmaengine_buffer->chan, + block->phys_addr, block->bytes_used, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc) + return -ENOMEM; + + desc->callback = iio_dmaengine_buffer_block_done; + desc->callback_param = block; + + cookie = dmaengine_submit(desc); + if (dma_submit_error(cookie)) + return dma_submit_error(cookie); + + spin_lock_irq(&dmaengine_buffer->queue.list_lock); + list_add_tail(&block->head, &dmaengine_buffer->active); + spin_unlock_irq(&dmaengine_buffer->queue.list_lock); + + dma_async_issue_pending(dmaengine_buffer->chan); + + return 0; +} + +static void iio_dmaengine_buffer_abort(struct iio_dma_buffer_queue *queue) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(&queue->buffer); + + dmaengine_terminate_all(dmaengine_buffer->chan); + /* FIXME: There is a slight chance of a race condition here. + * dmaengine_terminate_all() does not guarantee that all transfer + * callbacks have finished running. Need to introduce a + * dmaengine_terminate_all_sync(). + */ + iio_dma_buffer_block_list_abort(queue, &dmaengine_buffer->active); +} + +static void iio_dmaengine_buffer_release(struct iio_buffer *buf) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(buf); + + iio_dma_buffer_release(&dmaengine_buffer->queue); + kfree(dmaengine_buffer); +} + +static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { + .read_first_n = iio_dma_buffer_read, + .set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum, + .set_length = iio_dma_buffer_set_length, + .request_update = iio_dma_buffer_request_update, + .enable = iio_dma_buffer_enable, + .disable = iio_dma_buffer_disable, + .data_available = iio_dma_buffer_data_available, + .release = iio_dmaengine_buffer_release, + + .modes = INDIO_BUFFER_HARDWARE, + .flags = INDIO_BUFFER_FLAG_FIXED_WATERMARK, +}; + +static const struct iio_dma_buffer_ops iio_dmaengine_default_ops = { + .submit = iio_dmaengine_buffer_submit_block, + .abort = iio_dmaengine_buffer_abort, +}; + +/** + * iio_dmaengine_buffer_alloc() - Allocate new buffer which uses DMAengine + * @dev: Parent device for the buffer + * @channel: DMA channel name, typically "rx". + * + * This allocates a new IIO buffer which internally uses the DMAengine framework + * to perform its transfers. The parent device will be used to request the DMA + * channel. + * + * Once done using the buffer iio_dmaengine_buffer_free() should be used to + * release it. + */ +struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev, + const char *channel) +{ + struct dmaengine_buffer *dmaengine_buffer; + unsigned int width, src_width, dest_width; + struct dma_slave_caps caps; + struct dma_chan *chan; + int ret; + + dmaengine_buffer = kzalloc(sizeof(*dmaengine_buffer), GFP_KERNEL); + if (!dmaengine_buffer) + return ERR_PTR(-ENOMEM); + + chan = dma_request_slave_channel_reason(dev, channel); + if (IS_ERR(chan)) { + ret = PTR_ERR(chan); + goto err_free; + } + + ret = dma_get_slave_caps(chan, &caps); + if (ret < 0) + goto err_free; + + /* Needs to be aligned to the maximum of the minimums */ + if (caps.src_addr_widths) + src_width = __ffs(caps.src_addr_widths); + else + src_width = 1; + if (caps.dst_addr_widths) + dest_width = __ffs(caps.dst_addr_widths); + else + dest_width = 1; + width = max(src_width, dest_width); + + INIT_LIST_HEAD(&dmaengine_buffer->active); + dmaengine_buffer->chan = chan; + dmaengine_buffer->align = width; + dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev); + + iio_dma_buffer_init(&dmaengine_buffer->queue, chan->device->dev, + &iio_dmaengine_default_ops); + + dmaengine_buffer->queue.buffer.access = &iio_dmaengine_buffer_ops; + + return &dmaengine_buffer->queue.buffer; + +err_free: + kfree(dmaengine_buffer); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(iio_dmaengine_buffer_alloc); + +/** + * iio_dmaengine_buffer_free() - Free dmaengine buffer + * @buffer: Buffer to free + * + * Frees a buffer previously allocated with iio_dmaengine_buffer_alloc(). + */ +void iio_dmaengine_buffer_free(struct iio_buffer *buffer) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(buffer); + + iio_dma_buffer_exit(&dmaengine_buffer->queue); + dma_release_channel(dmaengine_buffer->chan); + + iio_buffer_put(buffer); +} +EXPORT_SYMBOL_GPL(iio_dmaengine_buffer_free); diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig index 3061b7299..f16de61be 100644 --- a/drivers/iio/chemical/Kconfig +++ b/drivers/iio/chemical/Kconfig @@ -4,6 +4,14 @@ menu "Chemical Sensors" +config IAQCORE + tristate "AMS iAQ-Core VOC sensors" + depends on I2C + help + Say Y here to build I2C interface support for the AMS + iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds) + sensors + config VZ89X tristate "SGX Sensortech MiCS VZ89X VOC sensor" depends on I2C diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile index 7292f2ded..167861fad 100644 --- a/drivers/iio/chemical/Makefile +++ b/drivers/iio/chemical/Makefile @@ -3,4 +3,5 @@ # # When adding new entries keep the list in alphabetical order +obj-$(CONFIG_IAQCORE) += ams-iaq-core.o obj-$(CONFIG_VZ89X) += vz89x.o diff --git a/drivers/iio/chemical/ams-iaq-core.c b/drivers/iio/chemical/ams-iaq-core.c new file mode 100644 index 000000000..41a8e6f2e --- /dev/null +++ b/drivers/iio/chemical/ams-iaq-core.c @@ -0,0 +1,200 @@ +/* + * ams-iaq-core.c - Support for AMS iAQ-Core VOC sensors + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> + +#define AMS_IAQCORE_DATA_SIZE 9 + +#define AMS_IAQCORE_VOC_CO2_IDX 0 +#define AMS_IAQCORE_VOC_RESISTANCE_IDX 1 +#define AMS_IAQCORE_VOC_TVOC_IDX 2 + +struct ams_iaqcore_reading { + __be16 co2_ppm; + u8 status; + __be32 resistance; + __be16 voc_ppb; +} __attribute__((__packed__)); + +struct ams_iaqcore_data { + struct i2c_client *client; + struct mutex lock; + unsigned long last_update; + + struct ams_iaqcore_reading buffer; +}; + +static const struct iio_chan_spec ams_iaqcore_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_CO2_IDX, + }, + { + .type = IIO_RESISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_RESISTANCE_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_TVOC_IDX, + }, +}; + +static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + struct i2c_msg msg = { + .addr = client->addr, + .flags = client->flags | I2C_M_RD, + .len = AMS_IAQCORE_DATA_SIZE, + .buf = (char *) &data->buffer, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + + return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret; +} + +static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data) +{ + int ret; + + /* sensor can only be polled once a second max per datasheet */ + if (!time_after(jiffies, data->last_update + HZ)) + return 0; + + ret = ams_iaqcore_read_measurement(data); + if (ret < 0) + return ret; + + data->last_update = jiffies; + + return 0; +} + +static int ams_iaqcore_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ams_iaqcore_data *data = iio_priv(indio_dev); + int ret; + + if (mask != IIO_CHAN_INFO_PROCESSED) + return -EINVAL; + + mutex_lock(&data->lock); + ret = ams_iaqcore_get_measurement(data); + + if (ret) + goto err_out; + + switch (chan->address) { + case AMS_IAQCORE_VOC_CO2_IDX: + *val = 0; + *val2 = be16_to_cpu(data->buffer.co2_ppm); + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case AMS_IAQCORE_VOC_RESISTANCE_IDX: + *val = be32_to_cpu(data->buffer.resistance); + ret = IIO_VAL_INT; + break; + case AMS_IAQCORE_VOC_TVOC_IDX: + *val = 0; + *val2 = be16_to_cpu(data->buffer.voc_ppb); + ret = IIO_VAL_INT_PLUS_NANO; + break; + default: + ret = -EINVAL; + } + +err_out: + mutex_unlock(&data->lock); + + return ret; +} + +static const struct iio_info ams_iaqcore_info = { + .read_raw = ams_iaqcore_read_raw, + .driver_module = THIS_MODULE, +}; + +static int ams_iaqcore_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct ams_iaqcore_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + /* so initial reading will complete */ + data->last_update = jiffies - HZ; + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ams_iaqcore_info, + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = ams_iaqcore_channels; + indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id ams_iaqcore_id[] = { + { "ams-iaq-core", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id); + +static const struct of_device_id ams_iaqcore_dt_ids[] = { + { .compatible = "ams,iaq-core" }, + { } +}; +MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids); + +static struct i2c_driver ams_iaqcore_driver = { + .driver = { + .name = "ams-iaq-core", + .of_match_table = of_match_ptr(ams_iaqcore_dt_ids), + }, + .probe = ams_iaqcore_probe, + .id_table = ams_iaqcore_id, +}; +module_i2c_driver(ams_iaqcore_driver); + +MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>"); +MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/vz89x.c b/drivers/iio/chemical/vz89x.c index 11e59a5a5..b8b804923 100644 --- a/drivers/iio/chemical/vz89x.c +++ b/drivers/iio/chemical/vz89x.c @@ -34,8 +34,9 @@ struct vz89x_data { struct i2c_client *client; struct mutex lock; - unsigned long last_update; + int (*xfer)(struct vz89x_data *data, u8 cmd); + unsigned long last_update; u8 buffer[VZ89X_REG_MEASUREMENT_SIZE]; }; @@ -100,27 +101,60 @@ static int vz89x_measurement_is_valid(struct vz89x_data *data) return !!(data->buffer[VZ89X_REG_MEASUREMENT_SIZE - 1] > 0); } -static int vz89x_get_measurement(struct vz89x_data *data) +static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd) { + struct i2c_client *client = data->client; + struct i2c_msg msg[2]; int ret; - int i; + u8 buf[3] = { cmd, 0, 0}; - /* sensor can only be polled once a second max per datasheet */ - if (!time_after(jiffies, data->last_update + HZ)) - return 0; + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].len = 3; + msg[0].buf = (char *) &buf; + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = VZ89X_REG_MEASUREMENT_SIZE; + msg[1].buf = (char *) &data->buffer; + + ret = i2c_transfer(client->adapter, msg, 2); - ret = i2c_smbus_write_word_data(data->client, - VZ89X_REG_MEASUREMENT, 0); + return (ret == 2) ? 0 : ret; +} + +static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd) +{ + struct i2c_client *client = data->client; + int ret; + int i; + + ret = i2c_smbus_write_word_data(client, cmd, 0); if (ret < 0) return ret; for (i = 0; i < VZ89X_REG_MEASUREMENT_SIZE; i++) { - ret = i2c_smbus_read_byte(data->client); + ret = i2c_smbus_read_byte(client); if (ret < 0) return ret; data->buffer[i] = ret; } + return 0; +} + +static int vz89x_get_measurement(struct vz89x_data *data) +{ + int ret; + + /* sensor can only be polled once a second max per datasheet */ + if (!time_after(jiffies, data->last_update + HZ)) + return 0; + + ret = data->xfer(data, VZ89X_REG_MEASUREMENT); + if (ret < 0) + return ret; + ret = vz89x_measurement_is_valid(data); if (ret) return -EAGAIN; @@ -204,15 +238,19 @@ static int vz89x_probe(struct i2c_client *client, struct iio_dev *indio_dev; struct vz89x_data *data; - if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA | - I2C_FUNC_SMBUS_BYTE)) - return -ENODEV; - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; - data = iio_priv(indio_dev); + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + data->xfer = vz89x_i2c_xfer; + else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) + data->xfer = vz89x_smbus_xfer; + else + return -ENOTSUPP; + i2c_set_clientdata(client, indio_dev); data->client = client; data->last_update = jiffies - HZ; diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c index 25258e2c1..8447c31e2 100644 --- a/drivers/iio/common/st_sensors/st_sensors_core.c +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -18,9 +18,6 @@ #include <asm/unaligned.h> #include <linux/iio/common/st_sensors.h> - -#define ST_SENSORS_WAI_ADDRESS 0x0f - static inline u32 st_sensors_get_unaligned_le24(const u8 *p) { return (s32)((p[0] | p[1] << 8 | p[2] << 16) << 8) >> 8; diff --git a/drivers/iio/dummy/Kconfig b/drivers/iio/dummy/Kconfig new file mode 100644 index 000000000..71805ced1 --- /dev/null +++ b/drivers/iio/dummy/Kconfig @@ -0,0 +1,36 @@ +# +# Industrial I/O subsystem Dummy Driver configuration +# +menu "IIO dummy driver" + depends on IIO + +config IIO_DUMMY_EVGEN + select IRQ_WORK + tristate + +config IIO_SIMPLE_DUMMY + tristate "An example driver with no hardware requirements" + help + Driver intended mainly as documentation for how to write + a driver. May also be useful for testing userspace code + without hardware. + +if IIO_SIMPLE_DUMMY + +config IIO_SIMPLE_DUMMY_EVENTS + bool "Event generation support" + select IIO_DUMMY_EVGEN + help + Add some dummy events to the simple dummy driver. + +config IIO_SIMPLE_DUMMY_BUFFER + bool "Buffered capture support" + select IIO_BUFFER + select IIO_TRIGGER + select IIO_KFIFO_BUF + help + Add buffered data capture to the simple dummy driver. + +endif # IIO_SIMPLE_DUMMY + +endmenu diff --git a/drivers/iio/dummy/Makefile b/drivers/iio/dummy/Makefile new file mode 100644 index 000000000..0765e93d7 --- /dev/null +++ b/drivers/iio/dummy/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the IIO Dummy Driver +# + +obj-$(CONFIG_IIO_SIMPLE_DUMMY) += iio_dummy.o +iio_dummy-y := iio_simple_dummy.o +iio_dummy-$(CONFIG_IIO_SIMPLE_DUMMY_EVENTS) += iio_simple_dummy_events.o +iio_dummy-$(CONFIG_IIO_SIMPLE_DUMMY_BUFFER) += iio_simple_dummy_buffer.o + +obj-$(CONFIG_IIO_DUMMY_EVGEN) += iio_dummy_evgen.o diff --git a/drivers/iio/dummy/iio_dummy_evgen.c b/drivers/iio/dummy/iio_dummy_evgen.c new file mode 100644 index 000000000..9e83f348d --- /dev/null +++ b/drivers/iio/dummy/iio_dummy_evgen.c @@ -0,0 +1,262 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Companion module to the iio simple dummy example driver. + * The purpose of this is to generate 'fake' event interrupts thus + * allowing that driver's code to be as close as possible to that of + * a normal driver talking to hardware. The approach used here + * is not intended to be general and just happens to work for this + * particular use case. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/sysfs.h> + +#include "iio_dummy_evgen.h" +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/irq_work.h> + +/* Fiddly bit of faking and irq without hardware */ +#define IIO_EVENTGEN_NO 10 + +/** + * struct iio_dummy_handle_irq - helper struct to simulate interrupt generation + * @work: irq_work used to run handlers from hardirq context + * @irq: fake irq line number to trigger an interrupt + */ +struct iio_dummy_handle_irq { + struct irq_work work; + int irq; +}; + +/** + * struct iio_dummy_evgen - evgen state + * @chip: irq chip we are faking + * @base: base of irq range + * @enabled: mask of which irqs are enabled + * @inuse: mask of which irqs are connected + * @regs: irq regs we are faking + * @lock: protect the evgen state + * @handler: helper for a 'hardware-like' interrupt simulation + */ +struct iio_dummy_eventgen { + struct irq_chip chip; + int base; + bool enabled[IIO_EVENTGEN_NO]; + bool inuse[IIO_EVENTGEN_NO]; + struct iio_dummy_regs regs[IIO_EVENTGEN_NO]; + struct mutex lock; + struct iio_dummy_handle_irq handler; +}; + +/* We can only ever have one instance of this 'device' */ +static struct iio_dummy_eventgen *iio_evgen; +static const char *iio_evgen_name = "iio_dummy_evgen"; + +static void iio_dummy_event_irqmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_dummy_eventgen *evgen = + container_of(chip, struct iio_dummy_eventgen, chip); + + evgen->enabled[d->irq - evgen->base] = false; +} + +static void iio_dummy_event_irqunmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_dummy_eventgen *evgen = + container_of(chip, struct iio_dummy_eventgen, chip); + + evgen->enabled[d->irq - evgen->base] = true; +} + +static void iio_dummy_work_handler(struct irq_work *work) +{ + struct iio_dummy_handle_irq *irq_handler; + + irq_handler = container_of(work, struct iio_dummy_handle_irq, work); + handle_simple_irq(irq_to_desc(irq_handler->irq)); +} + +static int iio_dummy_evgen_create(void) +{ + int ret, i; + + iio_evgen = kzalloc(sizeof(*iio_evgen), GFP_KERNEL); + if (!iio_evgen) + return -ENOMEM; + + iio_evgen->base = irq_alloc_descs(-1, 0, IIO_EVENTGEN_NO, 0); + if (iio_evgen->base < 0) { + ret = iio_evgen->base; + kfree(iio_evgen); + return ret; + } + iio_evgen->chip.name = iio_evgen_name; + iio_evgen->chip.irq_mask = &iio_dummy_event_irqmask; + iio_evgen->chip.irq_unmask = &iio_dummy_event_irqunmask; + for (i = 0; i < IIO_EVENTGEN_NO; i++) { + irq_set_chip(iio_evgen->base + i, &iio_evgen->chip); + irq_set_handler(iio_evgen->base + i, &handle_simple_irq); + irq_modify_status(iio_evgen->base + i, + IRQ_NOREQUEST | IRQ_NOAUTOEN, + IRQ_NOPROBE); + } + init_irq_work(&iio_evgen->handler.work, iio_dummy_work_handler); + mutex_init(&iio_evgen->lock); + return 0; +} + +/** + * iio_dummy_evgen_get_irq() - get an evgen provided irq for a device + * + * This function will give a free allocated irq to a client device. + * That irq can then be caused to 'fire' by using the associated sysfs file. + */ +int iio_dummy_evgen_get_irq(void) +{ + int i, ret = 0; + + if (!iio_evgen) + return -ENODEV; + + mutex_lock(&iio_evgen->lock); + for (i = 0; i < IIO_EVENTGEN_NO; i++) + if (!iio_evgen->inuse[i]) { + ret = iio_evgen->base + i; + iio_evgen->inuse[i] = true; + break; + } + mutex_unlock(&iio_evgen->lock); + if (i == IIO_EVENTGEN_NO) + return -ENOMEM; + return ret; +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_get_irq); + +/** + * iio_dummy_evgen_release_irq() - give the irq back. + * @irq: irq being returned to the pool + * + * Used by client driver instances to give the irqs back when they disconnect + */ +void iio_dummy_evgen_release_irq(int irq) +{ + mutex_lock(&iio_evgen->lock); + iio_evgen->inuse[irq - iio_evgen->base] = false; + mutex_unlock(&iio_evgen->lock); +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_release_irq); + +struct iio_dummy_regs *iio_dummy_evgen_get_regs(int irq) +{ + return &iio_evgen->regs[irq - iio_evgen->base]; +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_get_regs); + +static void iio_dummy_evgen_free(void) +{ + irq_free_descs(iio_evgen->base, IIO_EVENTGEN_NO); + kfree(iio_evgen); +} + +static void iio_evgen_release(struct device *dev) +{ + iio_dummy_evgen_free(); +} + +static ssize_t iio_evgen_poke(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned long event; + int ret; + + ret = kstrtoul(buf, 10, &event); + if (ret) + return ret; + + iio_evgen->regs[this_attr->address].reg_id = this_attr->address; + iio_evgen->regs[this_attr->address].reg_data = event; + + iio_evgen->handler.irq = iio_evgen->base + this_attr->address; + if (iio_evgen->enabled[this_attr->address]) + irq_work_queue(&iio_evgen->handler.work); + + return len; +} + +static IIO_DEVICE_ATTR(poke_ev0, S_IWUSR, NULL, &iio_evgen_poke, 0); +static IIO_DEVICE_ATTR(poke_ev1, S_IWUSR, NULL, &iio_evgen_poke, 1); +static IIO_DEVICE_ATTR(poke_ev2, S_IWUSR, NULL, &iio_evgen_poke, 2); +static IIO_DEVICE_ATTR(poke_ev3, S_IWUSR, NULL, &iio_evgen_poke, 3); +static IIO_DEVICE_ATTR(poke_ev4, S_IWUSR, NULL, &iio_evgen_poke, 4); +static IIO_DEVICE_ATTR(poke_ev5, S_IWUSR, NULL, &iio_evgen_poke, 5); +static IIO_DEVICE_ATTR(poke_ev6, S_IWUSR, NULL, &iio_evgen_poke, 6); +static IIO_DEVICE_ATTR(poke_ev7, S_IWUSR, NULL, &iio_evgen_poke, 7); +static IIO_DEVICE_ATTR(poke_ev8, S_IWUSR, NULL, &iio_evgen_poke, 8); +static IIO_DEVICE_ATTR(poke_ev9, S_IWUSR, NULL, &iio_evgen_poke, 9); + +static struct attribute *iio_evgen_attrs[] = { + &iio_dev_attr_poke_ev0.dev_attr.attr, + &iio_dev_attr_poke_ev1.dev_attr.attr, + &iio_dev_attr_poke_ev2.dev_attr.attr, + &iio_dev_attr_poke_ev3.dev_attr.attr, + &iio_dev_attr_poke_ev4.dev_attr.attr, + &iio_dev_attr_poke_ev5.dev_attr.attr, + &iio_dev_attr_poke_ev6.dev_attr.attr, + &iio_dev_attr_poke_ev7.dev_attr.attr, + &iio_dev_attr_poke_ev8.dev_attr.attr, + &iio_dev_attr_poke_ev9.dev_attr.attr, + NULL, +}; + +static const struct attribute_group iio_evgen_group = { + .attrs = iio_evgen_attrs, +}; + +static const struct attribute_group *iio_evgen_groups[] = { + &iio_evgen_group, + NULL +}; + +static struct device iio_evgen_dev = { + .bus = &iio_bus_type, + .groups = iio_evgen_groups, + .release = &iio_evgen_release, +}; + +static __init int iio_dummy_evgen_init(void) +{ + int ret = iio_dummy_evgen_create(); + + if (ret < 0) + return ret; + device_initialize(&iio_evgen_dev); + dev_set_name(&iio_evgen_dev, "iio_evgen"); + return device_add(&iio_evgen_dev); +} +module_init(iio_dummy_evgen_init); + +static __exit void iio_dummy_evgen_exit(void) +{ + device_unregister(&iio_evgen_dev); +} +module_exit(iio_dummy_evgen_exit); + +MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dummy/iio_dummy_evgen.h b/drivers/iio/dummy/iio_dummy_evgen.h new file mode 100644 index 000000000..d044b946e --- /dev/null +++ b/drivers/iio/dummy/iio_dummy_evgen.h @@ -0,0 +1,13 @@ +#ifndef _IIO_DUMMY_EVGEN_H_ +#define _IIO_DUMMY_EVGEN_H_ + +struct iio_dummy_regs { + u32 reg_id; + u32 reg_data; +}; + +struct iio_dummy_regs *iio_dummy_evgen_get_regs(int irq); +int iio_dummy_evgen_get_irq(void); +void iio_dummy_evgen_release_irq(int irq); + +#endif /* _IIO_DUMMY_EVGEN_H_ */ diff --git a/drivers/iio/dummy/iio_simple_dummy.c b/drivers/iio/dummy/iio_simple_dummy.c new file mode 100644 index 000000000..43fe4ba7d --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy.c @@ -0,0 +1,747 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * A reference industrial I/O driver to illustrate the functionality available. + * + * There are numerous real drivers to illustrate the finer points. + * The purpose of this driver is to provide a driver with far more comments + * and explanatory notes than any 'real' driver would have. + * Anyone starting out writing an IIO driver should first make sure they + * understand all of this driver except those bits specifically marked + * as being present to allow us to 'fake' the presence of hardware. + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/buffer.h> +#include "iio_simple_dummy.h" + +/* + * A few elements needed to fake a bus for this driver + * Note instances parameter controls how many of these + * dummy devices are registered. + */ +static unsigned instances = 1; +module_param(instances, uint, 0); + +/* Pointer array used to fake bus elements */ +static struct iio_dev **iio_dummy_devs; + +/* Fake a name for the part number, usually obtained from the id table */ +static const char *iio_dummy_part_number = "iio_dummy_part_no"; + +/** + * struct iio_dummy_accel_calibscale - realworld to register mapping + * @val: first value in read_raw - here integer part. + * @val2: second value in read_raw etc - here micro part. + * @regval: register value - magic device specific numbers. + */ +struct iio_dummy_accel_calibscale { + int val; + int val2; + int regval; /* what would be written to hardware */ +}; + +static const struct iio_dummy_accel_calibscale dummy_scales[] = { + { 0, 100, 0x8 }, /* 0.000100 */ + { 0, 133, 0x7 }, /* 0.000133 */ + { 733, 13, 0x9 }, /* 733.000013 */ +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + +/* + * simple event - triggered when value rises above + * a threshold + */ +static const struct iio_event_spec iio_dummy_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple step detect event - triggered when a step is detected + */ +static const struct iio_event_spec step_detect_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple transition event - triggered when the reported running confidence + * value rises above a threshold value + */ +static const struct iio_event_spec iio_running_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple transition event - triggered when the reported walking confidence + * value falls under a threshold value + */ +static const struct iio_event_spec iio_walking_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; +#endif + +/* + * iio_dummy_channels - Description of available channels + * + * This array of structures tells the IIO core about what the device + * actually provides for a given channel. + */ +static const struct iio_chan_spec iio_dummy_channels[] = { + /* indexed ADC channel in_voltage0_raw etc */ + { + .type = IIO_VOLTAGE, + /* Channel has a numeric index of 0 */ + .indexed = 1, + .channel = 0, + /* What other information is available? */ + .info_mask_separate = + /* + * in_voltage0_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + BIT(IIO_CHAN_INFO_RAW) | + /* + * in_voltage0_offset + * Offset for userspace to apply prior to scale + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_OFFSET) | + /* + * in_voltage0_scale + * Multipler for userspace to apply post offset + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + /* The ordering of elements in the buffer via an enum */ + .scan_index = DUMMY_INDEX_VOLTAGE_0, + .scan_type = { /* Description of storage in buffer */ + .sign = 'u', /* unsigned */ + .realbits = 13, /* 13 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_dummy_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + /* Differential ADC channel in_voltage1-voltage2_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + /* + * Indexing for differential channels uses channel + * for the positive part, channel2 for the negative. + */ + .indexed = 1, + .channel = 1, + .channel2 = 2, + /* + * in_voltage1-voltage2_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + /* + * in_voltage-voltage_scale + * Shared version of scale - shared by differential + * input channels of type IIO_VOLTAGE. + */ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .scan_index = DUMMY_INDEX_DIFFVOLTAGE_1M2, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 12, /* 12 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* Differential ADC channel in_voltage3-voltage4_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 3, + .channel2 = 4, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_DIFFVOLTAGE_3M4, + .scan_type = { + .sign = 's', + .realbits = 11, + .storagebits = 16, + .shift = 0, + }, + }, + /* + * 'modified' (i.e. axis specified) acceleration channel + * in_accel_z_raw + */ + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + /* + * Internal bias and gain correction values. Applied + * by the hardware or driver prior to userspace + * seeing the readings. Typically part of hardware + * calibration. + */ + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_ACCELX, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 16 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* + * Convenience macro for timestamps. 4 is the index in + * the buffer. + */ + IIO_CHAN_SOFT_TIMESTAMP(4), + /* DAC channel out_voltage0_raw */ + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = -1, /* No buffer support */ + .output = 1, + .indexed = 1, + .channel = 0, + }, + { + .type = IIO_STEPS, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_CALIBHEIGHT), + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &step_detect_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_RUNNING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_running_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_WALKING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_walking_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, +}; + +/** + * iio_dummy_read_raw() - data read function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be read + * @val: first element of returned value (typically INT) + * @val2: second element of returned value (typically MICRO) + * @mask: what we actually want to read as per the info_mask_* + * in iio_chan_spec. + */ +static int iio_dummy_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: /* magic value - channel value read */ + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) { + /* Set integer part to cached value */ + *val = st->dac_val; + ret = IIO_VAL_INT; + } else if (chan->differential) { + if (chan->channel == 1) + *val = st->differential_adc_val[0]; + else + *val = st->differential_adc_val[1]; + ret = IIO_VAL_INT; + } else { + *val = st->single_ended_adc_val; + ret = IIO_VAL_INT; + } + break; + case IIO_ACCEL: + *val = st->accel_val; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + *val = st->steps; + ret = IIO_VAL_INT; + break; + case IIO_ACTIVITY: + switch (chan->channel2) { + case IIO_MOD_RUNNING: + *val = st->activity_running; + ret = IIO_VAL_INT; + break; + case IIO_MOD_WALKING: + *val = st->activity_walking; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + default: + break; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* only single ended adc -> 7 */ + *val = 7; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + switch (chan->differential) { + case 0: + /* only single ended adc -> 0.001333 */ + *val = 0; + *val2 = 1333; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case 1: + /* all differential adc -> 0.000001344 */ + *val = 0; + *val2 = 1344; + ret = IIO_VAL_INT_PLUS_NANO; + } + break; + default: + break; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + /* only the acceleration axis - read from cache */ + *val = st->accel_calibbias; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + *val = st->accel_calibscale->val; + *val2 = st->accel_calibscale->val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = 3; + *val2 = 33; + ret = IIO_VAL_INT_PLUS_NANO; + break; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + *val = st->steps_enabled; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + *val = st->height; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + default: + break; + } + mutex_unlock(&st->lock); + return ret; +} + +/** + * iio_dummy_write_raw() - data write function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be written + * @val: first element of value to set (typically INT) + * @val2: second element of value to set (typically MICRO) + * @mask: what we actually want to write as per the info_mask_* + * in iio_chan_spec. + * + * Note that all raw writes are assumed IIO_VAL_INT and info mask elements + * are assumed to be IIO_INT_PLUS_MICRO unless the callback write_raw_get_fmt + * in struct iio_info is provided by the driver. + */ +static int iio_dummy_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int i; + int ret = 0; + struct iio_dummy_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output == 0) + return -EINVAL; + + /* Locking not required as writing single value */ + mutex_lock(&st->lock); + st->dac_val = val; + mutex_unlock(&st->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + mutex_lock(&st->lock); + st->steps = val; + mutex_unlock(&st->lock); + return 0; + case IIO_ACTIVITY: + if (val < 0) + val = 0; + if (val > 100) + val = 100; + switch (chan->channel2) { + case IIO_MOD_RUNNING: + st->activity_running = val; + return 0; + case IIO_MOD_WALKING: + st->activity_walking = val; + return 0; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: + mutex_lock(&st->lock); + /* Compare against table - hard matching here */ + for (i = 0; i < ARRAY_SIZE(dummy_scales); i++) + if (val == dummy_scales[i].val && + val2 == dummy_scales[i].val2) + break; + if (i == ARRAY_SIZE(dummy_scales)) + ret = -EINVAL; + else + st->accel_calibscale = &dummy_scales[i]; + mutex_unlock(&st->lock); + return ret; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&st->lock); + st->accel_calibbias = val; + mutex_unlock(&st->lock); + return 0; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + mutex_lock(&st->lock); + st->steps_enabled = val; + mutex_unlock(&st->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + st->height = val; + return 0; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +/* + * Device type specific information. + */ +static const struct iio_info iio_dummy_info = { + .driver_module = THIS_MODULE, + .read_raw = &iio_dummy_read_raw, + .write_raw = &iio_dummy_write_raw, +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .read_event_config = &iio_simple_dummy_read_event_config, + .write_event_config = &iio_simple_dummy_write_event_config, + .read_event_value = &iio_simple_dummy_read_event_value, + .write_event_value = &iio_simple_dummy_write_event_value, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ +}; + +/** + * iio_dummy_init_device() - device instance specific init + * @indio_dev: the iio device structure + * + * Most drivers have one of these to set up default values, + * reset the device to known state etc. + */ +static int iio_dummy_init_device(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->dac_val = 0; + st->single_ended_adc_val = 73; + st->differential_adc_val[0] = 33; + st->differential_adc_val[1] = -34; + st->accel_val = 34; + st->accel_calibbias = -7; + st->accel_calibscale = &dummy_scales[0]; + st->steps = 47; + st->activity_running = 98; + st->activity_walking = 4; + + return 0; +} + +/** + * iio_dummy_probe() - device instance probe + * @index: an id number for this instance. + * + * Arguments are bus type specific. + * I2C: iio_dummy_probe(struct i2c_client *client, + * const struct i2c_device_id *id) + * SPI: iio_dummy_probe(struct spi_device *spi) + */ +static int iio_dummy_probe(int index) +{ + int ret; + struct iio_dev *indio_dev; + struct iio_dummy_state *st; + + /* + * Allocate an IIO device. + * + * This structure contains all generic state + * information about the device instance. + * It also has a region (accessed by iio_priv() + * for chip specific state information. + */ + indio_dev = iio_device_alloc(sizeof(*st)); + if (!indio_dev) { + ret = -ENOMEM; + goto error_ret; + } + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + + iio_dummy_init_device(indio_dev); + /* + * With hardware: Set the parent device. + * indio_dev->dev.parent = &spi->dev; + * indio_dev->dev.parent = &client->dev; + */ + + /* + * Make the iio_dev struct available to remove function. + * Bus equivalents + * i2c_set_clientdata(client, indio_dev); + * spi_set_drvdata(spi, indio_dev); + */ + iio_dummy_devs[index] = indio_dev; + + /* + * Set the device name. + * + * This is typically a part number and obtained from the module + * id table. + * e.g. for i2c and spi: + * indio_dev->name = id->name; + * indio_dev->name = spi_get_device_id(spi)->name; + */ + indio_dev->name = iio_dummy_part_number; + + /* Provide description of available channels */ + indio_dev->channels = iio_dummy_channels; + indio_dev->num_channels = ARRAY_SIZE(iio_dummy_channels); + + /* + * Provide device type specific interface functions and + * constant data. + */ + indio_dev->info = &iio_dummy_info; + + /* Specify that device provides sysfs type interfaces */ + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_simple_dummy_events_register(indio_dev); + if (ret < 0) + goto error_free_device; + + ret = iio_simple_dummy_configure_buffer(indio_dev); + if (ret < 0) + goto error_unregister_events; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_unconfigure_buffer; + + return 0; +error_unconfigure_buffer: + iio_simple_dummy_unconfigure_buffer(indio_dev); +error_unregister_events: + iio_simple_dummy_events_unregister(indio_dev); +error_free_device: + iio_device_free(indio_dev); +error_ret: + return ret; +} + +/** + * iio_dummy_remove() - device instance removal function + * @index: device index. + * + * Parameters follow those of iio_dummy_probe for buses. + */ +static void iio_dummy_remove(int index) +{ + /* + * Get a pointer to the device instance iio_dev structure + * from the bus subsystem. E.g. + * struct iio_dev *indio_dev = i2c_get_clientdata(client); + * struct iio_dev *indio_dev = spi_get_drvdata(spi); + */ + struct iio_dev *indio_dev = iio_dummy_devs[index]; + + /* Unregister the device */ + iio_device_unregister(indio_dev); + + /* Device specific code to power down etc */ + + /* Buffered capture related cleanup */ + iio_simple_dummy_unconfigure_buffer(indio_dev); + + iio_simple_dummy_events_unregister(indio_dev); + + /* Free all structures */ + iio_device_free(indio_dev); +} + +/** + * iio_dummy_init() - device driver registration + * + * Varies depending on bus type of the device. As there is no device + * here, call probe directly. For information on device registration + * i2c: + * Documentation/i2c/writing-clients + * spi: + * Documentation/spi/spi-summary + */ +static __init int iio_dummy_init(void) +{ + int i, ret; + + if (instances > 10) { + instances = 1; + return -EINVAL; + } + + /* Fake a bus */ + iio_dummy_devs = kcalloc(instances, sizeof(*iio_dummy_devs), + GFP_KERNEL); + /* Here we have no actual device so call probe */ + for (i = 0; i < instances; i++) { + ret = iio_dummy_probe(i); + if (ret < 0) + goto error_remove_devs; + } + return 0; + +error_remove_devs: + while (i--) + iio_dummy_remove(i); + + kfree(iio_dummy_devs); + return ret; +} +module_init(iio_dummy_init); + +/** + * iio_dummy_exit() - device driver removal + * + * Varies depending on bus type of the device. + * As there is no device here, call remove directly. + */ +static __exit void iio_dummy_exit(void) +{ + int i; + + for (i = 0; i < instances; i++) + iio_dummy_remove(i); + kfree(iio_dummy_devs); +} +module_exit(iio_dummy_exit); + +MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dummy/iio_simple_dummy.h b/drivers/iio/dummy/iio_simple_dummy.h new file mode 100644 index 000000000..b9069a180 --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy.h @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Join together the various functionality of iio_simple_dummy driver + */ + +#ifndef _IIO_SIMPLE_DUMMY_H_ +#define _IIO_SIMPLE_DUMMY_H_ +#include <linux/kernel.h> + +struct iio_dummy_accel_calibscale; +struct iio_dummy_regs; + +/** + * struct iio_dummy_state - device instance specific state. + * @dac_val: cache for dac value + * @single_ended_adc_val: cache for single ended adc value + * @differential_adc_val: cache for differential adc value + * @accel_val: cache for acceleration value + * @accel_calibbias: cache for acceleration calibbias + * @accel_calibscale: cache for acceleration calibscale + * @lock: lock to ensure state is consistent + * @event_irq: irq number for event line (faked) + * @event_val: cache for event threshold value + * @event_en: cache of whether event is enabled + */ +struct iio_dummy_state { + int dac_val; + int single_ended_adc_val; + int differential_adc_val[2]; + int accel_val; + int accel_calibbias; + int activity_running; + int activity_walking; + const struct iio_dummy_accel_calibscale *accel_calibscale; + struct mutex lock; + struct iio_dummy_regs *regs; + int steps_enabled; + int steps; + int height; +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + int event_irq; + int event_val; + bool event_en; + s64 event_timestamp; +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + +struct iio_dev; + +int iio_simple_dummy_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir); + +int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state); + +int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2); + +int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, + int val2); + +int iio_simple_dummy_events_register(struct iio_dev *indio_dev); +void iio_simple_dummy_events_unregister(struct iio_dev *indio_dev); + +#else /* Stubs for when events are disabled at compile time */ + +static inline int +iio_simple_dummy_events_register(struct iio_dev *indio_dev) +{ + return 0; +}; + +static inline void +iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) +{ }; + +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS*/ + +/** + * enum iio_simple_dummy_scan_elements - scan index enum + * @DUMMY_INDEX_VOLTAGE_0: the single ended voltage channel + * @DUMMY_INDEX_DIFFVOLTAGE_1M2: first differential channel + * @DUMMY_INDEX_DIFFVOLTAGE_3M4: second differential channel + * @DUMMY_INDEX_ACCELX: acceleration channel + * + * Enum provides convenient numbering for the scan index. + */ +enum iio_simple_dummy_scan_elements { + DUMMY_INDEX_VOLTAGE_0, + DUMMY_INDEX_DIFFVOLTAGE_1M2, + DUMMY_INDEX_DIFFVOLTAGE_3M4, + DUMMY_INDEX_ACCELX, +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_BUFFER +int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev); +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev); +#else +static inline int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) +{ + return 0; +}; + +static inline +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) +{}; + +#endif /* CONFIG_IIO_SIMPLE_DUMMY_BUFFER */ +#endif /* _IIO_SIMPLE_DUMMY_H_ */ diff --git a/drivers/iio/dummy/iio_simple_dummy_buffer.c b/drivers/iio/dummy/iio_simple_dummy_buffer.c new file mode 100644 index 000000000..cf44a6f79 --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy_buffer.c @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Buffer handling elements of industrial I/O reference driver. + * Uses the kfifo buffer. + * + * To test without hardware use the sysfs trigger. + */ + +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/bitmap.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/kfifo_buf.h> + +#include "iio_simple_dummy.h" + +/* Some fake data */ + +static const s16 fakedata[] = { + [DUMMY_INDEX_VOLTAGE_0] = 7, + [DUMMY_INDEX_DIFFVOLTAGE_1M2] = -33, + [DUMMY_INDEX_DIFFVOLTAGE_3M4] = -2, + [DUMMY_INDEX_ACCELX] = 344, +}; + +/** + * iio_simple_dummy_trigger_h() - the trigger handler function + * @irq: the interrupt number + * @p: private data - always a pointer to the poll func. + * + * This is the guts of buffered capture. On a trigger event occurring, + * if the pollfunc is attached then this handler is called as a threaded + * interrupt (and hence may sleep). It is responsible for grabbing data + * from the device and pushing it into the associated buffer. + */ +static irqreturn_t iio_simple_dummy_trigger_h(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + int len = 0; + u16 *data; + + data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!data) + goto done; + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) { + /* + * Three common options here: + * hardware scans: certain combinations of channels make + * up a fast read. The capture will consist of all of them. + * Hence we just call the grab data function and fill the + * buffer without processing. + * software scans: can be considered to be random access + * so efficient reading is just a case of minimal bus + * transactions. + * software culled hardware scans: + * occasionally a driver may process the nearest hardware + * scan to avoid storing elements that are not desired. This + * is the fiddliest option by far. + * Here let's pretend we have random access. And the values are + * in the constant table fakedata. + */ + int i, j; + + for (i = 0, j = 0; + i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + i++, j++) { + j = find_next_bit(indio_dev->active_scan_mask, + indio_dev->masklength, j); + /* random access read from the 'device' */ + data[i] = fakedata[j]; + len += 2; + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, data, iio_get_time_ns()); + + kfree(data); + +done: + /* + * Tell the core we are done with this trigger and ready for the + * next one. + */ + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops iio_simple_dummy_buffer_setup_ops = { + /* + * iio_triggered_buffer_postenable: + * Generic function that simply attaches the pollfunc to the trigger. + * Replace this to mess with hardware state before we attach the + * trigger. + */ + .postenable = &iio_triggered_buffer_postenable, + /* + * iio_triggered_buffer_predisable: + * Generic function that simple detaches the pollfunc from the trigger. + * Replace this to put hardware state back again after the trigger is + * detached but before userspace knows we have disabled the ring. + */ + .predisable = &iio_triggered_buffer_predisable, +}; + +int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) +{ + int ret; + struct iio_buffer *buffer; + + /* Allocate a buffer to use - here a kfifo */ + buffer = iio_kfifo_allocate(); + if (!buffer) { + ret = -ENOMEM; + goto error_ret; + } + + iio_device_attach_buffer(indio_dev, buffer); + + /* Enable timestamps by default */ + buffer->scan_timestamp = true; + + /* + * Tell the core what device type specific functions should + * be run on either side of buffer capture enable / disable. + */ + indio_dev->setup_ops = &iio_simple_dummy_buffer_setup_ops; + + /* + * Configure a polling function. + * When a trigger event with this polling function connected + * occurs, this function is run. Typically this grabs data + * from the device. + * + * NULL for the bottom half. This is normally implemented only if we + * either want to ping a capture now pin (no sleeping) or grab + * a timestamp as close as possible to a data ready trigger firing. + * + * IRQF_ONESHOT ensures irqs are masked such that only one instance + * of the handler can run at a time. + * + * "iio_simple_dummy_consumer%d" formatting string for the irq 'name' + * as seen under /proc/interrupts. Remaining parameters as per printk. + */ + indio_dev->pollfunc = iio_alloc_pollfunc(NULL, + &iio_simple_dummy_trigger_h, + IRQF_ONESHOT, + indio_dev, + "iio_simple_dummy_consumer%d", + indio_dev->id); + + if (!indio_dev->pollfunc) { + ret = -ENOMEM; + goto error_free_buffer; + } + + /* + * Notify the core that this device is capable of buffered capture + * driven by a trigger. + */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + return 0; + +error_free_buffer: + iio_kfifo_free(indio_dev->buffer); +error_ret: + return ret; +} + +/** + * iio_simple_dummy_unconfigure_buffer() - release buffer resources + * @indo_dev: device instance state + */ +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_kfifo_free(indio_dev->buffer); +} diff --git a/drivers/iio/dummy/iio_simple_dummy_events.c b/drivers/iio/dummy/iio_simple_dummy_events.c new file mode 100644 index 000000000..6eb600ff7 --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy_events.c @@ -0,0 +1,276 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Event handling elements of industrial I/O reference driver. + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include "iio_simple_dummy.h" + +/* Evgen 'fakes' interrupt events for this example */ +#include "iio_dummy_evgen.h" + +/** + * iio_simple_dummy_read_event_config() - is event enabled? + * @indio_dev: the device instance data + * @chan: channel for the event whose state is being queried + * @type: type of the event whose state is being queried + * @dir: direction of the vent whose state is being queried + * + * This function would normally query the relevant registers or a cache to + * discover if the event generation is enabled on the device. + */ +int iio_simple_dummy_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + return st->event_en; +} + +/** + * iio_simple_dummy_write_event_config() - set whether event is enabled + * @indio_dev: the device instance data + * @chan: channel for the event whose state is being set + * @type: type of the event whose state is being set + * @dir: direction of the vent whose state is being set + * @state: whether to enable or disable the device. + * + * This function would normally set the relevant registers on the devices + * so that it generates the specified event. Here it just sets up a cached + * value. + */ +int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + /* + * Deliberately over the top code splitting to illustrate + * how this is done when multiple events exist. + */ + switch (chan->type) { + case IIO_VOLTAGE: + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_RISING) + st->event_en = state; + else + return -EINVAL; + default: + return -EINVAL; + } + break; + case IIO_ACTIVITY: + switch (type) { + case IIO_EV_TYPE_THRESH: + st->event_en = state; + break; + default: + return -EINVAL; + } + break; + case IIO_STEPS: + switch (type) { + case IIO_EV_TYPE_CHANGE: + st->event_en = state; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * iio_simple_dummy_read_event_value() - get value associated with event + * @indio_dev: device instance specific data + * @chan: channel for the event whose value is being read + * @type: type of the event whose value is being read + * @dir: direction of the vent whose value is being read + * @info: info type of the event whose value is being read + * @val: value for the event code. + * + * Many devices provide a large set of events of which only a subset may + * be enabled at a time, with value registers whose meaning changes depending + * on the event enabled. This often means that the driver must cache the values + * associated with each possible events so that the right value is in place when + * the enabled event is changed. + */ +int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + *val = st->event_val; + + return IIO_VAL_INT; +} + +/** + * iio_simple_dummy_write_event_value() - set value associate with event + * @indio_dev: device instance specific data + * @chan: channel for the event whose value is being set + * @type: type of the event whose value is being set + * @dir: direction of the vent whose value is being set + * @info: info type of the event whose value is being set + * @val: the value to be set. + */ +int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->event_val = val; + + return 0; +} + +static irqreturn_t iio_simple_dummy_get_timestamp(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->event_timestamp = iio_get_time_ns(); + return IRQ_WAKE_THREAD; +} + +/** + * iio_simple_dummy_event_handler() - identify and pass on event + * @irq: irq of event line + * @private: pointer to device instance state. + * + * This handler is responsible for querying the device to find out what + * event occurred and for then pushing that event towards userspace. + * Here only one event occurs so we push that directly on with locally + * grabbed timestamp. + */ +static irqreturn_t iio_simple_dummy_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct iio_dummy_state *st = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "id %x event %x\n", + st->regs->reg_id, st->regs->reg_data); + + switch (st->regs->reg_data) { + case 0: + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, 0, 0, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, 0, 0, 0), + st->event_timestamp); + break; + case 1: + if (st->activity_running > st->event_val) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_ACTIVITY, 0, + IIO_MOD_RUNNING, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + st->event_timestamp); + break; + case 2: + if (st->activity_walking < st->event_val) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_ACTIVITY, 0, + IIO_MOD_WALKING, + IIO_EV_DIR_FALLING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + st->event_timestamp); + break; + case 3: + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_STEPS, 0, IIO_NO_MOD, + IIO_EV_DIR_NONE, + IIO_EV_TYPE_CHANGE, 0, 0, 0), + st->event_timestamp); + break; + default: + break; + } + + return IRQ_HANDLED; +} + +/** + * iio_simple_dummy_events_register() - setup interrupt handling for events + * @indio_dev: device instance data + * + * This function requests the threaded interrupt to handle the events. + * Normally the irq is a hardware interrupt and the number comes + * from board configuration files. Here we get it from a companion + * module that fakes the interrupt for us. Note that module in + * no way forms part of this example. Just assume that events magically + * appear via the provided interrupt. + */ +int iio_simple_dummy_events_register(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret; + + /* Fire up event source - normally not present */ + st->event_irq = iio_dummy_evgen_get_irq(); + if (st->event_irq < 0) { + ret = st->event_irq; + goto error_ret; + } + st->regs = iio_dummy_evgen_get_regs(st->event_irq); + + ret = request_threaded_irq(st->event_irq, + &iio_simple_dummy_get_timestamp, + &iio_simple_dummy_event_handler, + IRQF_ONESHOT, + "iio_simple_event", + indio_dev); + if (ret < 0) + goto error_free_evgen; + return 0; + +error_free_evgen: + iio_dummy_evgen_release_irq(st->event_irq); +error_ret: + return ret; +} + +/** + * iio_simple_dummy_events_unregister() - tidy up interrupt handling on remove + * @indio_dev: device instance data + */ +void iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + free_irq(st->event_irq, indio_dev); + /* Not part of normal driver */ + iio_dummy_evgen_release_irq(st->event_irq); +} diff --git a/drivers/iio/gyro/adis16136.c b/drivers/iio/gyro/adis16136.c index f8d1c2210..b04faf93e 100644 --- a/drivers/iio/gyro/adis16136.c +++ b/drivers/iio/gyro/adis16136.c @@ -435,7 +435,9 @@ static int adis16136_initial_setup(struct iio_dev *indio_dev) if (ret) return ret; - sscanf(indio_dev->name, "adis%u\n", &device_id); + ret = sscanf(indio_dev->name, "adis%u\n", &device_id); + if (ret != 1) + return -EINVAL; if (prod_id != device_id) dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", diff --git a/drivers/iio/gyro/bmg160_core.c b/drivers/iio/gyro/bmg160_core.c index 02ff78985..bbce3b09a 100644 --- a/drivers/iio/gyro/bmg160_core.c +++ b/drivers/iio/gyro/bmg160_core.c @@ -1077,25 +1077,23 @@ int bmg160_core_probe(struct device *dev, struct regmap *regmap, int irq, goto err_trigger_unregister; } - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(dev, "unable to register iio device\n"); - goto err_buffer_cleanup; - } - ret = pm_runtime_set_active(dev); if (ret) - goto err_iio_unregister; + goto err_buffer_cleanup; pm_runtime_enable(dev); pm_runtime_set_autosuspend_delay(dev, BMG160_AUTO_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + return 0; -err_iio_unregister: - iio_device_unregister(indio_dev); err_buffer_cleanup: iio_triggered_buffer_cleanup(indio_dev); err_trigger_unregister: @@ -1113,11 +1111,12 @@ void bmg160_core_remove(struct device *dev) struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmg160_data *data = iio_priv(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(dev); pm_runtime_set_suspended(dev); pm_runtime_put_noidle(dev); - iio_device_unregister(indio_dev); iio_triggered_buffer_cleanup(indio_dev); if (data->dready_trig) { diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig new file mode 100644 index 000000000..a647679da --- /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 000000000..7c475d7fa --- /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 000000000..9d1c81f91 --- /dev/null +++ b/drivers/iio/health/max30100.c @@ -0,0 +1,453 @@ +/* + * 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_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_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_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); + } + + 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 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, ®); + if (ret < 0) + return ret; + *val = reg << 4; + + ret = regmap_read(data->regmap, MAX30100_REG_TEMP_FRACTION, ®); + 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; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Temperature reading can only be acquired while engine + * is running + */ + mutex_lock(&indio_dev->mlock); + + if (!iio_buffer_enabled(indio_dev)) + ret = -EAGAIN; + else { + ret = max30100_get_temp(data, val); + if (!ret) + ret = IIO_VAL_INT; + + } + + mutex_unlock(&indio_dev->mlock); + 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 = { + .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 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); +} + +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"); diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c index 1165b1c4f..cfc5a051a 100644 --- a/drivers/iio/humidity/dht11.c +++ b/drivers/iio/humidity/dht11.c @@ -117,7 +117,7 @@ static int dht11_decode(struct dht11 *dht11, int offset, int timeres) if (((hum_int + hum_dec + temp_int + temp_dec) & 0xff) != checksum) return -EIO; - dht11->timestamp = ktime_get_real_ns(); + dht11->timestamp = ktime_get_boot_ns(); if (hum_int < 20) { /* DHT22 */ dht11->temperature = (((temp_int & 0x7f) << 8) + temp_dec) * ((temp_int & 0x80) ? -100 : 100); @@ -145,7 +145,7 @@ static irqreturn_t dht11_handle_irq(int irq, void *data) /* TODO: Consider making the handler safe for IRQ sharing */ if (dht11->num_edges < DHT11_EDGES_PER_READ && dht11->num_edges >= 0) { - dht11->edges[dht11->num_edges].ts = ktime_get_real_ns(); + dht11->edges[dht11->num_edges].ts = ktime_get_boot_ns(); dht11->edges[dht11->num_edges++].value = gpio_get_value(dht11->gpio); @@ -164,7 +164,7 @@ static int dht11_read_raw(struct iio_dev *iio_dev, int ret, timeres; mutex_lock(&dht11->lock); - if (dht11->timestamp + DHT11_DATA_VALID_TIME < ktime_get_real_ns()) { + if (dht11->timestamp + DHT11_DATA_VALID_TIME < ktime_get_boot_ns()) { timeres = ktime_get_resolution_ns(); if (DHT11_DATA_BIT_HIGH < 2 * timeres) { dev_err(dht11->dev, "timeresolution %dns too low\n", @@ -279,7 +279,7 @@ static int dht11_probe(struct platform_device *pdev) return -EINVAL; } - dht11->timestamp = ktime_get_real_ns() - DHT11_DATA_VALID_TIME - 1; + dht11->timestamp = ktime_get_boot_ns() - DHT11_DATA_VALID_TIME - 1; dht11->num_edges = -1; platform_set_drvdata(pdev, iio); diff --git a/drivers/iio/imu/adis16400_core.c b/drivers/iio/imu/adis16400_core.c index 0618f831e..fb7c0dbed 100644 --- a/drivers/iio/imu/adis16400_core.c +++ b/drivers/iio/imu/adis16400_core.c @@ -288,7 +288,11 @@ static int adis16400_initial_setup(struct iio_dev *indio_dev) if (ret) goto err_ret; - sscanf(indio_dev->name, "adis%u\n", &device_id); + ret = sscanf(indio_dev->name, "adis%u\n", &device_id); + if (ret != 1) { + ret = -EINVAL; + goto err_ret; + } if (prod_id != device_id) dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", diff --git a/drivers/iio/imu/adis16480.c b/drivers/iio/imu/adis16480.c index 2485b88ee..8cf84d348 100644 --- a/drivers/iio/imu/adis16480.c +++ b/drivers/iio/imu/adis16480.c @@ -765,7 +765,9 @@ static int adis16480_initial_setup(struct iio_dev *indio_dev) if (ret) return ret; - sscanf(indio_dev->name, "adis%u\n", &device_id); + ret = sscanf(indio_dev->name, "adis%u\n", &device_id); + if (ret != 1) + return -EINVAL; if (prod_id != device_id) dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", diff --git a/drivers/iio/imu/inv_mpu6050/Kconfig b/drivers/iio/imu/inv_mpu6050/Kconfig index 48fbc0bc7..8f8d1370e 100644 --- a/drivers/iio/imu/inv_mpu6050/Kconfig +++ b/drivers/iio/imu/inv_mpu6050/Kconfig @@ -5,9 +5,9 @@ config INV_MPU6050_IIO tristate "Invensense MPU6050 devices" depends on I2C && SYSFS + depends on I2C_MUX select IIO_BUFFER select IIO_TRIGGERED_BUFFER - select I2C_MUX help This driver supports the Invensense MPU6050 devices. This driver can also support MPU6500 in MPU6050 compatibility mode diff --git a/drivers/iio/imu/kmx61.c b/drivers/iio/imu/kmx61.c index dbf5e9936..e5306b4e0 100644 --- a/drivers/iio/imu/kmx61.c +++ b/drivers/iio/imu/kmx61.c @@ -1390,6 +1390,14 @@ static int kmx61_probe(struct i2c_client *client, } } + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto err_buffer_cleanup_mag; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, KMX61_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + ret = iio_device_register(data->acc_indio_dev); if (ret < 0) { dev_err(&client->dev, "Failed to register acc iio device\n"); @@ -1402,18 +1410,8 @@ static int kmx61_probe(struct i2c_client *client, goto err_iio_unregister_acc; } - ret = pm_runtime_set_active(&client->dev); - if (ret < 0) - goto err_iio_unregister_mag; - - pm_runtime_enable(&client->dev); - pm_runtime_set_autosuspend_delay(&client->dev, KMX61_SLEEP_DELAY_MS); - pm_runtime_use_autosuspend(&client->dev); - return 0; -err_iio_unregister_mag: - iio_device_unregister(data->mag_indio_dev); err_iio_unregister_acc: iio_device_unregister(data->acc_indio_dev); err_buffer_cleanup_mag: @@ -1437,13 +1435,13 @@ static int kmx61_remove(struct i2c_client *client) { struct kmx61_data *data = i2c_get_clientdata(client); + iio_device_unregister(data->acc_indio_dev); + iio_device_unregister(data->mag_indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(data->acc_indio_dev); - iio_device_unregister(data->mag_indio_dev); - if (client->irq > 0) { iio_triggered_buffer_cleanup(data->acc_indio_dev); iio_triggered_buffer_cleanup(data->mag_indio_dev); diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index 0f6f63b20..139ae9162 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -193,7 +193,8 @@ void iio_buffer_init(struct iio_buffer *buffer) INIT_LIST_HEAD(&buffer->buffer_list); init_waitqueue_head(&buffer->pollq); kref_init(&buffer->ref); - buffer->watermark = 1; + if (!buffer->watermark) + buffer->watermark = 1; } EXPORT_SYMBOL(iio_buffer_init); @@ -567,6 +568,22 @@ static void iio_buffer_deactivate_all(struct iio_dev *indio_dev) iio_buffer_deactivate(buffer); } +static int iio_buffer_enable(struct iio_buffer *buffer, + struct iio_dev *indio_dev) +{ + if (!buffer->access->enable) + return 0; + return buffer->access->enable(buffer, indio_dev); +} + +static int iio_buffer_disable(struct iio_buffer *buffer, + struct iio_dev *indio_dev) +{ + if (!buffer->access->disable) + return 0; + return buffer->access->disable(buffer, indio_dev); +} + static void iio_buffer_update_bytes_per_datum(struct iio_dev *indio_dev, struct iio_buffer *buffer) { @@ -610,6 +627,7 @@ static void iio_free_scan_mask(struct iio_dev *indio_dev, struct iio_device_config { unsigned int mode; + unsigned int watermark; const unsigned long *scan_mask; unsigned int scan_bytes; bool scan_timestamp; @@ -642,10 +660,14 @@ static int iio_verify_update(struct iio_dev *indio_dev, if (buffer == remove_buffer) continue; modes &= buffer->access->modes; + config->watermark = min(config->watermark, buffer->watermark); } - if (insert_buffer) + if (insert_buffer) { modes &= insert_buffer->access->modes; + config->watermark = min(config->watermark, + insert_buffer->watermark); + } /* Definitely possible for devices to support both of these. */ if ((modes & INDIO_BUFFER_TRIGGERED) && indio_dev->trig) { @@ -713,6 +735,7 @@ static int iio_verify_update(struct iio_dev *indio_dev, static int iio_enable_buffers(struct iio_dev *indio_dev, struct iio_device_config *config) { + struct iio_buffer *buffer; int ret; indio_dev->active_scan_mask = config->scan_mask; @@ -743,6 +766,16 @@ static int iio_enable_buffers(struct iio_dev *indio_dev, } } + if (indio_dev->info->hwfifo_set_watermark) + indio_dev->info->hwfifo_set_watermark(indio_dev, + config->watermark); + + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) { + ret = iio_buffer_enable(buffer, indio_dev); + if (ret) + goto err_disable_buffers; + } + indio_dev->currentmode = config->mode; if (indio_dev->setup_ops->postenable) { @@ -750,12 +783,16 @@ static int iio_enable_buffers(struct iio_dev *indio_dev, if (ret) { dev_dbg(&indio_dev->dev, "Buffer not started: postenable failed (%d)\n", ret); - goto err_run_postdisable; + goto err_disable_buffers; } } return 0; +err_disable_buffers: + list_for_each_entry_continue_reverse(buffer, &indio_dev->buffer_list, + buffer_list) + iio_buffer_disable(buffer, indio_dev); err_run_postdisable: indio_dev->currentmode = INDIO_DIRECT_MODE; if (indio_dev->setup_ops->postdisable) @@ -768,6 +805,7 @@ err_undo_config: static int iio_disable_buffers(struct iio_dev *indio_dev) { + struct iio_buffer *buffer; int ret = 0; int ret2; @@ -788,6 +826,12 @@ static int iio_disable_buffers(struct iio_dev *indio_dev) ret = ret2; } + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) { + ret2 = iio_buffer_disable(buffer, indio_dev); + if (ret2 && !ret) + ret = ret2; + } + indio_dev->currentmode = INDIO_DIRECT_MODE; if (indio_dev->setup_ops->postdisable) { @@ -974,9 +1018,6 @@ static ssize_t iio_buffer_store_watermark(struct device *dev, } buffer->watermark = val; - - if (indio_dev->info->hwfifo_set_watermark) - indio_dev->info->hwfifo_set_watermark(indio_dev, val); out: mutex_unlock(&indio_dev->mlock); @@ -991,6 +1032,8 @@ static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, iio_buffer_show_enable, iio_buffer_store_enable); static DEVICE_ATTR(watermark, S_IRUGO | S_IWUSR, iio_buffer_show_watermark, iio_buffer_store_watermark); +static struct device_attribute dev_attr_watermark_ro = __ATTR(watermark, + S_IRUGO, iio_buffer_show_watermark, NULL); static struct attribute *iio_buffer_attrs[] = { &dev_attr_length.attr, @@ -1033,6 +1076,9 @@ int iio_buffer_alloc_sysfs_and_mask(struct iio_dev *indio_dev) if (!buffer->access->set_length) attr[0] = &dev_attr_length_ro.attr; + if (buffer->access->flags & INDIO_BUFFER_FLAG_FIXED_WATERMARK) + attr[2] = &dev_attr_watermark_ro.attr; + if (buffer->attrs) memcpy(&attr[ARRAY_SIZE(iio_buffer_attrs)], buffer->attrs, sizeof(struct attribute *) * attrcount); diff --git a/drivers/iio/industrialio-configfs.c b/drivers/iio/industrialio-configfs.c new file mode 100644 index 000000000..45ce2bc47 --- /dev/null +++ b/drivers/iio/industrialio-configfs.c @@ -0,0 +1,51 @@ +/* + * Industrial I/O configfs bits + * + * Copyright (c) 2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/configfs.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kmod.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/configfs.h> + +static struct config_item_type iio_root_group_type = { + .ct_owner = THIS_MODULE, +}; + +struct configfs_subsystem iio_configfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "iio", + .ci_type = &iio_root_group_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(iio_configfs_subsys.su_mutex), +}; +EXPORT_SYMBOL(iio_configfs_subsys); + +static int __init iio_configfs_init(void) +{ + config_group_init(&iio_configfs_subsys.su_group); + + return configfs_register_subsystem(&iio_configfs_subsys); +} +module_init(iio_configfs_init); + +static void __exit iio_configfs_exit(void) +{ + configfs_unregister_subsystem(&iio_configfs_subsys); +} +module_exit(iio_configfs_exit); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("Industrial I/O configfs support"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 159ede61f..af7cc1e65 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -433,16 +433,15 @@ ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals) scale_db = true; case IIO_VAL_INT_PLUS_MICRO: if (vals[1] < 0) - return sprintf(buf, "-%ld.%06u%s\n", abs(vals[0]), - -vals[1], - scale_db ? " dB" : ""); + return sprintf(buf, "-%d.%06u%s\n", abs(vals[0]), + -vals[1], scale_db ? " dB" : ""); else return sprintf(buf, "%d.%06u%s\n", vals[0], vals[1], scale_db ? " dB" : ""); case IIO_VAL_INT_PLUS_NANO: if (vals[1] < 0) - return sprintf(buf, "-%ld.%09u\n", abs(vals[0]), - -vals[1]); + return sprintf(buf, "-%d.%09u\n", abs(vals[0]), + -vals[1]); else return sprintf(buf, "%d.%09u\n", vals[0], vals[1]); case IIO_VAL_FRACTIONAL: @@ -470,6 +469,7 @@ ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals) return 0; } } +EXPORT_SYMBOL_GPL(iio_format_value); static ssize_t iio_read_channel_info(struct device *dev, struct device_attribute *attr, @@ -512,6 +512,12 @@ int iio_str_to_fixpoint(const char *str, int fract_mult, int i = 0, f = 0; bool integer_part = true, negative = false; + if (fract_mult == 0) { + *fract = 0; + + return kstrtoint(str, 0, integer); + } + if (str[0] == '-') { negative = true; str++; @@ -571,6 +577,9 @@ static ssize_t iio_write_channel_info(struct device *dev, if (indio_dev->info->write_raw_get_fmt) switch (indio_dev->info->write_raw_get_fmt(indio_dev, this_attr->c, this_attr->address)) { + case IIO_VAL_INT: + fract_mult = 0; + break; case IIO_VAL_INT_PLUS_MICRO: fract_mult = 100000; break; diff --git a/drivers/iio/industrialio-sw-trigger.c b/drivers/iio/industrialio-sw-trigger.c new file mode 100644 index 000000000..8d24fb159 --- /dev/null +++ b/drivers/iio/industrialio-sw-trigger.c @@ -0,0 +1,182 @@ +/* + * The Industrial I/O core, software trigger functions + * + * Copyright (c) 2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kmod.h> +#include <linux/list.h> +#include <linux/slab.h> + +#include <linux/iio/sw_trigger.h> +#include <linux/iio/configfs.h> +#include <linux/configfs.h> + +static struct config_group *iio_triggers_group; +static struct config_item_type iio_trigger_type_group_type; + +static struct config_item_type iio_triggers_group_type = { + .ct_owner = THIS_MODULE, +}; + +static LIST_HEAD(iio_trigger_types_list); +static DEFINE_MUTEX(iio_trigger_types_lock); + +static +struct iio_sw_trigger_type *__iio_find_sw_trigger_type(const char *name, + unsigned len) +{ + struct iio_sw_trigger_type *t = NULL, *iter; + + list_for_each_entry(iter, &iio_trigger_types_list, list) + if (!strcmp(iter->name, name)) { + t = iter; + break; + } + + return t; +} + +int iio_register_sw_trigger_type(struct iio_sw_trigger_type *t) +{ + struct iio_sw_trigger_type *iter; + int ret = 0; + + mutex_lock(&iio_trigger_types_lock); + iter = __iio_find_sw_trigger_type(t->name, strlen(t->name)); + if (iter) + ret = -EBUSY; + else + list_add_tail(&t->list, &iio_trigger_types_list); + mutex_unlock(&iio_trigger_types_lock); + + if (ret) + return ret; + + t->group = configfs_register_default_group(iio_triggers_group, t->name, + &iio_trigger_type_group_type); + if (IS_ERR(t->group)) + ret = PTR_ERR(t->group); + + return ret; +} +EXPORT_SYMBOL(iio_register_sw_trigger_type); + +void iio_unregister_sw_trigger_type(struct iio_sw_trigger_type *t) +{ + struct iio_sw_trigger_type *iter; + + mutex_lock(&iio_trigger_types_lock); + iter = __iio_find_sw_trigger_type(t->name, strlen(t->name)); + if (iter) + list_del(&t->list); + mutex_unlock(&iio_trigger_types_lock); + + configfs_unregister_default_group(t->group); +} +EXPORT_SYMBOL(iio_unregister_sw_trigger_type); + +static +struct iio_sw_trigger_type *iio_get_sw_trigger_type(const char *name) +{ + struct iio_sw_trigger_type *t; + + mutex_lock(&iio_trigger_types_lock); + t = __iio_find_sw_trigger_type(name, strlen(name)); + if (t && !try_module_get(t->owner)) + t = NULL; + mutex_unlock(&iio_trigger_types_lock); + + return t; +} + +struct iio_sw_trigger *iio_sw_trigger_create(const char *type, const char *name) +{ + struct iio_sw_trigger *t; + struct iio_sw_trigger_type *tt; + + tt = iio_get_sw_trigger_type(type); + if (!tt) { + pr_err("Invalid trigger type: %s\n", type); + return ERR_PTR(-EINVAL); + } + t = tt->ops->probe(name); + if (IS_ERR(t)) + goto out_module_put; + + t->trigger_type = tt; + + return t; +out_module_put: + module_put(tt->owner); + return t; +} +EXPORT_SYMBOL(iio_sw_trigger_create); + +void iio_sw_trigger_destroy(struct iio_sw_trigger *t) +{ + struct iio_sw_trigger_type *tt = t->trigger_type; + + tt->ops->remove(t); + module_put(tt->owner); +} +EXPORT_SYMBOL(iio_sw_trigger_destroy); + +static struct config_group *trigger_make_group(struct config_group *group, + const char *name) +{ + struct iio_sw_trigger *t; + + t = iio_sw_trigger_create(group->cg_item.ci_name, name); + if (IS_ERR(t)) + return ERR_CAST(t); + + config_item_set_name(&t->group.cg_item, "%s", name); + + return &t->group; +} + +static void trigger_drop_group(struct config_group *group, + struct config_item *item) +{ + struct iio_sw_trigger *t = to_iio_sw_trigger(item); + + iio_sw_trigger_destroy(t); + config_item_put(item); +} + +static struct configfs_group_operations trigger_ops = { + .make_group = &trigger_make_group, + .drop_item = &trigger_drop_group, +}; + +static struct config_item_type iio_trigger_type_group_type = { + .ct_group_ops = &trigger_ops, + .ct_owner = THIS_MODULE, +}; + +static int __init iio_sw_trigger_init(void) +{ + iio_triggers_group = + configfs_register_default_group(&iio_configfs_subsys.su_group, + "triggers", + &iio_triggers_group_type); + return PTR_ERR_OR_ZERO(iio_triggers_group); +} +module_init(iio_sw_trigger_init); + +static void __exit iio_sw_trigger_exit(void) +{ + configfs_unregister_default_group(iio_triggers_group); +} +module_exit(iio_sw_trigger_exit); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("Industrial I/O software triggers support"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c index 217e9306a..734a0042d 100644 --- a/drivers/iio/inkern.c +++ b/drivers/iio/inkern.c @@ -61,12 +61,10 @@ EXPORT_SYMBOL_GPL(iio_map_array_register); int iio_map_array_unregister(struct iio_dev *indio_dev) { int ret = -ENODEV; - struct iio_map_internal *mapi; - struct list_head *pos, *tmp; + struct iio_map_internal *mapi, *next; mutex_lock(&iio_map_list_lock); - list_for_each_safe(pos, tmp, &iio_map_list) { - mapi = list_entry(pos, struct iio_map_internal, l); + list_for_each_entry_safe(mapi, next, &iio_map_list, l) { if (indio_dev == mapi->indio_dev) { list_del(&mapi->l); kfree(mapi); diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c index 076bc46fa..e56937c40 100644 --- a/drivers/iio/light/lm3533-als.c +++ b/drivers/iio/light/lm3533-als.c @@ -743,8 +743,10 @@ static int lm3533_als_set_resistor(struct lm3533_als *als, u8 val) { int ret; - if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX) + if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX) { + dev_err(&als->pdev->dev, "invalid resistor value\n"); return -EINVAL; + }; ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val); if (ret) { diff --git a/drivers/iio/light/pa12203001.c b/drivers/iio/light/pa12203001.c index 45f7bde02..76a9e12b4 100644 --- a/drivers/iio/light/pa12203001.c +++ b/drivers/iio/light/pa12203001.c @@ -381,17 +381,23 @@ static int pa12203001_probe(struct i2c_client *client, return ret; ret = pm_runtime_set_active(&client->dev); - if (ret < 0) { - pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); - return ret; - } + if (ret < 0) + goto out_err; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, PA12203001_SLEEP_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); - return iio_device_register(indio_dev); + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_err; + + return 0; + +out_err: + pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); + return ret; } static int pa12203001_remove(struct i2c_client *client) diff --git a/drivers/iio/light/rpr0521.c b/drivers/iio/light/rpr0521.c index 4b75bb099..7de0f3971 100644 --- a/drivers/iio/light/rpr0521.c +++ b/drivers/iio/light/rpr0521.c @@ -507,34 +507,28 @@ static int rpr0521_probe(struct i2c_client *client, dev_err(&client->dev, "rpr0521 chip init failed\n"); return ret; } - ret = iio_device_register(indio_dev); - if (ret < 0) - return ret; ret = pm_runtime_set_active(&client->dev); if (ret < 0) - goto err_iio_unregister; + return ret; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, RPR0521_SLEEP_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); - return 0; - -err_iio_unregister: - iio_device_unregister(indio_dev); - return ret; + return iio_device_register(indio_dev); } static int rpr0521_remove(struct i2c_client *client) { struct iio_dev *indio_dev = i2c_get_clientdata(client); + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(indio_dev); rpr0521_poweroff(iio_priv(indio_dev)); return 0; diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c index 49dab3cb3..45bc2f742 100644 --- a/drivers/iio/light/us5182d.c +++ b/drivers/iio/light/us5182d.c @@ -20,14 +20,21 @@ #include <linux/acpi.h> #include <linux/delay.h> #include <linux/i2c.h> +#include <linux/iio/events.h> #include <linux/iio/iio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> #include <linux/iio/sysfs.h> #include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> #define US5182D_REG_CFG0 0x00 #define US5182D_CFG0_ONESHOT_EN BIT(6) #define US5182D_CFG0_SHUTDOWN_EN BIT(7) #define US5182D_CFG0_WORD_ENABLE BIT(0) +#define US5182D_CFG0_PROX BIT(3) +#define US5182D_CFG0_PX_IRQ BIT(2) #define US5182D_REG_CFG1 0x01 #define US5182D_CFG1_ALS_RES16 BIT(4) @@ -39,6 +46,7 @@ #define US5182D_REG_CFG3 0x03 #define US5182D_CFG3_LED_CURRENT100 (BIT(4) | BIT(5)) +#define US5182D_CFG3_INT_SOURCE_PX BIT(3) #define US5182D_REG_CFG4 0x10 @@ -53,6 +61,13 @@ #define US5182D_REG_AUTO_LDARK_GAIN 0x29 #define US5182D_REG_AUTO_HDARK_GAIN 0x2a +/* Thresholds for events: px low (0x08-l, 0x09-h), px high (0x0a-l 0x0b-h) */ +#define US5182D_REG_PXL_TH 0x08 +#define US5182D_REG_PXH_TH 0x0a + +#define US5182D_REG_PXL_TH_DEFAULT 1000 +#define US5182D_REG_PXH_TH_DEFAULT 30000 + #define US5182D_OPMODE_ALS 0x01 #define US5182D_OPMODE_PX 0x02 #define US5182D_OPMODE_SHIFT 4 @@ -81,6 +96,9 @@ #define US5182D_READ_BYTE 1 #define US5182D_READ_WORD 2 #define US5182D_OPSTORE_SLEEP_TIME 20 /* ms */ +#define US5182D_SLEEP_MS 3000 /* ms */ +#define US5182D_PXH_TH_DISABLE 0xffff +#define US5182D_PXL_TH_DISABLE 0x0000 /* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */ static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600, @@ -99,6 +117,11 @@ enum mode { US5182D_PX_ONLY }; +enum pmode { + US5182D_CONTINUOUS, + US5182D_ONESHOT +}; + struct us5182d_data { struct i2c_client *client; struct mutex lock; @@ -111,7 +134,19 @@ struct us5182d_data { u8 upper_dark_gain; u16 *us5182d_dark_ths; + u16 px_low_th; + u16 px_high_th; + + int rising_en; + int falling_en; + u8 opmode; + u8 power_mode; + + bool als_enabled; + bool px_enabled; + + bool default_continuous; }; static IIO_CONST_ATTR(in_illuminance_scale_available, @@ -130,16 +165,30 @@ static const struct { u8 reg; u8 val; } us5182d_regvals[] = { - {US5182D_REG_CFG0, (US5182D_CFG0_SHUTDOWN_EN | - US5182D_CFG0_WORD_ENABLE)}, + {US5182D_REG_CFG0, US5182D_CFG0_WORD_ENABLE}, {US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16}, {US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 | US5182D_CFG2_PXGAIN_DEFAULT)}, - {US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100}, - {US5182D_REG_MODE_STORE, US5182D_STORE_MODE}, + {US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100 | + US5182D_CFG3_INT_SOURCE_PX}, {US5182D_REG_CFG4, 0x00}, }; +static const struct iio_event_spec us5182d_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + static const struct iio_chan_spec us5182d_channels[] = { { .type = IIO_LIGHT, @@ -149,40 +198,39 @@ static const struct iio_chan_spec us5182d_channels[] = { { .type = IIO_PROXIMITY, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = us5182d_events, + .num_event_specs = ARRAY_SIZE(us5182d_events), } }; -static int us5182d_get_als(struct us5182d_data *data) +static int us5182d_oneshot_en(struct us5182d_data *data) { int ret; - unsigned long result; - ret = i2c_smbus_read_word_data(data->client, - US5182D_REG_ADL); + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); if (ret < 0) return ret; - result = ret * data->ga / US5182D_GA_RESOLUTION; - if (result > 0xffff) - result = 0xffff; + /* + * In oneshot mode the chip will power itself down after taking the + * required measurement. + */ + ret = ret | US5182D_CFG0_ONESHOT_EN; - return result; + return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); } static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) { int ret; + if (mode == data->opmode) + return 0; + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); if (ret < 0) return ret; - /* - * In oneshot mode the chip will power itself down after taking the - * required measurement. - */ - ret = ret | US5182D_CFG0_ONESHOT_EN; - /* update mode */ ret = ret & ~US5182D_OPMODE_MASK; ret = ret | (mode << US5182D_OPMODE_SHIFT); @@ -196,9 +244,6 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) if (ret < 0) return ret; - if (mode == data->opmode) - return 0; - ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_MODE_STORE, US5182D_STORE_MODE); if (ret < 0) @@ -210,6 +255,177 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) return 0; } +static int us5182d_als_enable(struct us5182d_data *data) +{ + int ret; + u8 mode; + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_set_opmode(data, US5182D_ALS_ONLY); + if (ret < 0) + return ret; + data->px_enabled = false; + } + + if (data->als_enabled) + return 0; + + mode = data->px_enabled ? US5182D_ALS_PX : US5182D_ALS_ONLY; + + ret = us5182d_set_opmode(data, mode); + if (ret < 0) + return ret; + + data->als_enabled = true; + + return 0; +} + +static int us5182d_px_enable(struct us5182d_data *data) +{ + int ret; + u8 mode; + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_set_opmode(data, US5182D_PX_ONLY); + if (ret < 0) + return ret; + data->als_enabled = false; + } + + if (data->px_enabled) + return 0; + + mode = data->als_enabled ? US5182D_ALS_PX : US5182D_PX_ONLY; + + ret = us5182d_set_opmode(data, mode); + if (ret < 0) + return ret; + + data->px_enabled = true; + + return 0; +} + +static int us5182d_get_als(struct us5182d_data *data) +{ + int ret; + unsigned long result; + + ret = us5182d_als_enable(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(data->client, + US5182D_REG_ADL); + if (ret < 0) + return ret; + + result = ret * data->ga / US5182D_GA_RESOLUTION; + if (result > 0xffff) + result = 0xffff; + + return result; +} + +static int us5182d_get_px(struct us5182d_data *data) +{ + int ret; + + ret = us5182d_px_enable(data); + if (ret < 0) + return ret; + + return i2c_smbus_read_word_data(data->client, + US5182D_REG_PDL); +} + +static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) +{ + int ret; + + if (data->power_mode == US5182D_ONESHOT) + return 0; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) + return ret; + + ret = ret & ~US5182D_CFG0_SHUTDOWN_EN; + ret = ret | state; + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); + if (ret < 0) + return ret; + + if (state & US5182D_CFG0_SHUTDOWN_EN) { + data->als_enabled = false; + data->px_enabled = false; + } + + return ret; +} + + +static int us5182d_set_power_state(struct us5182d_data *data, bool on) +{ + int ret; + + if (data->power_mode == US5182D_ONESHOT) + return 0; + + if (on) { + ret = pm_runtime_get_sync(&data->client->dev); + if (ret < 0) + pm_runtime_put_noidle(&data->client->dev); + } else { + pm_runtime_mark_last_busy(&data->client->dev); + ret = pm_runtime_put_autosuspend(&data->client->dev); + } + + return ret; +} + +static int us5182d_read_value(struct us5182d_data *data, + struct iio_chan_spec const *chan) +{ + int ret, value; + + mutex_lock(&data->lock); + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_oneshot_en(data); + if (ret < 0) + goto out_err; + } + + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto out_err; + + if (chan->type == IIO_LIGHT) + ret = us5182d_get_als(data); + else + ret = us5182d_get_px(data); + if (ret < 0) + goto out_poweroff; + + value = ret; + + ret = us5182d_set_power_state(data, false); + if (ret < 0) + goto out_err; + + mutex_unlock(&data->lock); + return value; + +out_poweroff: + us5182d_set_power_state(data, false); +out_err: + mutex_unlock(&data->lock); + return ret; +} + static int us5182d_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -219,53 +435,21 @@ static int us5182d_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: - switch (chan->type) { - case IIO_LIGHT: - mutex_lock(&data->lock); - ret = us5182d_set_opmode(data, US5182D_OPMODE_ALS); - if (ret < 0) - goto out_err; - - ret = us5182d_get_als(data); - if (ret < 0) - goto out_err; - mutex_unlock(&data->lock); - *val = ret; - return IIO_VAL_INT; - case IIO_PROXIMITY: - mutex_lock(&data->lock); - ret = us5182d_set_opmode(data, US5182D_OPMODE_PX); - if (ret < 0) - goto out_err; - - ret = i2c_smbus_read_word_data(data->client, - US5182D_REG_PDL); - if (ret < 0) - goto out_err; - mutex_unlock(&data->lock); - *val = ret; - return IIO_VAL_INT; - default: - return -EINVAL; - } - + ret = us5182d_read_value(data, chan); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1); if (ret < 0) return ret; - *val = 0; *val2 = us5182d_scales[ret & US5182D_AGAIN_MASK]; - return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } - - return -EINVAL; -out_err: - mutex_unlock(&data->lock); - return ret; } /** @@ -343,11 +527,201 @@ static int us5182d_write_raw(struct iio_dev *indio_dev, return -EINVAL; } +static int us5182d_setup_prox(struct iio_dev *indio_dev, + enum iio_event_direction dir, u16 val) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + if (dir == IIO_EV_DIR_FALLING) + return i2c_smbus_write_word_data(data->client, + US5182D_REG_PXL_TH, val); + else if (dir == IIO_EV_DIR_RISING) + return i2c_smbus_write_word_data(data->client, + US5182D_REG_PXH_TH, val); + + return 0; +} + +static int us5182d_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + *val = data->px_high_th; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + *val = data->px_low_th; + mutex_unlock(&data->lock); + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int us5182d_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + if (val < 0 || val > USHRT_MAX || val2 != 0) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + if (data->rising_en) { + ret = us5182d_setup_prox(indio_dev, dir, val); + if (ret < 0) + goto err; + } + data->px_high_th = val; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + if (data->falling_en) { + ret = us5182d_setup_prox(indio_dev, dir, val); + if (ret < 0) + goto err; + } + data->px_low_th = val; + mutex_unlock(&data->lock); + break; + default: + return -EINVAL; + } + + return 0; +err: + mutex_unlock(&data->lock); + return ret; +} + +static int us5182d_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + ret = data->rising_en; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + ret = data->falling_en; + mutex_unlock(&data->lock); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int us5182d_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, int state) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + u16 new_th; + + mutex_lock(&data->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + if (data->rising_en == state) { + mutex_unlock(&data->lock); + return 0; + } + new_th = US5182D_PXH_TH_DISABLE; + if (state) { + data->power_mode = US5182D_CONTINUOUS; + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto err; + ret = us5182d_px_enable(data); + if (ret < 0) + goto err_poweroff; + new_th = data->px_high_th; + } + ret = us5182d_setup_prox(indio_dev, dir, new_th); + if (ret < 0) + goto err_poweroff; + data->rising_en = state; + break; + case IIO_EV_DIR_FALLING: + if (data->falling_en == state) { + mutex_unlock(&data->lock); + return 0; + } + new_th = US5182D_PXL_TH_DISABLE; + if (state) { + data->power_mode = US5182D_CONTINUOUS; + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto err; + ret = us5182d_px_enable(data); + if (ret < 0) + goto err_poweroff; + new_th = data->px_low_th; + } + ret = us5182d_setup_prox(indio_dev, dir, new_th); + if (ret < 0) + goto err_poweroff; + data->falling_en = state; + break; + default: + ret = -EINVAL; + goto err; + } + + if (!state) { + ret = us5182d_set_power_state(data, false); + if (ret < 0) + goto err; + } + + if (!data->falling_en && !data->rising_en && !data->default_continuous) + data->power_mode = US5182D_ONESHOT; + + mutex_unlock(&data->lock); + return 0; + +err_poweroff: + if (state) + us5182d_set_power_state(data, false); +err: + mutex_unlock(&data->lock); + return ret; +} + static const struct iio_info us5182d_info = { .driver_module = THIS_MODULE, .read_raw = us5182d_read_raw, .write_raw = us5182d_write_raw, .attrs = &us5182d_attr_group, + .read_event_value = &us5182d_read_thresh, + .write_event_value = &us5182d_write_thresh, + .read_event_config = &us5182d_read_event_config, + .write_event_config = &us5182d_write_event_config, }; static int us5182d_reset(struct iio_dev *indio_dev) @@ -368,6 +742,10 @@ static int us5182d_init(struct iio_dev *indio_dev) return ret; data->opmode = 0; + data->power_mode = US5182D_CONTINUOUS; + data->px_low_th = US5182D_REG_PXL_TH_DEFAULT; + data->px_high_th = US5182D_REG_PXH_TH_DEFAULT; + for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) { ret = i2c_smbus_write_byte_data(data->client, us5182d_regvals[i].reg, @@ -376,7 +754,17 @@ static int us5182d_init(struct iio_dev *indio_dev) return ret; } - return 0; + data->als_enabled = true; + data->px_enabled = true; + + if (!data->default_continuous) { + ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + if (ret < 0) + return ret; + data->power_mode = US5182D_ONESHOT; + } + + return ret; } static void us5182d_get_platform_data(struct iio_dev *indio_dev) @@ -399,6 +787,8 @@ static void us5182d_get_platform_data(struct iio_dev *indio_dev) "upisemi,lower-dark-gain", &data->lower_dark_gain)) data->lower_dark_gain = US5182D_REG_AUTO_LDARK_GAIN_DEFAULT; + data->default_continuous = device_property_read_bool(&data->client->dev, + "upisemi,continuous"); } static int us5182d_dark_gain_config(struct iio_dev *indio_dev) @@ -426,6 +816,33 @@ static int us5182d_dark_gain_config(struct iio_dev *indio_dev) US5182D_REG_DARK_AUTO_EN_DEFAULT); } +static irqreturn_t us5182d_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct us5182d_data *data = iio_priv(indio_dev); + enum iio_event_direction dir; + int ret; + u64 ev; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + return IRQ_HANDLED; + } + + dir = ret & US5182D_CFG0_PROX ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, IIO_EV_TYPE_THRESH, dir); + + iio_push_event(indio_dev, ev, iio_get_time_ns()); + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, + ret & ~US5182D_CFG0_PX_IRQ); + if (ret < 0) + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + + return IRQ_HANDLED; +} + static int us5182d_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -457,6 +874,16 @@ static int us5182d_probe(struct i2c_client *client, return (ret < 0) ? ret : -ENODEV; } + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + us5182d_irq_thread_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "us5182d-irq", indio_dev); + if (ret < 0) + return ret; + } else + dev_warn(&client->dev, "no valid irq found\n"); + us5182d_get_platform_data(indio_dev); ret = us5182d_init(indio_dev); if (ret < 0) @@ -464,18 +891,73 @@ static int us5182d_probe(struct i2c_client *client, ret = us5182d_dark_gain_config(indio_dev); if (ret < 0) - return ret; + goto out_err; + + if (data->default_continuous) { + pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_err; + } + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + US5182D_SLEEP_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_err; + + return 0; + +out_err: + us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + return ret; - return iio_device_register(indio_dev); } static int us5182d_remove(struct i2c_client *client) { + struct us5182d_data *data = iio_priv(i2c_get_clientdata(client)); + iio_device_unregister(i2c_get_clientdata(client)); - return i2c_smbus_write_byte_data(client, US5182D_REG_CFG0, - US5182D_CFG0_SHUTDOWN_EN); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); +} + +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM) +static int us5182d_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct us5182d_data *data = iio_priv(indio_dev); + + if (data->power_mode == US5182D_CONTINUOUS) + return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + + return 0; } +static int us5182d_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct us5182d_data *data = iio_priv(indio_dev); + + if (data->power_mode == US5182D_CONTINUOUS) + return us5182d_shutdown_en(data, + ~US5182D_CFG0_SHUTDOWN_EN & 0xff); + + return 0; +} +#endif + +static const struct dev_pm_ops us5182d_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(us5182d_suspend, us5182d_resume) + SET_RUNTIME_PM_OPS(us5182d_suspend, us5182d_resume, NULL) +}; + static const struct acpi_device_id us5182d_acpi_match[] = { { "USD5182", 0}, {} @@ -493,6 +975,7 @@ MODULE_DEVICE_TABLE(i2c, us5182d_id); static struct i2c_driver us5182d_driver = { .driver = { .name = US5182D_DRV_NAME, + .pm = &us5182d_pm_ops, .acpi_match_table = ACPI_PTR(us5182d_acpi_match), }, .probe = us5182d_probe, diff --git a/drivers/iio/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c index 1615b23d7..ffcb75ea6 100644 --- a/drivers/iio/magnetometer/bmc150_magn.c +++ b/drivers/iio/magnetometer/bmc150_magn.c @@ -928,27 +928,24 @@ static int bmc150_magn_probe(struct i2c_client *client, goto err_free_irq; } - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(&client->dev, "unable to register iio device\n"); - goto err_buffer_cleanup; - } - ret = pm_runtime_set_active(&client->dev); if (ret) - goto err_iio_unregister; + goto err_buffer_cleanup; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, BMC150_MAGN_AUTO_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); - dev_dbg(&indio_dev->dev, "Registered device %s\n", name); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + dev_dbg(&indio_dev->dev, "Registered device %s\n", name); return 0; -err_iio_unregister: - iio_device_unregister(indio_dev); err_buffer_cleanup: iio_triggered_buffer_cleanup(indio_dev); err_free_irq: @@ -967,11 +964,12 @@ static int bmc150_magn_remove(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct bmc150_magn_data *data = iio_priv(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(indio_dev); iio_triggered_buffer_cleanup(indio_dev); if (client->irq > 0) diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c index e544fcfd5..db35e04a0 100644 --- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -13,7 +13,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * TODO: runtime pm, interrupt mode, and signal strength reporting + * TODO: interrupt mode, and signal strength reporting */ #include <linux/err.h> @@ -21,6 +21,7 @@ #include <linux/i2c.h> #include <linux/delay.h> #include <linux/module.h> +#include <linux/pm_runtime.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> #include <linux/iio/buffer.h> @@ -35,8 +36,11 @@ #define LIDAR_REG_STATUS_INVALID BIT(3) #define LIDAR_REG_STATUS_READY BIT(0) -#define LIDAR_REG_DATA_HBYTE 0x0f -#define LIDAR_REG_DATA_LBYTE 0x10 +#define LIDAR_REG_DATA_HBYTE 0x0f +#define LIDAR_REG_DATA_LBYTE 0x10 +#define LIDAR_REG_DATA_WORD_READ BIT(7) + +#define LIDAR_REG_PWR_CONTROL 0x65 #define LIDAR_DRV_NAME "lidar" @@ -44,6 +48,9 @@ struct lidar_data { struct iio_dev *indio_dev; struct i2c_client *client; + int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len); + int i2c_enabled; + u16 buffer[8]; /* 2 byte distance + 8 byte timestamp */ }; @@ -62,7 +69,28 @@ static const struct iio_chan_spec lidar_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(1), }; -static int lidar_read_byte(struct lidar_data *data, int reg) +static int lidar_i2c_xfer(struct lidar_data *data, u8 reg, u8 *val, int len) +{ + struct i2c_client *client = data->client; + struct i2c_msg msg[2]; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = client->flags | I2C_M_STOP; + msg[0].len = 1; + msg[0].buf = (char *) ® + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = len; + msg[1].buf = (char *) val; + + ret = i2c_transfer(client->adapter, msg, 2); + + return (ret == 2) ? 0 : -EIO; +} + +static int lidar_smbus_xfer(struct lidar_data *data, u8 reg, u8 *val, int len) { struct i2c_client *client = data->client; int ret; @@ -72,17 +100,35 @@ static int lidar_read_byte(struct lidar_data *data, int reg) * so in turn i2c_smbus_read_byte_data cannot be used */ - ret = i2c_smbus_write_byte(client, reg); - if (ret < 0) { - dev_err(&client->dev, "cannot write addr value"); - return ret; + while (len--) { + ret = i2c_smbus_write_byte(client, reg++); + if (ret < 0) { + dev_err(&client->dev, "cannot write addr value"); + return ret; + } + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(&client->dev, "cannot read data value"); + return ret; + } + + *(val++) = ret; } - ret = i2c_smbus_read_byte(client); + return 0; +} + +static int lidar_read_byte(struct lidar_data *data, u8 reg) +{ + int ret; + u8 val; + + ret = data->xfer(data, reg, &val, 1); if (ret < 0) - dev_err(&client->dev, "cannot read data value"); + return ret; - return ret; + return val; } static inline int lidar_write_control(struct lidar_data *data, int val) @@ -90,24 +136,22 @@ static inline int lidar_write_control(struct lidar_data *data, int val) return i2c_smbus_write_byte_data(data->client, LIDAR_REG_CONTROL, val); } -static int lidar_read_measurement(struct lidar_data *data, u16 *reg) +static inline int lidar_write_power(struct lidar_data *data, int val) { - int ret; - int val; - - ret = lidar_read_byte(data, LIDAR_REG_DATA_HBYTE); - if (ret < 0) - return ret; - val = ret << 8; + return i2c_smbus_write_byte_data(data->client, + LIDAR_REG_PWR_CONTROL, val); +} - ret = lidar_read_byte(data, LIDAR_REG_DATA_LBYTE); - if (ret < 0) - return ret; +static int lidar_read_measurement(struct lidar_data *data, u16 *reg) +{ + int ret = data->xfer(data, LIDAR_REG_DATA_HBYTE | + (data->i2c_enabled ? LIDAR_REG_DATA_WORD_READ : 0), + (u8 *) reg, 2); - val |= ret; - *reg = val; + if (!ret) + *reg = be16_to_cpu(*reg); - return 0; + return ret; } static int lidar_get_measurement(struct lidar_data *data, u16 *reg) @@ -116,6 +160,8 @@ static int lidar_get_measurement(struct lidar_data *data, u16 *reg) int tries = 10; int ret; + pm_runtime_get_sync(&client->dev); + /* start sample */ ret = lidar_write_control(data, LIDAR_REG_CONTROL_ACQUIRE); if (ret < 0) { @@ -144,6 +190,8 @@ static int lidar_get_measurement(struct lidar_data *data, u16 *reg) } ret = -EIO; } + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); return ret; } @@ -221,6 +269,16 @@ static int lidar_probe(struct i2c_client *client, indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; + data = iio_priv(indio_dev); + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + data->xfer = lidar_i2c_xfer; + data->i2c_enabled = 1; + } else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) + data->xfer = lidar_smbus_xfer; + else + return -ENOTSUPP; indio_dev->info = &lidar_info; indio_dev->name = LIDAR_DRV_NAME; @@ -228,7 +286,6 @@ static int lidar_probe(struct i2c_client *client, indio_dev->num_channels = ARRAY_SIZE(lidar_channels); indio_dev->modes = INDIO_DIRECT_MODE; - data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); data->client = client; @@ -243,6 +300,17 @@ static int lidar_probe(struct i2c_client *client, if (ret) goto error_unreg_buffer; + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + + ret = pm_runtime_set_active(&client->dev); + if (ret) + goto error_unreg_buffer; + pm_runtime_enable(&client->dev); + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_idle(&client->dev); + return 0; error_unreg_buffer: @@ -258,6 +326,9 @@ static int lidar_remove(struct i2c_client *client) iio_device_unregister(indio_dev); iio_triggered_buffer_cleanup(indio_dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + return 0; } @@ -273,10 +344,38 @@ static const struct of_device_id lidar_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, lidar_dt_ids); +#ifdef CONFIG_PM +static int lidar_pm_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct lidar_data *data = iio_priv(indio_dev); + + return lidar_write_power(data, 0x0f); +} + +static int lidar_pm_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct lidar_data *data = iio_priv(indio_dev); + int ret = lidar_write_power(data, 0); + + /* regulator and FPGA needs settling time */ + usleep_range(15000, 20000); + + return ret; +} +#endif + +static const struct dev_pm_ops lidar_pm_ops = { + SET_RUNTIME_PM_OPS(lidar_pm_runtime_suspend, + lidar_pm_runtime_resume, NULL) +}; + static struct i2c_driver lidar_driver = { .driver = { .name = LIDAR_DRV_NAME, .of_match_table = of_match_ptr(lidar_dt_ids), + .pm = &lidar_pm_ops, }, .probe = lidar_probe, .remove = lidar_remove, diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig index 79996123a..519e6772f 100644 --- a/drivers/iio/trigger/Kconfig +++ b/drivers/iio/trigger/Kconfig @@ -5,6 +5,16 @@ menu "Triggers - standalone" +config IIO_HRTIMER_TRIGGER + tristate "High resolution timer trigger" + depends on IIO_SW_TRIGGER + help + Provides a frequency based IIO trigger using high resolution + timers as interrupt source. + + To compile this driver as a module, choose M here: the + module will be called iio-trig-hrtimer. + config IIO_INTERRUPT_TRIGGER tristate "Generic interrupt trigger" help diff --git a/drivers/iio/trigger/Makefile b/drivers/iio/trigger/Makefile index 0694daeca..fe06eb564 100644 --- a/drivers/iio/trigger/Makefile +++ b/drivers/iio/trigger/Makefile @@ -3,5 +3,7 @@ # # When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_IIO_HRTIMER_TRIGGER) += iio-trig-hrtimer.o obj-$(CONFIG_IIO_INTERRUPT_TRIGGER) += iio-trig-interrupt.o obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o diff --git a/drivers/iio/trigger/iio-trig-hrtimer.c b/drivers/iio/trigger/iio-trig-hrtimer.c new file mode 100644 index 000000000..5e6d451fe --- /dev/null +++ b/drivers/iio/trigger/iio-trig-hrtimer.c @@ -0,0 +1,193 @@ +/** + * The industrial I/O periodic hrtimer trigger driver + * + * Copyright (C) Intuitive Aerial AB + * Written by Marten Svanfeldt, marten@intuitiveaerial.com + * Copyright (C) 2012, Analog Device Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * Copyright (C) 2015, Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/hrtimer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/sw_trigger.h> + +/* default sampling frequency - 100Hz */ +#define HRTIMER_DEFAULT_SAMPLING_FREQUENCY 100 + +struct iio_hrtimer_info { + struct iio_sw_trigger swt; + struct hrtimer timer; + unsigned long sampling_frequency; + ktime_t period; +}; + +static struct config_item_type iio_hrtimer_type = { + .ct_owner = THIS_MODULE, +}; + +static +ssize_t iio_hrtimer_show_sampling_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig); + + return snprintf(buf, PAGE_SIZE, "%lu\n", info->sampling_frequency); +} + +static +ssize_t iio_hrtimer_store_sampling_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (!val || val > NSEC_PER_SEC) + return -EINVAL; + + info->sampling_frequency = val; + info->period = ktime_set(0, NSEC_PER_SEC / val); + + return len; +} + +static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, + iio_hrtimer_show_sampling_frequency, + iio_hrtimer_store_sampling_frequency); + +static struct attribute *iio_hrtimer_attrs[] = { + &dev_attr_sampling_frequency.attr, + NULL +}; + +static const struct attribute_group iio_hrtimer_attr_group = { + .attrs = iio_hrtimer_attrs, +}; + +static const struct attribute_group *iio_hrtimer_attr_groups[] = { + &iio_hrtimer_attr_group, + NULL +}; + +static enum hrtimer_restart iio_hrtimer_trig_handler(struct hrtimer *timer) +{ + struct iio_hrtimer_info *info; + + info = container_of(timer, struct iio_hrtimer_info, timer); + + hrtimer_forward_now(timer, info->period); + iio_trigger_poll(info->swt.trigger); + + return HRTIMER_RESTART; +} + +static int iio_trig_hrtimer_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_hrtimer_info *trig_info; + + trig_info = iio_trigger_get_drvdata(trig); + + if (state) + hrtimer_start(&trig_info->timer, trig_info->period, + HRTIMER_MODE_REL); + else + hrtimer_cancel(&trig_info->timer); + + return 0; +} + +static const struct iio_trigger_ops iio_hrtimer_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = iio_trig_hrtimer_set_state, +}; + +static struct iio_sw_trigger *iio_trig_hrtimer_probe(const char *name) +{ + struct iio_hrtimer_info *trig_info; + int ret; + + trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); + if (!trig_info) + return ERR_PTR(-ENOMEM); + + trig_info->swt.trigger = iio_trigger_alloc("%s", name); + if (!trig_info->swt.trigger) { + ret = -ENOMEM; + goto err_free_trig_info; + } + + iio_trigger_set_drvdata(trig_info->swt.trigger, trig_info); + trig_info->swt.trigger->ops = &iio_hrtimer_trigger_ops; + trig_info->swt.trigger->dev.groups = iio_hrtimer_attr_groups; + + hrtimer_init(&trig_info->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + trig_info->timer.function = iio_hrtimer_trig_handler; + + trig_info->sampling_frequency = HRTIMER_DEFAULT_SAMPLING_FREQUENCY; + trig_info->period = ktime_set(0, NSEC_PER_SEC / + trig_info->sampling_frequency); + + ret = iio_trigger_register(trig_info->swt.trigger); + if (ret) + goto err_free_trigger; + + iio_swt_group_init_type_name(&trig_info->swt, name, &iio_hrtimer_type); + return &trig_info->swt; +err_free_trigger: + iio_trigger_free(trig_info->swt.trigger); +err_free_trig_info: + kfree(trig_info); + + return ERR_PTR(ret); +} + +static int iio_trig_hrtimer_remove(struct iio_sw_trigger *swt) +{ + struct iio_hrtimer_info *trig_info; + + trig_info = iio_trigger_get_drvdata(swt->trigger); + + iio_trigger_unregister(swt->trigger); + + /* cancel the timer after unreg to make sure no one rearms it */ + hrtimer_cancel(&trig_info->timer); + iio_trigger_free(swt->trigger); + kfree(trig_info); + + return 0; +} + +static const struct iio_sw_trigger_ops iio_trig_hrtimer_ops = { + .probe = iio_trig_hrtimer_probe, + .remove = iio_trig_hrtimer_remove, +}; + +static struct iio_sw_trigger_type iio_trig_hrtimer = { + .name = "hrtimer", + .owner = THIS_MODULE, + .ops = &iio_trig_hrtimer_ops, +}; + +module_iio_sw_trigger_driver(iio_trig_hrtimer); + +MODULE_AUTHOR("Marten Svanfeldt <marten@intuitiveaerial.com>"); +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("Periodic hrtimer trigger for the IIO subsystem"); +MODULE_LICENSE("GPL v2"); |