diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-12-01 20:13:29 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-12-01 20:13:29 +0300 |
commit | f3cf3fb7ec854c2b2429e5bb23186746e6511dae (patch) | |
tree | b4e71d2a6422ca33a95f9a1e0458ab90c3b89846 /drivers/iio | |
parent | f84a187019ccfce97fbf38fa9f3a8261fef91d8e (diff) | |
parent | 4ac4e086fd8c59e6b69089e6f7605500b63a6d17 (diff) | |
download | linux-f3cf3fb7ec854c2b2429e5bb23186746e6511dae.tar.xz |
Merge tag 'iio-for-4.5a' of git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio into staging-next
Jonathan writes:
First set of new device support, features and cleanups for IIO in the 4.5 cycle
Usual mixed bag, but the big item perhaps in this series is the DMA buffer
support added by Lars-Peter Clausen. It's been in the works for a long time
and it will be interesting to see what hardware support shows up now that
this is available.
New core features + associate cleanup.
* Add generic DMA buffer infrastructure
* Add a DMAengine framework based buffer
Also associated minor changes.
- Set the device buffer watermark based on the minimum watermark for all
attached buffers rather than just the 'primary' one.
- iio_buffer_init - only set the watermark default if one hasn't already
been provided. This allows simple support for devices with a fixed
watermark.
- read only attribute for watermark on fixed watermark devices.
- add explicit buffer enable/disable callbacks to allow the buffer to
do more than trivial actions when it is being turned on and off.
* IIO_VAL_INT support in write_raw_get_fmt function.
New device support
* Freescale MMA7455/7456L accelerometers
* Memsic MXC6255XC accelerometer
* ST lis2dh12 accelerometer
* TI ADS8688 ADC
* TI Palamas (twl6035/7) gpadc
New driver features
* mma8452
- support either of the available interrupt pins to cope with the case
where board layout has lead to a particular one being connected.
Staging graduation
* Dummy driver
- this driver acts as both an example and a test device for those with
out hardware to develop userspace code against.
Cleanups and minor bits and bobs.
* treewide
- Sort out the ordering of iio_device_register/unregister vs runtime
pm function calls so that it's all nice and consistent and not race
prone.
- Check sscanf return values. None of the cases will actually happen as
the strings are supplied internally, but best to be consistent on this.
* ad7780
- switch over to the gpio descriptor interface and remove the now unused
platform data which gets rid of a header entirely.
* ad7793
- drop a pointless else statement.
* at91_adc
- Swap kmalloc_array in for a kmalloc doing the same job.
* dummy
- get rid of some commented out lines that snuck in during the move of
the driver.
* lm3533-als
- Print an error message on provision of an invalid resistance.
* mcp320x
- Add compatible strings with vendor prefix and deprecate those with
no vendor prefix.
* mxs-lradc
- Use BIT macro in various places rather than shifted ones.
* pa12203001
- Power off the chip if the registration fails.
* pulsedlight-lidar-lite
- add runtime PM support.
* xilinx XADC
- constify an iio_buffer_setup_ops structure.
Diffstat (limited to 'drivers/iio')
50 files changed, 4897 insertions, 115 deletions
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 66792e707d74..6b8c77c97d40 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -50,6 +50,7 @@ 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/humidity/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index aeca7269fe44..6769f2f43e86 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -16,6 +16,7 @@ obj-y += buffer/ obj-y += chemical/ obj-y += common/ obj-y += dac/ +obj-y += dummy/ obj-y += gyro/ obj-y += frequency/ obj-y += humidity/ diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 969428dd6329..edc29b173f6c 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 7925f166e6e9..71b6794de885 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 2d33f1e821db..c73331f7782b 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 18c1b06684c1..edec1d099e91 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 000000000000..2b1152c53d4f --- /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 000000000000..c633cc2c0789 --- /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 000000000000..3cab5fb4a3c4 --- /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 000000000000..79df8f27cf99 --- /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 1eccc2dcf14c..116a6e401a6a 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)) @@ -1130,13 +1131,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 7db7cc0bf362..d899a4d4307f 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 9408ef3add58..fa7d36217c4b 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 000000000000..97ccde722e7b --- /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 468f21fa2950..5d4a1897b293 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 197a08b4e2f3..70f042797f15 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 8b9cc84fd44f..294a32f89367 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 f71b0d391272..fcd5847a3fd3 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 7868c744fd4b..9162dfefff30 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -275,6 +275,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 @@ -333,6 +341,16 @@ config TI_ADC128S052 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 99b37a963a1e..91a65bf11c58 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -27,11 +27,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 4d960d3b93c0..7b07bb651671 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 7b40925dd4ff..f284cd6a93d6 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/mcp320x.c b/drivers/iio/adc/mcp320x.c index 8569c8e1f4b2..d1c05f6eed18 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/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c new file mode 100644 index 000000000000..f42eb8a7d21f --- /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-ads8688.c b/drivers/iio/adc/ti-ads8688.c new file mode 100644 index 000000000000..03e907028cb6 --- /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 02e636a1c49a..0a6beb3d99cb 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 0a7b2fd3699b..4ffd3db7817f 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 4d193b9a9123..85beaae831ae 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 000000000000..212cbedc7abb --- /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 000000000000..ebdb838d3a1c --- /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/dummy/Kconfig b/drivers/iio/dummy/Kconfig new file mode 100644 index 000000000000..e8676aa97d62 --- /dev/null +++ b/drivers/iio/dummy/Kconfig @@ -0,0 +1,35 @@ +# +# Industrial I/O subsystem Dummy Driver configuration +# +menu "IIO dummy driver" + depends on IIO + +config IIO_DUMMY_EVGEN + 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 000000000000..0765e93d7804 --- /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 000000000000..9e83f348df51 --- /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 000000000000..d044b946e74a --- /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 000000000000..43fe4ba7d0dc --- /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 000000000000..b9069a180672 --- /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 000000000000..cf44a6f79431 --- /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 000000000000..bfbf1c56bd22 --- /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_HANDLED; +} + +/** + * 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 f8d1c2210066..b04faf93e1bc 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 02ff789852a0..bbce3b09ac45 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/imu/adis16400_core.c b/drivers/iio/imu/adis16400_core.c index 0618f831ecd4..fb7c0dbed51c 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 2485b88ee1b6..8cf84d3488b2 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/kmx61.c b/drivers/iio/imu/kmx61.c index dbf5e9936635..e5306b4e020e 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 d7e908acb480..a4b164a478c4 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-core.c b/drivers/iio/industrialio-core.c index 208358f9e7e3..d0a84febd435 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -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/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c index 076bc46fad03..e56937c40a18 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 45f7bde02bbf..76a9e12b46bc 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 4b75bb0998b3..7de0f397194b 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/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c index 1615b23d7b2a..ffcb75ea64fb 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 961f9f990faf..be8ccef735f8 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> @@ -37,6 +38,7 @@ #define LIDAR_REG_DATA_HBYTE 0x0f #define LIDAR_REG_DATA_LBYTE 0x10 +#define LIDAR_REG_PWR_CONTROL 0x65 #define LIDAR_DRV_NAME "lidar" @@ -90,6 +92,12 @@ 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 inline int lidar_write_power(struct lidar_data *data, int val) +{ + return i2c_smbus_write_byte_data(data->client, + LIDAR_REG_PWR_CONTROL, val); +} + static int lidar_read_measurement(struct lidar_data *data, u16 *reg) { int ret; @@ -116,6 +124,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 +154,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; } @@ -243,6 +255,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 +281,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 +299,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, |