This implements buffered reads for the accelerometer data. A buffered IIO device is created containing 3 channels. The device FIFO is used for sample buffering to reduce the IRQ load on the host system. Reading of the device is triggered by a FIFO waterlevel interrupt. The waterlevel settings are dependent on the sampling frequency to leverage IRQ load against responsiveness. Signed-off-by: Paul Geurts <paul_geurts@xxxxxxx> --- drivers/iio/accel/adxl345.h | 2 +- drivers/iio/accel/adxl345_core.c | 387 ++++++++++++++++++++++++++++--- drivers/iio/accel/adxl345_i2c.c | 2 +- drivers/iio/accel/adxl345_spi.c | 2 +- 4 files changed, 363 insertions(+), 30 deletions(-) diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h index 284bd387ce69..269ce69517ce 100644 --- a/drivers/iio/accel/adxl345.h +++ b/drivers/iio/accel/adxl345.h @@ -28,6 +28,6 @@ struct adxl345_chip_info { int uscale; }; -int adxl345_core_probe(struct device *dev, struct regmap *regmap); +int adxl345_core_probe(struct device *dev, struct regmap *regmap, int irq); #endif /* _ADXL345_H_ */ diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index 8bd30a23ed3b..1f38d2287783 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -11,28 +11,50 @@ #include <linux/property.h> #include <linux/regmap.h> #include <linux/units.h> +#include <linux/irq.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 "adxl345.h" #define ADXL345_REG_DEVID 0x00 +#define ADXL345_REG_THRESH_TAP 0x1D #define ADXL345_REG_OFSX 0x1e #define ADXL345_REG_OFSY 0x1f #define ADXL345_REG_OFSZ 0x20 #define ADXL345_REG_OFS_AXIS(index) (ADXL345_REG_OFSX + (index)) #define ADXL345_REG_BW_RATE 0x2C #define ADXL345_REG_POWER_CTL 0x2D +#define ADXL345_REG_INT_ENABLE 0x2E +#define ADXL345_REG_INT_SOURCE 0x30 #define ADXL345_REG_DATA_FORMAT 0x31 #define ADXL345_REG_DATAX0 0x32 #define ADXL345_REG_DATAY0 0x34 #define ADXL345_REG_DATAZ0 0x36 #define ADXL345_REG_DATA_AXIS(index) \ (ADXL345_REG_DATAX0 + (index) * sizeof(__le16)) +#define ADXL345_REG_FIFO_CTL 0x38 +#define ADXL345_REG_FIFO_STATUS 0x39 #define ADXL345_BW_RATE GENMASK(3, 0) #define ADXL345_BASE_RATE_NANO_HZ 97656250LL +#define ADXL345_MAX_RATE_NANO_HZ (3200LL * NANOHZ_PER_HZ) +#define ADXL345_MAX_BUFFERED_RATE 400L +#define ADXL345_DEFAULT_RATE (100LL * NANOHZ_PER_HZ) + +#define ADXL345_INT_DATA_READY BIT(7) +#define ADXL345_INT_SINGLE_TAP BIT(6) +#define ADXL345_INT_DOUBLE_TAP BIT(5) +#define ADXL345_INT_ACTIVITY BIT(4) +#define ADXL345_INT_INACTIVITY BIT(3) +#define ADXL345_INT_FREE_FALL BIT(2) +#define ADXL345_INT_WATERMARK BIT(1) +#define ADXL345_INT_OVERRUN BIT(0) #define ADXL345_POWER_CTL_MEASURE BIT(3) #define ADXL345_POWER_CTL_STANDBY 0x00 @@ -43,12 +65,30 @@ #define ADXL345_DATA_FORMAT_8G 2 #define ADXL345_DATA_FORMAT_16G 3 -#define ADXL345_DEVID 0xE5 +#define ADXL345_FIFO_CTL_MODE_FIFO BIT(6) +#define ADXL345_FIFO_CTL_MODE_STREAM BIT(7) +#define ADXL345_FIFO_CTL_MODE_TRIGGER (BIT(6) | BIT(7)) +#define ADXL345_FIFO_CTL_SAMPLES_MASK GENMASK(4, 0) +#define ADXL345_FIFO_CTL_SAMPLES(n) ((n) & ADXL345_FIFO_CTL_SAMPLES_MASK) +#define ADXL345_FIFO_MAX_FREQ_WATERLEVEL 20 + +#define ADXL345_DEVID 0xE5 + +#define ADXL345_SCAN_SIZE (sizeof(__le16) * 3) struct adxl345_data { const struct adxl345_chip_info *info; struct regmap *regmap; - u8 data_range; + int irq; + + struct iio_trigger *drdy_trig; + /* + * This lock is for protecting the consistency of series of i2c operations, that is, to + * make sure a measurement process will not be interrupted by a set frequency operation, + * which should be taken where a series of i2c or SPI operations start, released where the + * operation ends. + */ + struct mutex lock; }; #define ADXL345_CHANNEL(index, axis) { \ @@ -60,6 +100,13 @@ struct adxl345_data { BIT(IIO_CHAN_INFO_CALIBBIAS), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 13, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ } static const struct iio_chan_spec adxl345_channels[] = { @@ -68,6 +115,98 @@ static const struct iio_chan_spec adxl345_channels[] = { ADXL345_CHANNEL(2, Z), }; +static const unsigned long adxl345_available_scan_masks[] = { + /* Only allow all axis to be sampled, as this is the only option in HW */ + BIT(0) | BIT(1) | BIT(2), + 0, /* Array should end with 0 */ +}; + +/* Trigger handling */ +static irqreturn_t adxl345_irq_handler(int irq, void *d) +{ + struct iio_dev *indio_dev = d; + struct adxl345_data *data = iio_priv(indio_dev); + int regval, ret; + + ret = regmap_read(data->regmap, ADXL345_REG_INT_SOURCE, ®val); + if (ret < 0) { + dev_err(indio_dev->dev.parent, "Broken IRQ!\n"); + return IRQ_HANDLED; + } + + if (regval & ADXL345_INT_OVERRUN) + dev_err(indio_dev->dev.parent, "FIFO overrun detected! Data lost\n"); + + if (regval & ADXL345_INT_WATERMARK) + iio_trigger_poll_nested(data->drdy_trig); + else + dev_err(indio_dev->dev.parent, "Unexpected IRQ! Source: 0x%02X\n", regval); + + return IRQ_HANDLED; +} + +static int adxl345_get_sampling_freq(struct adxl345_data *data, s64 *freq_nhz) +{ + int ret; + unsigned int regval; + + mutex_lock(&data->lock); + ret = regmap_read(data->regmap, ADXL345_REG_BW_RATE, ®val); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + *freq_nhz = ADXL345_BASE_RATE_NANO_HZ << + (regval & ADXL345_BW_RATE); + + return IIO_VAL_INT_PLUS_NANO; +} + +static int adxl345_set_sampling_freq(struct adxl345_data *data, s64 freq_nhz) +{ + int ret, waterlevel; + s64 n; + + /* Only allow valid sampling rates */ + if (freq_nhz < ADXL345_BASE_RATE_NANO_HZ || freq_nhz > ADXL345_MAX_RATE_NANO_HZ) + return -EINVAL; + + /* + * Trade-off the number of IRQs to the responsiveness on lower sample rates. + * sample rates < 100Hz don't use the FIFO + * 100<>1600Hz issue 100 IRQs per second + * 3200Hz issues 160 IRQs per second + */ + if (freq_nhz < ADXL345_DEFAULT_RATE) + waterlevel = 0; + else if (freq_nhz < ADXL345_MAX_RATE_NANO_HZ) + waterlevel = (freq_nhz / ADXL345_DEFAULT_RATE); + else + waterlevel = ADXL345_FIFO_MAX_FREQ_WATERLEVEL; + + n = div_s64(freq_nhz, ADXL345_BASE_RATE_NANO_HZ); + + /* Disable the IRQ before claiming the mutex to prevent deadlocks */ + if (data->irq) + disable_irq(data->irq); + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, ADXL345_REG_BW_RATE, + ADXL345_BW_RATE, clamp_val(ilog2(n), 0, ADXL345_BW_RATE)); + if (ret < 0) + goto out; + ret = regmap_update_bits(data->regmap, ADXL345_REG_FIFO_CTL, + ADXL345_FIFO_CTL_SAMPLES_MASK, + ADXL345_FIFO_CTL_SAMPLES(waterlevel)); + +out: + mutex_unlock(&data->lock); + if (data->irq) + enable_irq(data->irq); + return ret; +} + +/* Direct mode functions */ static int adxl345_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -80,26 +219,35 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret < 0) + return ret; /* * Data is stored in adjacent registers: * ADXL345_REG_DATA(X0/Y0/Z0) contain the least significant byte * and ADXL345_REG_DATA(X0/Y0/Z0) + 1 the most significant byte */ + mutex_lock(&data->lock); ret = regmap_bulk_read(data->regmap, ADXL345_REG_DATA_AXIS(chan->address), &accel, sizeof(accel)); + mutex_unlock(&data->lock); if (ret < 0) return ret; *val = sign_extend32(le16_to_cpu(accel), 12); + + iio_device_release_direct_mode(indio_dev); return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = 0; *val2 = data->info->uscale; return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&data->lock); ret = regmap_read(data->regmap, ADXL345_REG_OFS_AXIS(chan->address), ®val); + mutex_unlock(&data->lock); if (ret < 0) return ret; /* @@ -110,15 +258,10 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SAMP_FREQ: - ret = regmap_read(data->regmap, ADXL345_REG_BW_RATE, ®val); - if (ret < 0) - return ret; - - samp_freq_nhz = ADXL345_BASE_RATE_NANO_HZ << - (regval & ADXL345_BW_RATE); + ret = adxl345_get_sampling_freq(data, &samp_freq_nhz); *val = div_s64_rem(samp_freq_nhz, NANOHZ_PER_HZ, val2); - return IIO_VAL_INT_PLUS_NANO; + return ret; } return -EINVAL; @@ -129,7 +272,7 @@ static int adxl345_write_raw(struct iio_dev *indio_dev, int val, int val2, long mask) { struct adxl345_data *data = iio_priv(indio_dev); - s64 n; + int ret = -EINVAL; switch (mask) { case IIO_CHAN_INFO_CALIBBIAS: @@ -137,20 +280,17 @@ static int adxl345_write_raw(struct iio_dev *indio_dev, * 8-bit resolution at +/- 2g, that is 4x accel data scale * factor */ - return regmap_write(data->regmap, - ADXL345_REG_OFS_AXIS(chan->address), - val / 4); + mutex_lock(&data->lock); + ret = regmap_write(data->regmap, + ADXL345_REG_OFS_AXIS(chan->address), val / 4); + mutex_unlock(&data->lock); + break; case IIO_CHAN_INFO_SAMP_FREQ: - n = div_s64(val * NANOHZ_PER_HZ + val2, - ADXL345_BASE_RATE_NANO_HZ); - - return regmap_update_bits(data->regmap, ADXL345_REG_BW_RATE, - ADXL345_BW_RATE, - clamp_val(ilog2(n), 0, - ADXL345_BW_RATE)); + ret = adxl345_set_sampling_freq(data, (s64)(val * NANOHZ_PER_HZ + val2)); + break; } - return -EINVAL; + return ret; } static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev, @@ -197,7 +337,149 @@ static void adxl345_powerdown(void *regmap) regmap_write(regmap, ADXL345_REG_POWER_CTL, ADXL345_POWER_CTL_STANDBY); } -int adxl345_core_probe(struct device *dev, struct regmap *regmap) +static int adxl345_flush_fifo(struct regmap *map) +{ + __le16 axis_data[3]; + int ret, regval; + + /* Clear the sample FIFO */ + ret = regmap_read(map, ADXL345_REG_INT_SOURCE, ®val); + if (ret < 0) + goto out; + while (regval & ADXL345_INT_DATA_READY) { + ret = regmap_bulk_read(map, ADXL345_REG_DATA_AXIS(0), &axis_data, + sizeof(axis_data)); + if (ret < 0) + goto out; + ret = regmap_read(map, ADXL345_REG_INT_SOURCE, ®val); + if (ret < 0) + goto out; + } + +out: + return ret; +} + +static int adxl345_buffer_preenable(struct iio_dev *indio_dev) +{ + struct adxl345_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + /* Disable measurement mode to setup everything */ + ret = regmap_clear_bits(data->regmap, ADXL345_REG_POWER_CTL, ADXL345_POWER_CTL_MEASURE); + if (ret < 0) + goto out; + + ret = adxl345_flush_fifo(data->regmap); + if (ret < 0) + goto out_enable; + + /* + * Set the FIFO up in streaming mode so the chip keeps sampling. + * Waterlevel is set by the sample frequency functions as it is dynamic + */ + ret = regmap_update_bits(data->regmap, ADXL345_REG_FIFO_CTL, + (int)~(ADXL345_FIFO_CTL_SAMPLES_MASK), + ADXL345_FIFO_CTL_MODE_STREAM); + if (ret < 0) + goto out_enable; + + /* Enable the Watermark and Overrun interrupt */ + ret = regmap_write(data->regmap, ADXL345_REG_INT_ENABLE, (ADXL345_INT_WATERMARK | + ADXL345_INT_OVERRUN)); + if (ret < 0) + goto out_enable; + + /* Re-enable measurement mode */ + ret = regmap_set_bits(data->regmap, ADXL345_REG_POWER_CTL, ADXL345_POWER_CTL_MEASURE); + goto out; + +out_enable: + dev_err(indio_dev->dev.parent, "Error %d Setting up device\n", ret); + /* Re-enable measurement mode */ + regmap_set_bits(data->regmap, ADXL345_REG_POWER_CTL, ADXL345_POWER_CTL_MEASURE); +out: + mutex_unlock(&data->lock); + return ret; +} + +static int adxl345_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct adxl345_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + /* Disable measurement mode and interrupts*/ + regmap_clear_bits(data->regmap, ADXL345_REG_POWER_CTL, ADXL345_POWER_CTL_MEASURE); + regmap_write(data->regmap, ADXL345_REG_INT_ENABLE, 0x00); + + /* Clear the FIFO and disable FIFO mode */ + adxl345_flush_fifo(data->regmap); + regmap_update_bits(data->regmap, ADXL345_REG_FIFO_CTL, + (int)~(ADXL345_FIFO_CTL_SAMPLES_MASK), 0x00); + + /* re-enable measurement mode for direct reads */ + regmap_set_bits(data->regmap, ADXL345_REG_POWER_CTL, ADXL345_POWER_CTL_MEASURE); + mutex_unlock(&data->lock); + + return 0; +} + +static const struct iio_buffer_setup_ops adxl345_buffer_ops = { + .preenable = adxl345_buffer_preenable, + .postdisable = adxl345_buffer_postdisable, +}; + +static irqreturn_t adxl345_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adxl345_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + u8 buffer[ADXL345_SCAN_SIZE] __aligned(8); + int ret, data_available; + + mutex_lock(&data->lock); + + /* Disable the IRQ before reading the FIFO */ + if (data->irq) + disable_irq_nosync(data->irq); + + ret = regmap_read(regmap, ADXL345_REG_INT_SOURCE, &data_available); + if (ret < 0) { + dev_err(indio_dev->dev.parent, "Could not read available data (%d)\n", ret); + goto done; + } + + while (data_available & ADXL345_INT_DATA_READY) { + /* Read all axis data to make sure the IRQ flag is cleared. */ + ret = regmap_bulk_read(regmap, ADXL345_REG_DATA_AXIS(0), + &buffer, (sizeof(buffer))); + if (ret < 0) { + dev_err(indio_dev->dev.parent, "Could not read device FIFO (%d)\n", ret); + goto done; + } + iio_push_to_buffers(indio_dev, buffer); + ret = regmap_read(regmap, ADXL345_REG_INT_SOURCE, &data_available); + if (ret < 0) { + dev_err(indio_dev->dev.parent, "Could not push data to buffers (%d)\n", + ret); + goto done; + } + } +done: + iio_trigger_notify_done(indio_dev->trig); + + /* Re-enable the IRQ */ + if (data->irq) + enable_irq(data->irq); + + mutex_unlock(&data->lock); + + return IRQ_HANDLED; +} + +int adxl345_core_probe(struct device *dev, struct regmap *regmap, int irq) { struct adxl345_data *data; struct iio_dev *indio_dev; @@ -218,22 +500,29 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap) data = iio_priv(indio_dev); data->regmap = regmap; - /* Enable full-resolution mode */ - data->data_range = ADXL345_DATA_FORMAT_FULL_RES; data->info = device_get_match_data(dev); if (!data->info) return -ENODEV; + /* Enable full-resolution mode */ ret = regmap_write(data->regmap, ADXL345_REG_DATA_FORMAT, - data->data_range); + ADXL345_DATA_FORMAT_FULL_RES); if (ret < 0) return dev_err_probe(dev, ret, "Failed to set data range\n"); + /* Set the default sampling frequency */ + ret = adxl345_set_sampling_freq(data, ADXL345_DEFAULT_RATE); + if (ret < 0) + dev_err(dev, "Failed to set sampling rate: %d\n", ret); + + mutex_init(&data->lock); + indio_dev->name = data->info->name; indio_dev->info = &adxl345_info; - indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_TRIGGERED; indio_dev->channels = adxl345_channels; indio_dev->num_channels = ARRAY_SIZE(adxl345_channels); + indio_dev->available_scan_masks = adxl345_available_scan_masks; /* Enable measurement mode */ ret = adxl345_powerup(data->regmap); @@ -244,7 +533,51 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap) if (ret < 0) return ret; - return devm_iio_device_register(dev, indio_dev); + if (irq) { + /* Setup the data ready trigger */ + ret = devm_request_threaded_irq(dev, irq, NULL, adxl345_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret < 0) { + dev_err(dev, "request irq line failed.\n"); + return ret; + } + data->irq = irq; + + data->drdy_trig = devm_iio_trigger_alloc(dev, "%s-drdy%d", + indio_dev->name, iio_device_id(indio_dev)); + if (!data->drdy_trig) { + dev_err(dev, "Could not allocate drdy trigger\n"); + return -ENOMEM; + } + iio_trigger_set_drvdata(data->drdy_trig, indio_dev); + ret = devm_iio_trigger_register(dev, data->drdy_trig); + if (ret < 0) { + dev_err(dev, "Could not register drdy trigger\n"); + return ret; + } + /* Set the new trigger as the current trigger for this device */ + indio_dev->trig = iio_trigger_get(data->drdy_trig); + } else { + dev_info(dev, "Not registering IIO trigger as no IRQ has been specified\n"); + } + + /* Setup the triggered buffer */ + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + NULL, + adxl345_trigger_handler, + &adxl345_buffer_ops); + if (ret < 0) { + dev_err(dev, "Error setting up the triggered buffer\n"); + return ret; + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + dev_err(dev, "Error registering IIO device: %d\n", ret); + else + dev_info(dev, "Registered IIO device\n"); + return ret; } EXPORT_SYMBOL_NS_GPL(adxl345_core_probe, IIO_ADXL345); diff --git a/drivers/iio/accel/adxl345_i2c.c b/drivers/iio/accel/adxl345_i2c.c index a3084b0a8f78..b90d58a7a73c 100644 --- a/drivers/iio/accel/adxl345_i2c.c +++ b/drivers/iio/accel/adxl345_i2c.c @@ -27,7 +27,7 @@ static int adxl345_i2c_probe(struct i2c_client *client) if (IS_ERR(regmap)) return dev_err_probe(&client->dev, PTR_ERR(regmap), "Error initializing regmap\n"); - return adxl345_core_probe(&client->dev, regmap); + return adxl345_core_probe(&client->dev, regmap, client->irq); } static const struct adxl345_chip_info adxl345_i2c_info = { diff --git a/drivers/iio/accel/adxl345_spi.c b/drivers/iio/accel/adxl345_spi.c index 93ca349f1780..ea9a8438b540 100644 --- a/drivers/iio/accel/adxl345_spi.c +++ b/drivers/iio/accel/adxl345_spi.c @@ -33,7 +33,7 @@ static int adxl345_spi_probe(struct spi_device *spi) if (IS_ERR(regmap)) return dev_err_probe(&spi->dev, PTR_ERR(regmap), "Error initializing regmap\n"); - return adxl345_core_probe(&spi->dev, regmap); + return adxl345_core_probe(&spi->dev, regmap, spi->irq); } static const struct adxl345_chip_info adxl345_spi_info = { -- 2.20.1