[PATCH] iio: accel: adxl345: Added buffered read support

From: Paul Geurts
Date: Wed Apr 17 2024 - 04:29:09 EST


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, &regval);
+ 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, &regval);
+ 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), &regval);
+ 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, &regval);
- 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, &regval);
+ 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, &regval);
+ 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