diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2021-11-04 08:21:47 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2021-11-04 08:21:47 -0700 |
commit | 5c904c66ed4e86c31ac7c033b64274cebed04e0e (patch) | |
tree | 769d366c5e61ffa45d5d8a99c61ae9d5ea39a0a0 /drivers/iio/accel | |
parent | 5cd4dc44b8a0f656100e3b6916cf73b1623299eb (diff) | |
parent | 536de747bc48262225889a533db6650731ab25d3 (diff) | |
download | linux-5c904c66ed4e86c31ac7c033b64274cebed04e0e.tar.gz |
Merge tag 'char-misc-5.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull char/misc driver updates from Greg KH:
"Here is the big set of char and misc and other tiny driver subsystem
updates for 5.16-rc1.
Loads of things in here, all of which have been in linux-next for a
while with no reported problems (except for one called out below.)
Included are:
- habanana labs driver updates, including dma_buf usage, reviewed and
acked by the dma_buf maintainers
- iio driver update (going through this tree not staging as they
really do not belong going through that tree anymore)
- counter driver updates
- hwmon driver updates that the counter drivers needed, acked by the
hwmon maintainer
- xillybus driver updates
- binder driver updates
- extcon driver updates
- dma_buf module namespaces added (will cause a build error in arm64
for allmodconfig, but that change is on its way through the drm
tree)
- lkdtm driver updates
- pvpanic driver updates
- phy driver updates
- virt acrn and nitr_enclaves driver updates
- smaller char and misc driver updates"
* tag 'char-misc-5.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (386 commits)
comedi: dt9812: fix DMA buffers on stack
comedi: ni_usb6501: fix NULL-deref in command paths
arm64: errata: Enable TRBE workaround for write to out-of-range address
arm64: errata: Enable workaround for TRBE overwrite in FILL mode
coresight: trbe: Work around write to out of range
coresight: trbe: Make sure we have enough space
coresight: trbe: Add a helper to determine the minimum buffer size
coresight: trbe: Workaround TRBE errata overwrite in FILL mode
coresight: trbe: Add infrastructure for Errata handling
coresight: trbe: Allow driver to choose a different alignment
coresight: trbe: Decouple buffer base from the hardware base
coresight: trbe: Add a helper to pad a given buffer area
coresight: trbe: Add a helper to calculate the trace generated
coresight: trbe: Defer the probe on offline CPUs
coresight: trbe: Fix incorrect access of the sink specific data
coresight: etm4x: Add ETM PID for Kryo-5XX
coresight: trbe: Prohibit trace before disabling TRBE
coresight: trbe: End the AUX handle on truncation
coresight: trbe: Do not truncate buffer on IRQ
coresight: trbe: Fix handling of spurious interrupts
...
Diffstat (limited to 'drivers/iio/accel')
36 files changed, 1918 insertions, 107 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index a0e9061f6d6b..49587c992a6d 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -30,6 +30,35 @@ config ADIS16209 To compile this driver as a module, say M here: the module will be called adis16209. +config ADXL313 + tristate + +config ADXL313_I2C + tristate "Analog Devices ADXL313 3-Axis Digital Accelerometer I2C Driver" + depends on I2C + select ADXL313 + select REGMAP_I2C + help + Say Y here if you want to build i2c support for the Analog Devices + ADXL313 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl313_i2c and you will also get adxl313_core + for the core module. + +config ADXL313_SPI + tristate "Analog Devices ADXL313 3-Axis Digital Accelerometer SPI Driver" + depends on SPI + select ADXL313 + select REGMAP_SPI + help + Say Y here if you want to build spi support for the Analog Devices + ADXL313 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl313_spi and you will also get adxl313_core + for the core module. + config ADXL345 tristate @@ -61,6 +90,39 @@ config ADXL345_SPI will be called adxl345_spi and you will also get adxl345_core for the core module. +config ADXL355 + tristate + +config ADXL355_I2C + tristate "Analog Devices ADXL355 3-Axis Digital Accelerometer I2C Driver" + depends on I2C + select ADXL355 + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build i2c support for the Analog Devices + ADXL355 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl355_i2c and you will also get adxl355_core + for the core module. + +config ADXL355_SPI + tristate "Analog Devices ADXL355 3-Axis Digital Accelerometer SPI Driver" + depends on SPI + select ADXL355 + select REGMAP_SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build spi support for the Analog Devices + ADXL355 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl355_spi and you will also get adxl355_core + for the core module. + config ADXL372 tristate select IIO_BUFFER diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index 89280e823bcd..d03e2f6bba08 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -6,9 +6,15 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_ADIS16201) += adis16201.o obj-$(CONFIG_ADIS16209) += adis16209.o +obj-$(CONFIG_ADXL313) += adxl313_core.o +obj-$(CONFIG_ADXL313_I2C) += adxl313_i2c.o +obj-$(CONFIG_ADXL313_SPI) += adxl313_spi.o obj-$(CONFIG_ADXL345) += adxl345_core.o obj-$(CONFIG_ADXL345_I2C) += adxl345_i2c.o obj-$(CONFIG_ADXL345_SPI) += adxl345_spi.o +obj-$(CONFIG_ADXL355) += adxl355_core.o +obj-$(CONFIG_ADXL355_I2C) += adxl355_i2c.o +obj-$(CONFIG_ADXL355_SPI) += adxl355_spi.o obj-$(CONFIG_ADXL372) += adxl372.o obj-$(CONFIG_ADXL372_I2C) += adxl372_i2c.o obj-$(CONFIG_ADXL372_SPI) += adxl372_spi.o diff --git a/drivers/iio/accel/adxl313.h b/drivers/iio/accel/adxl313.h new file mode 100644 index 000000000000..4415f2fc07e1 --- /dev/null +++ b/drivers/iio/accel/adxl313.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ADXL313 3-Axis Digital Accelerometer + * + * Copyright (c) 2021 Lucas Stankus <lucas.p.stankus@gmail.com> + */ + +#ifndef _ADXL313_H_ +#define _ADXL313_H_ + +/* ADXL313 register definitions */ +#define ADXL313_REG_DEVID0 0x00 +#define ADXL313_REG_DEVID1 0x01 +#define ADXL313_REG_PARTID 0x02 +#define ADXL313_REG_XID 0x04 +#define ADXL313_REG_SOFT_RESET 0x18 +#define ADXL313_REG_OFS_AXIS(index) (0x1E + (index)) +#define ADXL313_REG_THRESH_ACT 0x24 +#define ADXL313_REG_ACT_INACT_CTL 0x27 +#define ADXL313_REG_BW_RATE 0x2C +#define ADXL313_REG_POWER_CTL 0x2D +#define ADXL313_REG_INT_MAP 0x2F +#define ADXL313_REG_DATA_FORMAT 0x31 +#define ADXL313_REG_DATA_AXIS(index) (0x32 + ((index) * 2)) +#define ADXL313_REG_FIFO_CTL 0x38 +#define ADXL313_REG_FIFO_STATUS 0x39 + +#define ADXL313_DEVID0 0xAD +#define ADXL313_DEVID1 0x1D +#define ADXL313_PARTID 0xCB +#define ADXL313_SOFT_RESET 0x52 + +#define ADXL313_RATE_MSK GENMASK(3, 0) +#define ADXL313_RATE_BASE 6 + +#define ADXL313_POWER_CTL_MSK GENMASK(3, 2) +#define ADXL313_MEASUREMENT_MODE BIT(3) + +#define ADXL313_RANGE_MSK GENMASK(1, 0) +#define ADXL313_RANGE_4G 3 + +#define ADXL313_FULL_RES BIT(3) +#define ADXL313_SPI_3WIRE BIT(6) +#define ADXL313_I2C_DISABLE BIT(6) + +extern const struct regmap_access_table adxl313_readable_regs_table; + +extern const struct regmap_access_table adxl313_writable_regs_table; + +int adxl313_core_probe(struct device *dev, + struct regmap *regmap, + const char *name, + int (*setup)(struct device *, struct regmap *)); +#endif /* _ADXL313_H_ */ diff --git a/drivers/iio/accel/adxl313_core.c b/drivers/iio/accel/adxl313_core.c new file mode 100644 index 000000000000..0d243341f1a7 --- /dev/null +++ b/drivers/iio/accel/adxl313_core.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL313 3-Axis Digital Accelerometer + * + * Copyright (c) 2021 Lucas Stankus <lucas.p.stankus@gmail.com> + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf + */ + +#include <linux/bitfield.h> +#include <linux/iio/iio.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "adxl313.h" + +static const struct regmap_range adxl313_readable_reg_range[] = { + regmap_reg_range(ADXL313_REG_DEVID0, ADXL313_REG_XID), + regmap_reg_range(ADXL313_REG_SOFT_RESET, ADXL313_REG_SOFT_RESET), + regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)), + regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL), + regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_FIFO_STATUS), +}; + +const struct regmap_access_table adxl313_readable_regs_table = { + .yes_ranges = adxl313_readable_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl313_readable_reg_range), +}; +EXPORT_SYMBOL_GPL(adxl313_readable_regs_table); + +static const struct regmap_range adxl313_writable_reg_range[] = { + regmap_reg_range(ADXL313_REG_SOFT_RESET, ADXL313_REG_SOFT_RESET), + regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)), + regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL), + regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_INT_MAP), + regmap_reg_range(ADXL313_REG_DATA_FORMAT, ADXL313_REG_DATA_FORMAT), + regmap_reg_range(ADXL313_REG_FIFO_CTL, ADXL313_REG_FIFO_CTL), +}; + +const struct regmap_access_table adxl313_writable_regs_table = { + .yes_ranges = adxl313_writable_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl313_writable_reg_range), +}; +EXPORT_SYMBOL_GPL(adxl313_writable_regs_table); + +struct adxl313_data { + struct regmap *regmap; + struct mutex lock; /* lock to protect transf_buf */ + __le16 transf_buf ____cacheline_aligned; +}; + +static const int adxl313_odr_freqs[][2] = { + [0] = { 6, 250000 }, + [1] = { 12, 500000 }, + [2] = { 25, 0 }, + [3] = { 50, 0 }, + [4] = { 100, 0 }, + [5] = { 200, 0 }, + [6] = { 400, 0 }, + [7] = { 800, 0 }, + [8] = { 1600, 0 }, + [9] = { 3200, 0 }, +}; + +#define ADXL313_ACCEL_CHANNEL(index, axis) { \ + .type = IIO_ACCEL, \ + .address = index, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = { \ + .realbits = 13, \ + }, \ +} + +static const struct iio_chan_spec adxl313_channels[] = { + ADXL313_ACCEL_CHANNEL(0, X), + ADXL313_ACCEL_CHANNEL(1, Y), + ADXL313_ACCEL_CHANNEL(2, Z), +}; + +static int adxl313_set_odr(struct adxl313_data *data, + unsigned int freq1, unsigned int freq2) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(adxl313_odr_freqs); i++) { + if (adxl313_odr_freqs[i][0] == freq1 && + adxl313_odr_freqs[i][1] == freq2) + break; + } + + if (i == ARRAY_SIZE(adxl313_odr_freqs)) + return -EINVAL; + + return regmap_update_bits(data->regmap, ADXL313_REG_BW_RATE, + ADXL313_RATE_MSK, + FIELD_PREP(ADXL313_RATE_MSK, ADXL313_RATE_BASE + i)); +} + +static int adxl313_read_axis(struct adxl313_data *data, + struct iio_chan_spec const *chan) +{ + int ret; + + mutex_lock(&data->lock); + + ret = regmap_bulk_read(data->regmap, + ADXL313_REG_DATA_AXIS(chan->address), + &data->transf_buf, sizeof(data->transf_buf)); + if (ret) + goto unlock_ret; + + ret = le16_to_cpu(data->transf_buf); + +unlock_ret: + mutex_unlock(&data->lock); + return ret; +} + +static int adxl313_read_freq_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)adxl313_odr_freqs; + *length = ARRAY_SIZE(adxl313_odr_freqs) * 2; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int adxl313_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adxl313_data *data = iio_priv(indio_dev); + unsigned int regval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = adxl313_read_axis(data, chan); + if (ret < 0) + return ret; + + *val = sign_extend32(ret, chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * Scale for any g range is given in datasheet as + * 1024 LSB/g = 0.0009765625 * 9.80665 = 0.009576806640625 m/s^2 + */ + *val = 0; + *val2 = 9576806; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_CALIBBIAS: + ret = regmap_read(data->regmap, + ADXL313_REG_OFS_AXIS(chan->address), ®val); + if (ret) + return ret; + + /* + * 8-bit resolution at +/- 0.5g, that is 4x accel data scale + * factor at full resolution + */ + *val = sign_extend32(regval, 7) * 4; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(data->regmap, ADXL313_REG_BW_RATE, ®val); + if (ret) + return ret; + + ret = FIELD_GET(ADXL313_RATE_MSK, regval) - ADXL313_RATE_BASE; + *val = adxl313_odr_freqs[ret][0]; + *val2 = adxl313_odr_freqs[ret][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int adxl313_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adxl313_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + /* + * 8-bit resolution at +/- 0.5g, that is 4x accel data scale + * factor at full resolution + */ + if (clamp_val(val, -128 * 4, 127 * 4) != val) + return -EINVAL; + + return regmap_write(data->regmap, + ADXL313_REG_OFS_AXIS(chan->address), + val / 4); + case IIO_CHAN_INFO_SAMP_FREQ: + return adxl313_set_odr(data, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info adxl313_info = { + .read_raw = adxl313_read_raw, + .write_raw = adxl313_write_raw, + .read_avail = adxl313_read_freq_avail, +}; + +static int adxl313_setup(struct device *dev, struct adxl313_data *data, + int (*setup)(struct device *, struct regmap *)) +{ + unsigned int regval; + int ret; + + /* Ensures the device is in a consistent state after start up */ + ret = regmap_write(data->regmap, ADXL313_REG_SOFT_RESET, + ADXL313_SOFT_RESET); + if (ret) + return ret; + + if (setup) { + ret = setup(dev, data->regmap); + if (ret) + return ret; + } + + ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, ®val); + if (ret) + return ret; + + if (regval != ADXL313_DEVID0) { + dev_err(dev, "Invalid manufacturer ID: 0x%02x\n", regval); + return -ENODEV; + } + + ret = regmap_read(data->regmap, ADXL313_REG_DEVID1, ®val); + if (ret) + return ret; + + if (regval != ADXL313_DEVID1) { + dev_err(dev, "Invalid mems ID: 0x%02x\n", regval); + return -ENODEV; + } + + ret = regmap_read(data->regmap, ADXL313_REG_PARTID, ®val); + if (ret) + return ret; + + if (regval != ADXL313_PARTID) { + dev_err(dev, "Invalid device ID: 0x%02x\n", regval); + return -ENODEV; + } + + /* Sets the range to +/- 4g */ + ret = regmap_update_bits(data->regmap, ADXL313_REG_DATA_FORMAT, + ADXL313_RANGE_MSK, + FIELD_PREP(ADXL313_RANGE_MSK, ADXL313_RANGE_4G)); + if (ret) + return ret; + + /* Enables full resolution */ + ret = regmap_update_bits(data->regmap, ADXL313_REG_DATA_FORMAT, + ADXL313_FULL_RES, ADXL313_FULL_RES); + if (ret) + return ret; + + /* Enables measurement mode */ + return regmap_update_bits(data->regmap, ADXL313_REG_POWER_CTL, + ADXL313_POWER_CTL_MSK, + ADXL313_MEASUREMENT_MODE); +} + +/** + * adxl313_core_probe() - probe and setup for adxl313 accelerometer + * @dev: Driver model representation of the device + * @regmap: Register map of the device + * @name: Device name buffer reference + * @setup: Setup routine to be executed right before the standard device + * setup, can also be set to NULL if not required + * + * Return: 0 on success, negative errno on error cases + */ +int adxl313_core_probe(struct device *dev, + struct regmap *regmap, + const char *name, + int (*setup)(struct device *, struct regmap *)) +{ + struct adxl313_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + mutex_init(&data->lock); + + indio_dev->name = name; + indio_dev->info = &adxl313_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adxl313_channels; + indio_dev->num_channels = ARRAY_SIZE(adxl313_channels); + + ret = adxl313_setup(dev, data, setup); + if (ret) { + dev_err(dev, "ADXL313 setup failed\n"); + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_GPL(adxl313_core_probe); + +MODULE_AUTHOR("Lucas Stankus <lucas.p.stankus@gmail.com>"); +MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl313_i2c.c b/drivers/iio/accel/adxl313_i2c.c new file mode 100644 index 000000000000..82e9fb2db1e6 --- /dev/null +++ b/drivers/iio/accel/adxl313_i2c.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL313 3-Axis Digital Accelerometer + * + * Copyright (c) 2021 Lucas Stankus <lucas.p.stankus@gmail.com> + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf + */ + +#include <linux/i2c.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "adxl313.h" + +static const struct regmap_config adxl313_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &adxl313_readable_regs_table, + .wr_table = &adxl313_writable_regs_table, + .max_register = 0x39, +}; + +static int adxl313_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &adxl313_i2c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Error initializing i2c regmap: %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return adxl313_core_probe(&client->dev, regmap, client->name, NULL); +} + +static const struct i2c_device_id adxl313_i2c_id[] = { + { "adxl313" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, adxl313_i2c_id); + +static const struct of_device_id adxl313_of_match[] = { + { .compatible = "adi,adxl313" }, + { } +}; + +MODULE_DEVICE_TABLE(of, adxl313_of_match); + +static struct i2c_driver adxl313_i2c_driver = { + .driver = { + .name = "adxl313_i2c", + .of_match_table = adxl313_of_match, + }, + .probe_new = adxl313_i2c_probe, + .id_table = adxl313_i2c_id, +}; + +module_i2c_driver(adxl313_i2c_driver); + +MODULE_AUTHOR("Lucas Stankus <lucas.p.stankus@gmail.com>"); +MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl313_spi.c b/drivers/iio/accel/adxl313_spi.c new file mode 100644 index 000000000000..a6162f36ef52 --- /dev/null +++ b/drivers/iio/accel/adxl313_spi.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL313 3-Axis Digital Accelerometer + * + * Copyright (c) 2021 Lucas Stankus <lucas.p.stankus@gmail.com> + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf + */ + +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> + +#include "adxl313.h" + +static const struct regmap_config adxl313_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &adxl313_readable_regs_table, + .wr_table = &adxl313_writable_regs_table, + .max_register = 0x39, + /* Setting bits 7 and 6 enables multiple-byte read */ + .read_flag_mask = BIT(7) | BIT(6), +}; + +static int adxl313_spi_setup(struct device *dev, struct regmap *regmap) +{ + struct spi_device *spi = container_of(dev, struct spi_device, dev); + int ret; + + if (spi->mode & SPI_3WIRE) { + ret = regmap_write(regmap, ADXL313_REG_DATA_FORMAT, + ADXL313_SPI_3WIRE); + if (ret) + return ret; + } + + return regmap_update_bits(regmap, ADXL313_REG_POWER_CTL, + ADXL313_I2C_DISABLE, ADXL313_I2C_DISABLE); +} + +static int adxl313_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + int ret; + + spi->mode |= SPI_MODE_3; + ret = spi_setup(spi); + if (ret) + return ret; + + regmap = devm_regmap_init_spi(spi, &adxl313_spi_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Error initializing spi regmap: %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return adxl313_core_probe(&spi->dev, regmap, id->name, + &adxl313_spi_setup); +} + +static const struct spi_device_id adxl313_spi_id[] = { + { "adxl313" }, + { } +}; + +MODULE_DEVICE_TABLE(spi, adxl313_spi_id); + +static const struct of_device_id adxl313_of_match[] = { + { .compatible = "adi,adxl313" }, + { } +}; + +MODULE_DEVICE_TABLE(of, adxl313_of_match); + +static struct spi_driver adxl313_spi_driver = { + .driver = { + .name = "adxl313_spi", + .of_match_table = adxl313_of_match, + }, + .probe = adxl313_spi_probe, + .id_table = adxl313_spi_id, +}; + +module_spi_driver(adxl313_spi_driver); + +MODULE_AUTHOR("Lucas Stankus <lucas.p.stankus@gmail.com>"); +MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl355.h b/drivers/iio/accel/adxl355.h new file mode 100644 index 000000000000..6dd49b13e4fd --- /dev/null +++ b/drivers/iio/accel/adxl355.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ADXL355 3-Axis Digital Accelerometer + * + * Copyright (c) 2021 Puranjay Mohan <puranjay12@gmail.com> + */ + +#ifndef _ADXL355_H_ +#define _ADXL355_H_ + +#include <linux/regmap.h> + +struct device; + +extern const struct regmap_access_table adxl355_readable_regs_tbl; +extern const struct regmap_access_table adxl355_writeable_regs_tbl; + +int adxl355_core_probe(struct device *dev, struct regmap *regmap, + const char *name); + +#endif /* _ADXL355_H_ */ diff --git a/drivers/iio/accel/adxl355_core.c b/drivers/iio/accel/adxl355_core.c new file mode 100644 index 000000000000..4f485909f459 --- /dev/null +++ b/drivers/iio/accel/adxl355_core.c @@ -0,0 +1,765 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL355 3-Axis Digital Accelerometer IIO core driver + * + * Copyright (c) 2021 Puranjay Mohan <puranjay12@gmail.com> + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/adxl354_adxl355.pdf + */ + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/limits.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> +#include <asm/unaligned.h> + +#include "adxl355.h" + +/* ADXL355 Register Definitions */ +#define ADXL355_DEVID_AD_REG 0x00 +#define ADXL355_DEVID_MST_REG 0x01 +#define ADXL355_PARTID_REG 0x02 +#define ADXL355_STATUS_REG 0x04 +#define ADXL355_FIFO_ENTRIES_REG 0x05 +#define ADXL355_TEMP2_REG 0x06 +#define ADXL355_XDATA3_REG 0x08 +#define ADXL355_YDATA3_REG 0x0B +#define ADXL355_ZDATA3_REG 0x0E +#define ADXL355_FIFO_DATA_REG 0x11 +#define ADXL355_OFFSET_X_H_REG 0x1E +#define ADXL355_OFFSET_Y_H_REG 0x20 +#define ADXL355_OFFSET_Z_H_REG 0x22 +#define ADXL355_ACT_EN_REG 0x24 +#define ADXL355_ACT_THRESH_H_REG 0x25 +#define ADXL355_ACT_THRESH_L_REG 0x26 +#define ADXL355_ACT_COUNT_REG 0x27 +#define ADXL355_FILTER_REG 0x28 +#define ADXL355_FILTER_ODR_MSK GENMASK(3, 0) +#define ADXL355_FILTER_HPF_MSK GENMASK(6, 4) +#define ADXL355_FIFO_SAMPLES_REG 0x29 +#define ADXL355_INT_MAP_REG 0x2A +#define ADXL355_SYNC_REG 0x2B +#define ADXL355_RANGE_REG 0x2C +#define ADXL355_POWER_CTL_REG 0x2D +#define ADXL355_POWER_CTL_MODE_MSK GENMASK(1, 0) +#define ADXL355_POWER_CTL_DRDY_MSK BIT(2) +#define ADXL355_SELF_TEST_REG 0x2E +#define ADXL355_RESET_REG 0x2F + +#define ADXL355_DEVID_AD_VAL 0xAD +#define ADXL355_DEVID_MST_VAL 0x1D +#define ADXL355_PARTID_VAL 0xED +#define ADXL355_RESET_CODE 0x52 + +#define MEGA 1000000UL +#define TERA 1000000000000ULL + +static const struct regmap_range adxl355_read_reg_range[] = { + regmap_reg_range(ADXL355_DEVID_AD_REG, ADXL355_FIFO_DATA_REG), + regmap_reg_range(ADXL355_OFFSET_X_H_REG, ADXL355_SELF_TEST_REG), +}; + +const struct regmap_access_table adxl355_readable_regs_tbl = { + .yes_ranges = adxl355_read_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl355_read_reg_range), +}; +EXPORT_SYMBOL_GPL(adxl355_readable_regs_tbl); + +static const struct regmap_range adxl355_write_reg_range[] = { + regmap_reg_range(ADXL355_OFFSET_X_H_REG, ADXL355_RESET_REG), +}; + +const struct regmap_access_table adxl355_writeable_regs_tbl = { + .yes_ranges = adxl355_write_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl355_write_reg_range), +}; +EXPORT_SYMBOL_GPL(adxl355_writeable_regs_tbl); + +enum adxl355_op_mode { + ADXL355_MEASUREMENT, + ADXL355_STANDBY, + ADXL355_TEMP_OFF, +}; + +enum adxl355_odr { + ADXL355_ODR_4000HZ, + ADXL355_ODR_2000HZ, + ADXL355_ODR_1000HZ, + ADXL355_ODR_500HZ, + ADXL355_ODR_250HZ, + ADXL355_ODR_125HZ, + ADXL355_ODR_62_5HZ, + ADXL355_ODR_31_25HZ, + ADXL355_ODR_15_625HZ, + ADXL355_ODR_7_813HZ, + ADXL355_ODR_3_906HZ, +}; + +enum adxl355_hpf_3db { + ADXL355_HPF_OFF, + ADXL355_HPF_24_7, + ADXL355_HPF_6_2084, + ADXL355_HPF_1_5545, + ADXL355_HPF_0_3862, + ADXL355_HPF_0_0954, + ADXL355_HPF_0_0238, +}; + +static const int adxl355_odr_table[][2] = { + [0] = {4000, 0}, + [1] = {2000, 0}, + [2] = {1000, 0}, + [3] = {500, 0}, + [4] = {250, 0}, + [5] = {125, 0}, + [6] = {62, 500000}, + [7] = {31, 250000}, + [8] = {15, 625000}, + [9] = {7, 813000}, + [10] = {3, 906000}, +}; + +static const int adxl355_hpf_3db_multipliers[] = { + 0, + 247000, + 62084, + 15545, + 3862, + 954, + 238, +}; + +enum adxl355_chans { + chan_x, chan_y, chan_z, +}; + +struct adxl355_chan_info { + u8 data_reg; + u8 offset_reg; +}; + +static const struct adxl355_chan_info adxl355_chans[] = { + [chan_x] = { + .data_reg = ADXL355_XDATA3_REG, + .offset_reg = ADXL355_OFFSET_X_H_REG + }, + [chan_y] = { + .data_reg = ADXL355_YDATA3_REG, + .offset_reg = ADXL355_OFFSET_Y_H_REG + }, + [chan_z] = { + .data_reg = ADXL355_ZDATA3_REG, + .offset_reg = ADXL355_OFFSET_Z_H_REG + }, +}; + +struct adxl355_data { + struct regmap *regmap; + struct device *dev; + struct mutex lock; /* lock to protect op_mode */ + enum adxl355_op_mode op_mode; + enum adxl355_odr odr; + enum adxl355_hpf_3db hpf_3db; + int calibbias[3]; + int adxl355_hpf_3db_table[7][2]; + struct iio_trigger *dready_trig; + union { + u8 transf_buf[3]; + struct { + u8 buf[14]; + s64 ts; + } buffer; + } ____cacheline_aligned; +}; + +static int adxl355_set_op_mode(struct adxl355_data *data, + enum adxl355_op_mode op_mode) +{ + int ret; + + if (data->op_mode == op_mode) + return 0; + + ret = regmap_update_bits(data->regmap, ADXL355_POWER_CTL_REG, + ADXL355_POWER_CTL_MODE_MSK, op_mode); + if (ret) + return ret; + + data->op_mode = op_mode; + + return ret; +} + +static int adxl355_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct adxl355_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, ADXL355_POWER_CTL_REG, + ADXL355_POWER_CTL_DRDY_MSK, + FIELD_PREP(ADXL355_POWER_CTL_DRDY_MSK, + state ? 0 : 1)); + mutex_unlock(&data->lock); + + return ret; +} + +static void adxl355_fill_3db_frequency_table(struct adxl355_data *data) +{ + u32 multiplier; + u64 div, rem; + u64 odr; + int i; + + odr = mul_u64_u32_shr(adxl355_odr_table[data->odr][0], MEGA, 0) + + adxl355_odr_table[data->odr][1]; + + for (i = 0; i < ARRAY_SIZE(adxl355_hpf_3db_multipliers); i++) { + multiplier = adxl355_hpf_3db_multipliers[i]; + div = div64_u64_rem(mul_u64_u32_shr(odr, multiplier, 0), + TERA * 100, &rem); + + data->adxl355_hpf_3db_table[i][0] = div; + data->adxl355_hpf_3db_table[i][1] = div_u64(rem, MEGA * 100); + } +} + +static int adxl355_setup(struct adxl355_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, ADXL355_DEVID_AD_REG, ®val); + if (ret) + return ret; + + if (regval != ADXL355_DEVID_AD_VAL) { + dev_err(data->dev, "Invalid ADI ID 0x%02x\n", regval); + return -ENODEV; + } + + ret = regmap_read(data->regmap, ADXL355_DEVID_MST_REG, ®val); + if (ret) + return ret; + + if (regval != ADXL355_DEVID_MST_VAL) { + dev_err(data->dev, "Invalid MEMS ID 0x%02x\n", regval); + return -ENODEV; + } + + ret = regmap_read(data->regmap, ADXL355_PARTID_REG, ®val); + if (ret) + return ret; + + if (regval != ADXL355_PARTID_VAL) { + dev_err(data->dev, "Invalid DEV ID 0x%02x\n", regval); + return -ENODEV; + } + + /* + * Perform a software reset to make sure the device is in a consistent + * state after start-up. + */ + ret = regmap_write(data->regmap, ADXL355_RESET_REG, ADXL355_RESET_CODE); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADXL355_POWER_CTL_REG, + ADXL355_POWER_CTL_DRDY_MSK, + FIELD_PREP(ADXL355_POWER_CTL_DRDY_MSK, 1)); + if (ret) + return ret; + + adxl355_fill_3db_frequency_table(data); + + return adxl355_set_op_mode(data, ADXL355_MEASUREMENT); +} + +static int adxl355_get_temp_data(struct adxl355_data *data, u8 addr) +{ + return regmap_bulk_read(data->regmap, addr, data->transf_buf, 2); +} + +static int adxl355_read_axis(struct adxl355_data *data, u8 addr) +{ + int ret; + + ret = regmap_bulk_read(data->regmap, addr, data->transf_buf, + ARRAY_SIZE(data->transf_buf)); + if (ret) + return ret; + + return get_unaligned_be24(data->transf_buf); +} + +static int adxl355_find_match(const int (*freq_tbl)[2], const int n, + const int val, const int val2) +{ + int i; + + for (i = 0; i < n; i++) { + if (freq_tbl[i][0] == val && freq_tbl[i][1] == val2) + return i; + } + + return -EINVAL; +} + +static int adxl355_set_odr(struct adxl355_data *data, + enum adxl355_odr odr) +{ + int ret; + + mutex_lock(&data->lock); + + if (data->odr == odr) { + mutex_unlock(&data->lock); + return 0; + } + + ret = adxl355_set_op_mode(data, ADXL355_STANDBY); + if (ret) + goto err_unlock; + + ret = regmap_update_bits(data->regmap, ADXL355_FILTER_REG, + ADXL355_FILTER_ODR_MSK, + FIELD_PREP(ADXL355_FILTER_ODR_MSK, odr)); + if (ret) + goto err_set_opmode; + + data->odr = odr; + adxl355_fill_3db_frequency_table(data); + + ret = adxl355_set_op_mode(data, ADXL355_MEASUREMENT); + if (ret) + goto err_set_opmode; + + mutex_unlock(&data->lock); + return 0; + +err_set_opmode: + adxl355_set_op_mode(data, ADXL355_MEASUREMENT); +err_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int adxl355_set_hpf_3db(struct adxl355_data *data, + enum adxl355_hpf_3db hpf) +{ + int ret; + + mutex_lock(&data->lock); + + if (data->hpf_3db == hpf) { + mutex_unlock(&data->lock); + return 0; + } + + ret = adxl355_set_op_mode(data, ADXL355_STANDBY); + if (ret) + goto err_unlock; + + ret = regmap_update_bits(data->regmap, ADXL355_FILTER_REG, + ADXL355_FILTER_HPF_MSK, + FIELD_PREP(ADXL355_FILTER_HPF_MSK, hpf)); + if (ret) + goto err_set_opmode; + + data->hpf_3db = hpf; + + ret = adxl355_set_op_mode(data, ADXL355_MEASUREMENT); + if (ret) + goto err_set_opmode; + + mutex_unlock(&data->lock); + return 0; + +err_set_opmode: + adxl355_set_op_mode(data, ADXL355_MEASUREMENT); +err_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int adxl355_set_calibbias(struct adxl355_data *data, + enum adxl355_chans chan, int calibbias) +{ + int ret; + + mutex_lock(&data->lock); + + ret = adxl355_set_op_mode(data, ADXL355_STANDBY); + if (ret) + goto err_unlock; + + put_unaligned_be16(calibbias, data->transf_buf); + ret = regmap_bulk_write(data->regmap, + adxl355_chans[chan].offset_reg, + data->transf_buf, 2); + if (ret) + goto err_set_opmode; + + data->calibbias[chan] = calibbias; + + ret = adxl355_set_op_mode(data, ADXL355_MEASUREMENT); + if (ret) + goto err_set_opmode; + + mutex_unlock(&data->lock); + return 0; + +err_set_opmode: + adxl355_set_op_mode(data, ADXL355_MEASUREMENT); +err_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int adxl355_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adxl355_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + ret = adxl355_get_temp_data(data, chan->address); + if (ret < 0) + return ret; + *val = get_unaligned_be16(data->transf_buf); + + return IIO_VAL_INT; + case IIO_ACCEL: + ret = adxl355_read_axis(data, adxl355_chans[ + chan->address].data_reg); + if (ret < 0) + return ret; + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + /* + * The datasheet defines an intercept of 1885 LSB at 25 degC + * and a slope of -9.05 LSB/C. The following formula can be used + * to find the temperature: + * Temp = ((RAW - 1885)/(-9.05)) + 25 but this doesn't follow + * the format of the IIO which is Temp = (RAW + OFFSET) * SCALE. + * Hence using some rearranging we get the scale as -110.497238 + * and offset as -2111.25. + */ + case IIO_TEMP: + *val = -110; + *val2 = 497238; + return IIO_VAL_INT_PLUS_MICRO; + /* + * At +/- 2g with 20-bit resolution, scale is given in datasheet + * as 3.9ug/LSB = 0.0000039 * 9.80665 = 0.00003824593 m/s^2. + */ + case IIO_ACCEL: + *val = 0; + *val2 = 38245; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = -2111; + *val2 = 250000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + *val = sign_extend32(data->calibbias[chan->address], 15); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = adxl355_odr_table[data->odr][0]; + *val2 = adxl355_odr_table[data->odr][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + *val = data->adxl355_hpf_3db_table[data->hpf_3db][0]; + *val2 = data->adxl355_hpf_3db_table[data->hpf_3db][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int adxl355_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adxl355_data *data = iio_priv(indio_dev); + int odr_idx, hpf_idx, calibbias; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + odr_idx = adxl355_find_match(adxl355_odr_table, + ARRAY_SIZE(adxl355_odr_table), + val, val2); + if (odr_idx < 0) + return odr_idx; + + return adxl355_set_odr(data, odr_idx); + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + hpf_idx = adxl355_find_match(data->adxl355_hpf_3db_table, + ARRAY_SIZE(data->adxl355_hpf_3db_table), + val, val2); + if (hpf_idx < 0) + return hpf_idx; + + return adxl355_set_hpf_3db(data, hpf_idx); + case IIO_CHAN_INFO_CALIBBIAS: + calibbias = clamp_t(int, val, S16_MIN, S16_MAX); + + return adxl355_set_calibbias(data, chan->address, calibbias); + default: + return -EINVAL; + } +} + +static int adxl355_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct adxl355_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)adxl355_odr_table; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(adxl355_odr_table) * 2; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + *vals = (const int *)data->adxl355_hpf_3db_table; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(data->adxl355_hpf_3db_table) * 2; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const unsigned long adxl355_avail_scan_masks[] = { + GENMASK(3, 0), + 0 +}; + +static const struct iio_info adxl355_info = { + .read_raw = adxl355_read_raw, + .write_raw = adxl355_write_raw, + .read_avail = &adxl355_read_avail, +}; + +static const struct iio_trigger_ops adxl355_trigger_ops = { + .set_trigger_state = &adxl355_data_rdy_trigger_set_state, + .validate_device = &iio_trigger_validate_own_device, +}; + +static irqreturn_t adxl355_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adxl355_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + + /* + * data->buffer is used both for triggered buffer support + * and read/write_raw(), hence, it has to be zeroed here before usage. + */ + data->buffer.buf[0] = 0; + + /* + * The acceleration data is 24 bits and big endian. It has to be saved + * in 32 bits, hence, it is saved in the 2nd byte of the 4 byte buffer. + * The buf array is 14 bytes as it includes 3x4=12 bytes for + * accelaration data of x, y, and z axis. It also includes 2 bytes for + * temperature data. + */ + ret = regmap_bulk_read(data->regmap, ADXL355_XDATA3_REG, + &data->buffer.buf[1], 3); + if (ret) + goto out_unlock_notify; + + ret = regmap_bulk_read(data->regmap, ADXL355_YDATA3_REG, + &data->buffer.buf[5], 3); + if (ret) + goto out_unlock_notify; + + ret = regmap_bulk_read(data->regmap, ADXL355_ZDATA3_REG, + &data->buffer.buf[9], 3); + if (ret) + goto out_unlock_notify; + + ret = regmap_bulk_read(data->regmap, ADXL355_TEMP2_REG, + &data->buffer.buf[12], 2); + if (ret) + goto out_unlock_notify; + + iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, + pf->timestamp); + +out_unlock_notify: + mutex_unlock(&data->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define ADXL355_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 20, \ + .storagebits = 32, \ + .shift = 4, \ + .endianness = IIO_BE, \ + } \ +} + +static const struct iio_chan_spec adxl355_channels[] = { + ADXL355_ACCEL_CHANNEL(0, chan_x, X), + ADXL355_ACCEL_CHANNEL(1, chan_y, Y), + ADXL355_ACCEL_CHANNEL(2, chan_z, Z), + { + .type = IIO_TEMP, + .address = ADXL355_TEMP2_REG, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 3, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int adxl355_probe_trigger(struct iio_dev *indio_dev, int irq) +{ + struct adxl355_data *data = iio_priv(indio_dev); + int ret; + + data->dready_trig = devm_iio_trigger_alloc(data->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->dready_trig) + return -ENOMEM; + + data->dready_trig->ops = &adxl355_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + + ret = devm_request_irq(data->dev, irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_ONESHOT, "adxl355_irq", data->dready_trig); + if (ret) + return dev_err_probe(data->dev, ret, "request irq %d failed\n", + irq); + + ret = devm_iio_trigger_register(data->dev, data->dready_trig); + if (ret) { + dev_err(data->dev, "iio trigger register failed\n"); + return ret; + } + + indio_dev->trig = iio_trigger_get(data->dready_trig); + + return 0; +} + +int adxl355_core_probe(struct device *dev, struct regmap *regmap, + const char *name) +{ + struct adxl355_data *data; + struct iio_dev *indio_dev; + int ret; + int irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + data->dev = dev; + data->op_mode = ADXL355_STANDBY; + mutex_init(&data->lock); + + indio_dev->name = name; + indio_dev->info = &adxl355_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adxl355_channels; + indio_dev->num_channels = ARRAY_SIZE(adxl355_channels); + indio_dev->available_scan_masks = adxl355_avail_scan_masks; + + ret = adxl355_setup(data); + if (ret) { + dev_err(dev, "ADXL355 setup failed\n"); + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &adxl355_trigger_handler, NULL); + if (ret) { + dev_err(dev, "iio triggered buffer setup failed\n"); + return ret; + } + + /* + * TODO: Would be good to move it to the generic version. + */ + irq = of_irq_get_byname(dev->of_node, "DRDY"); + if (irq > 0) { + ret = adxl355_probe_trigger(indio_dev, irq); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_GPL(adxl355_core_probe); + +MODULE_AUTHOR("Puranjay Mohan <puranjay12@gmail.com>"); +MODULE_DESCRIPTION("ADXL355 3-Axis Digital Accelerometer core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl355_i2c.c b/drivers/iio/accel/adxl355_i2c.c new file mode 100644 index 000000000000..5a987bda9060 --- /dev/null +++ b/drivers/iio/accel/adxl355_i2c.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL355 3-Axis Digital Accelerometer I2C driver + * + * Copyright (c) 2021 Puranjay Mohan <puranjay12@gmail.com> + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/regmap.h> + +#include "adxl355.h" + +static const struct regmap_config adxl355_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x2F, + .rd_table = &adxl355_readable_regs_tbl, + .wr_table = &adxl355_writeable_regs_tbl, +}; + +static int adxl355_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &adxl355_i2c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Error initializing i2c regmap: %ld\n", + PTR_ERR(regmap)); + + return PTR_ERR(regmap); + } + + return adxl355_core_probe(&client->dev, regmap, client->name); +} + +static const struct i2c_device_id adxl355_i2c_id[] = { + { "adxl355", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adxl355_i2c_id); + +static const struct of_device_id adxl355_of_match[] = { + { .compatible = "adi,adxl355" }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl355_of_match); + +static struct i2c_driver adxl355_i2c_driver = { + .driver = { + .name = "adxl355_i2c", + .of_match_table = adxl355_of_match, + }, + .probe_new = adxl355_i2c_probe, + .id_table = adxl355_i2c_id, +}; +module_i2c_driver(adxl355_i2c_driver); + +MODULE_AUTHOR("Puranjay Mohan <puranjay12@gmail.com>"); +MODULE_DESCRIPTION("ADXL355 3-Axis Digital Accelerometer I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl355_spi.c b/drivers/iio/accel/adxl355_spi.c new file mode 100644 index 000000000000..fb225aeb56e3 --- /dev/null +++ b/drivers/iio/accel/adxl355_spi.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL355 3-Axis Digital Accelerometer SPI driver + * + * Copyright (c) 2021 Puranjay Mohan <puranjay12@gmail.com> + */ + +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> + +#include "adxl355.h" + +static const struct regmap_config adxl355_spi_regmap_config = { + .reg_bits = 7, + .pad_bits = 1, + .val_bits = 8, + .read_flag_mask = BIT(0), + .max_register = 0x2F, + .rd_table = &adxl355_readable_regs_tbl, + .wr_table = &adxl355_writeable_regs_tbl, +}; + +static int adxl355_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, &adxl355_spi_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Error initializing spi regmap: %ld\n", + PTR_ERR(regmap)); + + return PTR_ERR(regmap); + } + + return adxl355_core_probe(&spi->dev, regmap, id->name); +} + +static const struct spi_device_id adxl355_spi_id[] = { + { "adxl355", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adxl355_spi_id); + +static const struct of_device_id adxl355_of_match[] = { + { .compatible = "adi,adxl355" }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl355_of_match); + +static struct spi_driver adxl355_spi_driver = { + .driver = { + .name = "adxl355_spi", + .of_match_table = adxl355_of_match, + }, + .probe = adxl355_spi_probe, + .id_table = adxl355_spi_id, +}; +module_spi_driver(adxl355_spi_driver); + +MODULE_AUTHOR("Puranjay Mohan <puranjay12@gmail.com>"); +MODULE_DESCRIPTION("ADXL355 3-Axis Digital Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl372.c b/drivers/iio/accel/adxl372.c index fc9592407717..758952584f8c 100644 --- a/drivers/iio/accel/adxl372.c +++ b/drivers/iio/accel/adxl372.c @@ -1214,6 +1214,7 @@ int adxl372_probe(struct device *dev, struct regmap *regmap, ret = devm_iio_triggered_buffer_setup_ext(dev, indio_dev, NULL, adxl372_trigger_handler, + IIO_BUFFER_DIRECTION_IN, &adxl372_buffer_ops, adxl372_fifo_attributes); if (ret < 0) diff --git a/drivers/iio/accel/bma400.h b/drivers/iio/accel/bma400.h index 5ad10db9819f..c4c8d74155c2 100644 --- a/drivers/iio/accel/bma400.h +++ b/drivers/iio/accel/bma400.h @@ -94,6 +94,6 @@ extern const struct regmap_config bma400_regmap_config; int bma400_probe(struct device *dev, struct regmap *regmap, const char *name); -int bma400_remove(struct device *dev); +void bma400_remove(struct device *dev); #endif diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c index 21520e022a21..fd2647b728d3 100644 --- a/drivers/iio/accel/bma400_core.c +++ b/drivers/iio/accel/bma400_core.c @@ -828,7 +828,7 @@ int bma400_probe(struct device *dev, struct regmap *regmap, const char *name) } EXPORT_SYMBOL(bma400_probe); -int bma400_remove(struct device *dev) +void bma400_remove(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bma400_data *data = iio_priv(indio_dev); @@ -838,12 +838,13 @@ int bma400_remove(struct device *dev) ret = bma400_set_power_mode(data, POWER_MODE_SLEEP); mutex_unlock(&data->mutex); + if (ret) + dev_warn(dev, "Failed to put device into sleep mode (%pe)\n", ERR_PTR(ret)); + regulator_bulk_disable(ARRAY_SIZE(data->regulators), data->regulators); iio_device_unregister(indio_dev); - - return ret; } EXPORT_SYMBOL(bma400_remove); diff --git a/drivers/iio/accel/bma400_i2c.c b/drivers/iio/accel/bma400_i2c.c index 9dcb7cc9996e..f50df5310beb 100644 --- a/drivers/iio/accel/bma400_i2c.c +++ b/drivers/iio/accel/bma400_i2c.c @@ -29,7 +29,9 @@ static int bma400_i2c_probe(struct i2c_client *client, static int bma400_i2c_remove(struct i2c_client *client) { - return bma400_remove(&client->dev); + bma400_remove(&client->dev); + + return 0; } static const struct i2c_device_id bma400_i2c_ids[] = { diff --git a/drivers/iio/accel/bma400_spi.c b/drivers/iio/accel/bma400_spi.c index 7c2825904e08..9f622e37477b 100644 --- a/drivers/iio/accel/bma400_spi.c +++ b/drivers/iio/accel/bma400_spi.c @@ -89,7 +89,9 @@ static int bma400_spi_probe(struct spi_device *spi) static int bma400_spi_remove(struct spi_device *spi) { - return bma400_remove(&spi->dev); + bma400_remove(&spi->dev); + + return 0; } static const struct spi_device_id bma400_spi_ids[] = { diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c index e8693a42ad46..b0678c351e82 100644 --- a/drivers/iio/accel/bmc150-accel-core.c +++ b/drivers/iio/accel/bmc150-accel-core.c @@ -1734,6 +1734,7 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, ret = iio_triggered_buffer_setup_ext(indio_dev, &iio_pollfunc_store_time, bmc150_accel_trigger_handler, + IIO_BUFFER_DIRECTION_IN, &bmc150_accel_buffer_ops, fifo_attrs); if (ret < 0) { @@ -1799,7 +1800,7 @@ err_disable_regulators: } EXPORT_SYMBOL_GPL(bmc150_accel_core_probe); -int bmc150_accel_core_remove(struct device *dev) +void 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); @@ -1819,8 +1820,6 @@ int bmc150_accel_core_remove(struct device *dev) regulator_bulk_disable(ARRAY_SIZE(data->regulators), data->regulators); - - return 0; } EXPORT_SYMBOL_GPL(bmc150_accel_core_remove); diff --git a/drivers/iio/accel/bmc150-accel-i2c.c b/drivers/iio/accel/bmc150-accel-i2c.c index 88bd8a25f142..9e52df9a8f07 100644 --- a/drivers/iio/accel/bmc150-accel-i2c.c +++ b/drivers/iio/accel/bmc150-accel-i2c.c @@ -213,7 +213,9 @@ static int bmc150_accel_remove(struct i2c_client *client) { bmc150_acpi_dual_accel_remove(client); - return bmc150_accel_core_remove(&client->dev); + bmc150_accel_core_remove(&client->dev); + + return 0; } static const struct acpi_device_id bmc150_accel_acpi_match[] = { diff --git a/drivers/iio/accel/bmc150-accel-spi.c b/drivers/iio/accel/bmc150-accel-spi.c index 191e312dc91a..11559567cb39 100644 --- a/drivers/iio/accel/bmc150-accel-spi.c +++ b/drivers/iio/accel/bmc150-accel-spi.c @@ -37,7 +37,9 @@ static int bmc150_accel_probe(struct spi_device *spi) static int bmc150_accel_remove(struct spi_device *spi) { - return bmc150_accel_core_remove(&spi->dev); + bmc150_accel_core_remove(&spi->dev); + + return 0; } static const struct acpi_device_id bmc150_accel_acpi_match[] = { diff --git a/drivers/iio/accel/bmc150-accel.h b/drivers/iio/accel/bmc150-accel.h index 1bb5023e8ed9..7775c5edaeef 100644 --- a/drivers/iio/accel/bmc150-accel.h +++ b/drivers/iio/accel/bmc150-accel.h @@ -88,7 +88,7 @@ struct bmc150_accel_data { int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, enum bmc150_type type, const char *name, bool block_supported); -int bmc150_accel_core_remove(struct device *dev); +void bmc150_accel_core_remove(struct device *dev); extern const struct dev_pm_ops bmc150_accel_pm_ops; extern const struct regmap_config bmc150_regmap_conf; diff --git a/drivers/iio/accel/bmi088-accel-core.c b/drivers/iio/accel/bmi088-accel-core.c index a06dae5c971d..d74465214feb 100644 --- a/drivers/iio/accel/bmi088-accel-core.c +++ b/drivers/iio/accel/bmi088-accel-core.c @@ -536,7 +536,7 @@ int bmi088_accel_core_probe(struct device *dev, struct regmap *regmap, EXPORT_SYMBOL_GPL(bmi088_accel_core_probe); -int bmi088_accel_core_remove(struct device *dev) +void bmi088_accel_core_remove(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmi088_accel_data *data = iio_priv(indio_dev); @@ -546,8 +546,6 @@ int bmi088_accel_core_remove(struct device *dev) pm_runtime_disable(dev); pm_runtime_set_suspended(dev); bmi088_accel_power_down(data); - - return 0; } EXPORT_SYMBOL_GPL(bmi088_accel_core_remove); diff --git a/drivers/iio/accel/bmi088-accel-spi.c b/drivers/iio/accel/bmi088-accel-spi.c index dd1e3f6cf211..758ad2f12896 100644 --- a/drivers/iio/accel/bmi088-accel-spi.c +++ b/drivers/iio/accel/bmi088-accel-spi.c @@ -58,7 +58,9 @@ static int bmi088_accel_probe(struct spi_device *spi) static int bmi088_accel_remove(struct spi_device *spi) { - return bmi088_accel_core_remove(&spi->dev); + bmi088_accel_core_remove(&spi->dev); + + return 0; } static const struct spi_device_id bmi088_accel_id[] = { diff --git a/drivers/iio/accel/bmi088-accel.h b/drivers/iio/accel/bmi088-accel.h index 5c25f16b672c..5d40c7cf1cbc 100644 --- a/drivers/iio/accel/bmi088-accel.h +++ b/drivers/iio/accel/bmi088-accel.h @@ -13,6 +13,6 @@ extern const struct dev_pm_ops bmi088_accel_pm_ops; int bmi088_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, const char *name, bool block_supported); -int bmi088_accel_core_remove(struct device *dev); +void bmi088_accel_core_remove(struct device *dev); #endif /* BMI088_ACCEL_H */ diff --git a/drivers/iio/accel/fxls8962af-core.c b/drivers/iio/accel/fxls8962af-core.c index f41db9e0249a..32989d91b982 100644 --- a/drivers/iio/accel/fxls8962af-core.c +++ b/drivers/iio/accel/fxls8962af-core.c @@ -22,6 +22,7 @@ #include <linux/regmap.h> #include <linux/iio/buffer.h> +#include <linux/iio/events.h> #include <linux/iio/iio.h> #include <linux/iio/kfifo_buf.h> #include <linux/iio/sysfs.h> @@ -30,6 +31,7 @@ #define FXLS8962AF_INT_STATUS 0x00 #define FXLS8962AF_INT_STATUS_SRC_BOOT BIT(0) +#define FXLS8962AF_INT_STATUS_SRC_SDCD_OT BIT(4) #define FXLS8962AF_INT_STATUS_SRC_BUF BIT(5) #define FXLS8962AF_INT_STATUS_SRC_DRDY BIT(7) #define FXLS8962AF_TEMP_OUT 0x01 @@ -73,6 +75,7 @@ #define FXLS8962AF_ASLP_COUNT_LSB 0x1e #define FXLS8962AF_INT_EN 0x20 +#define FXLS8962AF_INT_EN_SDCD_OT_EN BIT(5) #define FXLS8962AF_INT_EN_BUF_EN BIT(6) #define FXLS8962AF_INT_PIN_SEL 0x21 #define FXLS8962AF_INT_PIN_SEL_MASK GENMASK(7, 0) @@ -96,9 +99,21 @@ #define FXLS8962AF_ORIENT_THS_REG 0x2c #define FXLS8962AF_SDCD_INT_SRC1 0x2d +#define FXLS8962AF_SDCD_INT_SRC1_X_OT BIT(5) +#define FXLS8962AF_SDCD_INT_SRC1_X_POL BIT(4) +#define FXLS8962AF_SDCD_INT_SRC1_Y_OT BIT(3) +#define FXLS8962AF_SDCD_INT_SRC1_Y_POL BIT(2) +#define FXLS8962AF_SDCD_INT_SRC1_Z_OT BIT(1) +#define FXLS8962AF_SDCD_INT_SRC1_Z_POL BIT(0) #define FXLS8962AF_SDCD_INT_SRC2 0x2e #define FXLS8962AF_SDCD_CONFIG1 0x2f +#define FXLS8962AF_SDCD_CONFIG1_Z_OT_EN BIT(3) +#define FXLS8962AF_SDCD_CONFIG1_Y_OT_EN BIT(4) +#define FXLS8962AF_SDCD_CONFIG1_X_OT_EN BIT(5) +#define FXLS8962AF_SDCD_CONFIG1_OT_ELE BIT(7) #define FXLS8962AF_SDCD_CONFIG2 0x30 +#define FXLS8962AF_SDCD_CONFIG2_SDCD_EN BIT(7) +#define FXLS8962AF_SC2_REF_UPDM_AC GENMASK(6, 5) #define FXLS8962AF_SDCD_OT_DBCNT 0x31 #define FXLS8962AF_SDCD_WT_DBCNT 0x32 #define FXLS8962AF_SDCD_LTHS_LSB 0x33 @@ -151,7 +166,11 @@ struct fxls8962af_data { } scan; int64_t timestamp, old_timestamp; /* Only used in hw fifo mode. */ struct iio_mount_matrix orientation; + int irq; u8 watermark; + u8 enable_event; + u16 lower_thres; + u16 upper_thres; }; const struct regmap_config fxls8962af_regmap_conf = { @@ -238,7 +257,7 @@ static int fxls8962af_get_out(struct fxls8962af_data *data, } ret = regmap_bulk_read(data->regmap, chan->address, - &raw_val, (chan->scan_type.storagebits / 8)); + &raw_val, sizeof(data->lower_thres)); if (!is_active) fxls8962af_power_off(data); @@ -451,6 +470,15 @@ static int fxls8962af_write_raw(struct iio_dev *indio_dev, } } +static int fxls8962af_event_setup(struct fxls8962af_data *data, int state) +{ + /* Enable wakeup interrupt */ + int mask = FXLS8962AF_INT_EN_SDCD_OT_EN; + int value = state ? mask : 0; + + return regmap_update_bits(data->regmap, FXLS8962AF_INT_EN, mask, value); +} + static int fxls8962af_set_watermark(struct iio_dev *indio_dev, unsigned val) { struct fxls8962af_data *data = iio_priv(indio_dev); @@ -463,6 +491,217 @@ static int fxls8962af_set_watermark(struct iio_dev *indio_dev, unsigned val) return 0; } +static int __fxls8962af_set_thresholds(struct fxls8962af_data *data, + const struct iio_chan_spec *chan, + enum iio_event_direction dir, + int val) +{ + switch (dir) { + case IIO_EV_DIR_FALLING: + data->lower_thres = val; + return regmap_bulk_write(data->regmap, FXLS8962AF_SDCD_LTHS_LSB, + &data->lower_thres, sizeof(data->lower_thres)); + case IIO_EV_DIR_RISING: + data->upper_thres = val; + return regmap_bulk_write(data->regmap, FXLS8962AF_SDCD_UTHS_LSB, + &data->upper_thres, sizeof(data->upper_thres)); + default: + return -EINVAL; + } +} + +static int fxls8962af_read_event(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 fxls8962af_data *data = iio_priv(indio_dev); + int ret; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_read(data->regmap, FXLS8962AF_SDCD_LTHS_LSB, + &data->lower_thres, sizeof(data->lower_thres)); + if (ret) + return ret; + + *val = sign_extend32(data->lower_thres, chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_EV_DIR_RISING: + ret = regmap_bulk_read(data->regmap, FXLS8962AF_SDCD_UTHS_LSB, + &data->upper_thres, sizeof(data->upper_thres)); + if (ret) + return ret; + + *val = sign_extend32(data->upper_thres, chan->scan_type.realbits - 1); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int fxls8962af_write_event(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 fxls8962af_data *data = iio_priv(indio_dev); + int ret, val_masked; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + if (val < -2048 || val > 2047) + return -EINVAL; + + if (data->enable_event) + return -EBUSY; + + val_masked = val & GENMASK(11, 0); + if (fxls8962af_is_active(data)) { + ret = fxls8962af_standby(data); + if (ret) + return ret; + + ret = __fxls8962af_set_thresholds(data, chan, dir, val_masked); + if (ret) + return ret; + + return fxls8962af_active(data); + } else { + return __fxls8962af_set_thresholds(data, chan, dir, val_masked); + } +} + +static int +fxls8962af_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 fxls8962af_data *data = iio_priv(indio_dev); + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + return !!(FXLS8962AF_SDCD_CONFIG1_X_OT_EN & data->enable_event); + case IIO_MOD_Y: + return !!(FXLS8962AF_SDCD_CONFIG1_Y_OT_EN & data->enable_event); + case IIO_MOD_Z: + return !!(FXLS8962AF_SDCD_CONFIG1_Z_OT_EN & data->enable_event); + default: + return -EINVAL; + } +} + +static int +fxls8962af_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 fxls8962af_data *data = iio_priv(indio_dev); + u8 enable_event, enable_bits; + int ret, value; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + enable_bits = FXLS8962AF_SDCD_CONFIG1_X_OT_EN; + break; + case IIO_MOD_Y: + enable_bits = FXLS8962AF_SDCD_CONFIG1_Y_OT_EN; + break; + case IIO_MOD_Z: + enable_bits = FXLS8962AF_SDCD_CONFIG1_Z_OT_EN; + break; + default: + return -EINVAL; + } + + if (state) + enable_event = data->enable_event | enable_bits; + else + enable_event = data->enable_event & ~enable_bits; + + if (data->enable_event == enable_event) + return 0; + + ret = fxls8962af_standby(data); + if (ret) + return ret; + + /* Enable events */ + value = enable_event | FXLS8962AF_SDCD_CONFIG1_OT_ELE; + ret = regmap_write(data->regmap, FXLS8962AF_SDCD_CONFIG1, value); + if (ret) + return ret; + + /* + * Enable update of SDCD_REF_X/Y/Z values with the current decimated and + * trimmed X/Y/Z acceleration input data. This allows for acceleration + * slope detection with Data(n) to Data(n–1) always used as the input + * to the window comparator. + */ + value = enable_event ? + FXLS8962AF_SDCD_CONFIG2_SDCD_EN | FXLS8962AF_SC2_REF_UPDM_AC : + 0x00; + ret = regmap_write(data->regmap, FXLS8962AF_SDCD_CONFIG2, value); + if (ret) + return ret; + + ret = fxls8962af_event_setup(data, state); + if (ret) + return ret; + + data->enable_event = enable_event; + + if (data->enable_event) { + fxls8962af_active(data); + ret = fxls8962af_power_on(data); + } else { + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + /* Not in buffered mode so disable power */ + ret = fxls8962af_power_off(data); + + iio_device_release_direct_mode(indio_dev); + } + + return ret; +} + +static const struct iio_event_spec fxls8962af_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, +}; + #define FXLS8962AF_CHANNEL(axis, reg, idx) { \ .type = IIO_ACCEL, \ .address = reg, \ @@ -481,6 +720,8 @@ static int fxls8962af_set_watermark(struct iio_dev *indio_dev, unsigned val) .shift = 4, \ .endianness = IIO_BE, \ }, \ + .event_spec = fxls8962af_event, \ + .num_event_specs = ARRAY_SIZE(fxls8962af_event), \ } #define FXLS8962AF_TEMP_CHANNEL { \ @@ -522,6 +763,10 @@ static const struct iio_info fxls8962af_info = { .read_raw = &fxls8962af_read_raw, .write_raw = &fxls8962af_write_raw, .write_raw_get_fmt = fxls8962af_write_raw_get_fmt, + .read_event_value = fxls8962af_read_event, + .write_event_value = fxls8962af_write_event, + .read_event_config = fxls8962af_read_event_config, + .write_event_config = fxls8962af_write_event_config, .read_avail = fxls8962af_read_avail, .hwfifo_set_watermark = fxls8962af_set_watermark, }; @@ -605,7 +850,8 @@ static int fxls8962af_buffer_predisable(struct iio_dev *indio_dev) ret = __fxls8962af_fifo_set_mode(data, false); - fxls8962af_active(data); + if (data->enable_event) + fxls8962af_active(data); return ret; } @@ -614,7 +860,10 @@ static int fxls8962af_buffer_postdisable(struct iio_dev *indio_dev) { struct fxls8962af_data *data = iio_priv(indio_dev); - return fxls8962af_power_off(data); + if (!data->enable_event) + fxls8962af_power_off(data); + + return 0; } static const struct iio_buffer_setup_ops fxls8962af_buffer_ops = { @@ -725,6 +974,45 @@ static int fxls8962af_fifo_flush(struct iio_dev *indio_dev) return count; } +static int fxls8962af_event_interrupt(struct iio_dev *indio_dev) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + s64 ts = iio_get_time_ns(indio_dev); + unsigned int reg; + u64 ev_code; + int ret; + + ret = regmap_read(data->regmap, FXLS8962AF_SDCD_INT_SRC1, ®); + if (ret) + return ret; + + if (reg & FXLS8962AF_SDCD_INT_SRC1_X_OT) { + ev_code = reg & FXLS8962AF_SDCD_INT_SRC1_X_POL ? + IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, + IIO_EV_TYPE_THRESH, ev_code), ts); + } + + if (reg & FXLS8962AF_SDCD_INT_SRC1_Y_OT) { + ev_code = reg & FXLS8962AF_SDCD_INT_SRC1_Y_POL ? + IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, + IIO_EV_TYPE_THRESH, ev_code), ts); + } + + if (reg & FXLS8962AF_SDCD_INT_SRC1_Z_OT) { + ev_code = reg & FXLS8962AF_SDCD_INT_SRC1_Z_POL ? + IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, + IIO_EV_TYPE_THRESH, ev_code), ts); + } + + return 0; +} + static irqreturn_t fxls8962af_interrupt(int irq, void *p) { struct iio_dev *indio_dev = p; @@ -744,6 +1032,14 @@ static irqreturn_t fxls8962af_interrupt(int irq, void *p) return IRQ_HANDLED; } + if (reg & FXLS8962AF_INT_STATUS_SRC_SDCD_OT) { + ret = fxls8962af_event_interrupt(indio_dev); + if (ret < 0) + return IRQ_NONE; + + return IRQ_HANDLED; + } + return IRQ_NONE; } @@ -861,6 +1157,7 @@ int fxls8962af_core_probe(struct device *dev, struct regmap *regmap, int irq) data = iio_priv(indio_dev); dev_set_drvdata(dev, indio_dev); data->regmap = regmap; + data->irq = irq; ret = iio_read_mount_matrix(dev, &data->orientation); if (ret) @@ -930,6 +1227,9 @@ int fxls8962af_core_probe(struct device *dev, struct regmap *regmap, int irq) if (ret) return ret; + if (device_property_read_bool(dev, "wakeup-source")) + device_init_wakeup(dev, true); + return devm_iio_device_register(dev, indio_dev); } EXPORT_SYMBOL_GPL(fxls8962af_core_probe); @@ -955,9 +1255,46 @@ static int __maybe_unused fxls8962af_runtime_resume(struct device *dev) return fxls8962af_active(data); } +static int __maybe_unused fxls8962af_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct fxls8962af_data *data = iio_priv(indio_dev); + + if (device_may_wakeup(dev) && data->enable_event) { + enable_irq_wake(data->irq); + + /* + * Disable buffer, as the buffer is so small the device will wake + * almost immediately. + */ + if (iio_buffer_enabled(indio_dev)) + fxls8962af_buffer_predisable(indio_dev); + } else { + fxls8962af_runtime_suspend(dev); + } + + return 0; +} + +static int __maybe_unused fxls8962af_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct fxls8962af_data *data = iio_priv(indio_dev); + + if (device_may_wakeup(dev) && data->enable_event) { + disable_irq_wake(data->irq); + + if (iio_buffer_enabled(indio_dev)) + fxls8962af_buffer_postenable(indio_dev); + } else { + fxls8962af_runtime_resume(dev); + } + + return 0; +} + const struct dev_pm_ops fxls8962af_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, - pm_runtime_force_resume) + SET_SYSTEM_SLEEP_PM_OPS(fxls8962af_suspend, fxls8962af_resume) SET_RUNTIME_PM_OPS(fxls8962af_runtime_suspend, fxls8962af_runtime_resume, NULL) }; diff --git a/drivers/iio/accel/kxsd9-i2c.c b/drivers/iio/accel/kxsd9-i2c.c index b580d605f848..274b41a6e603 100644 --- a/drivers/iio/accel/kxsd9-i2c.c +++ b/drivers/iio/accel/kxsd9-i2c.c @@ -34,7 +34,9 @@ static int kxsd9_i2c_probe(struct i2c_client *i2c, static int kxsd9_i2c_remove(struct i2c_client *client) { - return kxsd9_common_remove(&client->dev); + kxsd9_common_remove(&client->dev); + + return 0; } static const struct of_device_id kxsd9_of_match[] = { diff --git a/drivers/iio/accel/kxsd9-spi.c b/drivers/iio/accel/kxsd9-spi.c index 7971ec1eeb7e..441e6b764281 100644 --- a/drivers/iio/accel/kxsd9-spi.c +++ b/drivers/iio/accel/kxsd9-spi.c @@ -34,7 +34,9 @@ static int kxsd9_spi_probe(struct spi_device *spi) static int kxsd9_spi_remove(struct spi_device *spi) { - return kxsd9_common_remove(&spi->dev); + kxsd9_common_remove(&spi->dev); + + return 0; } static const struct spi_device_id kxsd9_spi_id[] = { diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c index bf7ed9e7d00f..2faf85ca996e 100644 --- a/drivers/iio/accel/kxsd9.c +++ b/drivers/iio/accel/kxsd9.c @@ -478,7 +478,7 @@ err_power_down: } EXPORT_SYMBOL(kxsd9_common_probe); -int kxsd9_common_remove(struct device *dev) +void kxsd9_common_remove(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct kxsd9_state *st = iio_priv(indio_dev); @@ -489,8 +489,6 @@ int kxsd9_common_remove(struct device *dev) pm_runtime_put_noidle(dev); pm_runtime_disable(dev); kxsd9_power_down(st); - - return 0; } EXPORT_SYMBOL(kxsd9_common_remove); diff --git a/drivers/iio/accel/kxsd9.h b/drivers/iio/accel/kxsd9.h index 5e3ca212f5be..c04dbfa4e0d0 100644 --- a/drivers/iio/accel/kxsd9.h +++ b/drivers/iio/accel/kxsd9.h @@ -8,6 +8,6 @@ int kxsd9_common_probe(struct device *dev, struct regmap *map, const char *name); -int kxsd9_common_remove(struct device *dev); +void kxsd9_common_remove(struct device *dev); extern const struct dev_pm_ops kxsd9_dev_pm_ops; diff --git a/drivers/iio/accel/mma7455.h b/drivers/iio/accel/mma7455.h index 4e3fa988f690..1fcc4b64b3af 100644 --- a/drivers/iio/accel/mma7455.h +++ b/drivers/iio/accel/mma7455.h @@ -11,6 +11,6 @@ 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); +void mma7455_core_remove(struct device *dev); #endif diff --git a/drivers/iio/accel/mma7455_core.c b/drivers/iio/accel/mma7455_core.c index 922bd38ff6ea..777c6c384b09 100644 --- a/drivers/iio/accel/mma7455_core.c +++ b/drivers/iio/accel/mma7455_core.c @@ -294,7 +294,7 @@ int mma7455_core_probe(struct device *dev, struct regmap *regmap, } EXPORT_SYMBOL_GPL(mma7455_core_probe); -int mma7455_core_remove(struct device *dev) +void mma7455_core_remove(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct mma7455_data *mma7455 = iio_priv(indio_dev); @@ -304,8 +304,6 @@ int mma7455_core_remove(struct device *dev) regmap_write(mma7455->regmap, MMA7455_REG_MCTL, MMA7455_MCTL_MODE_STANDBY); - - return 0; } EXPORT_SYMBOL_GPL(mma7455_core_remove); diff --git a/drivers/iio/accel/mma7455_i2c.c b/drivers/iio/accel/mma7455_i2c.c index cddeaa9e230a..8a5256516f9f 100644 --- a/drivers/iio/accel/mma7455_i2c.c +++ b/drivers/iio/accel/mma7455_i2c.c @@ -28,7 +28,9 @@ static int mma7455_i2c_probe(struct i2c_client *i2c, static int mma7455_i2c_remove(struct i2c_client *i2c) { - return mma7455_core_remove(&i2c->dev); + mma7455_core_remove(&i2c->dev); + + return 0; } static const struct i2c_device_id mma7455_i2c_ids[] = { diff --git a/drivers/iio/accel/mma7455_spi.c b/drivers/iio/accel/mma7455_spi.c index eb82cdfa8abc..ecf690692dcc 100644 --- a/drivers/iio/accel/mma7455_spi.c +++ b/drivers/iio/accel/mma7455_spi.c @@ -24,7 +24,9 @@ static int mma7455_spi_probe(struct spi_device *spi) static int mma7455_spi_remove(struct spi_device *spi) { - return mma7455_core_remove(&spi->dev); + mma7455_core_remove(&spi->dev); + + return 0; } static const struct spi_device_id mma7455_spi_ids[] = { diff --git a/drivers/iio/accel/mma7660.c b/drivers/iio/accel/mma7660.c index 47f5cd66e996..cd6cdf2c51b0 100644 --- a/drivers/iio/accel/mma7660.c +++ b/drivers/iio/accel/mma7660.c @@ -254,7 +254,7 @@ static const struct of_device_id mma7660_of_match[] = { }; MODULE_DEVICE_TABLE(of, mma7660_of_match); -static const struct acpi_device_id mma7660_acpi_id[] = { +static const struct acpi_device_id __maybe_unused mma7660_acpi_id[] = { {"MMA7660", 0}, {} }; diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c index cb753a43533c..c6b75308148a 100644 --- a/drivers/iio/accel/sca3000.c +++ b/drivers/iio/accel/sca3000.c @@ -731,8 +731,7 @@ static int sca3000_read_raw(struct iio_dev *indio_dev, return ret; } *val = (be16_to_cpup((__be16 *)st->rx) >> 3) & 0x1FFF; - *val = ((*val) << (sizeof(*val) * 8 - 13)) >> - (sizeof(*val) * 8 - 13); + *val = sign_extend32(*val, 12); } else { /* get the temperature when available */ ret = sca3000_read_data_short(st, diff --git a/drivers/iio/accel/st_accel_core.c b/drivers/iio/accel/st_accel_core.c index f1e6ec380667..31ea19d0ba71 100644 --- a/drivers/iio/accel/st_accel_core.c +++ b/drivers/iio/accel/st_accel_core.c @@ -1210,7 +1210,7 @@ static int apply_acpi_orientation(struct iio_dev *indio_dev) }; - adev = ACPI_COMPANION(adata->dev); + adev = ACPI_COMPANION(indio_dev->dev.parent); if (!adev) return 0; @@ -1334,7 +1334,8 @@ EXPORT_SYMBOL(st_accel_get_settings); int st_accel_common_probe(struct iio_dev *indio_dev) { struct st_sensor_data *adata = iio_priv(indio_dev); - struct st_sensors_platform_data *pdata = dev_get_platdata(adata->dev); + struct device *parent = indio_dev->dev.parent; + struct st_sensors_platform_data *pdata = dev_get_platdata(parent); int err; indio_dev->modes = INDIO_DIRECT_MODE; @@ -1354,7 +1355,7 @@ int st_accel_common_probe(struct iio_dev *indio_dev) */ err = apply_acpi_orientation(indio_dev); if (err) { - err = iio_read_mount_matrix(adata->dev, &adata->mount_matrix); + err = iio_read_mount_matrix(parent, &adata->mount_matrix); if (err) return err; } @@ -1380,32 +1381,10 @@ int st_accel_common_probe(struct iio_dev *indio_dev) return err; } - err = iio_device_register(indio_dev); - if (err) - goto st_accel_device_register_error; - - dev_info(&indio_dev->dev, "registered accelerometer %s\n", - indio_dev->name); - - return 0; - -st_accel_device_register_error: - if (adata->irq > 0) - st_sensors_deallocate_trigger(indio_dev); - return err; + return devm_iio_device_register(parent, indio_dev); } EXPORT_SYMBOL(st_accel_common_probe); -void st_accel_common_remove(struct iio_dev *indio_dev) -{ - struct st_sensor_data *adata = iio_priv(indio_dev); - - iio_device_unregister(indio_dev); - if (adata->irq > 0) - st_sensors_deallocate_trigger(indio_dev); -} -EXPORT_SYMBOL(st_accel_common_remove); - MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); MODULE_DESCRIPTION("STMicroelectronics accelerometers driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c index f711756e41e3..c0ce78eebad9 100644 --- a/drivers/iio/accel/st_accel_i2c.c +++ b/drivers/iio/accel/st_accel_i2c.c @@ -177,27 +177,7 @@ static int st_accel_i2c_probe(struct i2c_client *client) if (ret) return ret; - ret = st_accel_common_probe(indio_dev); - if (ret < 0) - goto st_accel_power_off; - - return 0; - -st_accel_power_off: - st_sensors_power_disable(indio_dev); - - return ret; -} - -static int st_accel_i2c_remove(struct i2c_client *client) -{ - struct iio_dev *indio_dev = i2c_get_clientdata(client); - - st_sensors_power_disable(indio_dev); - - st_accel_common_remove(indio_dev); - - return 0; + return st_accel_common_probe(indio_dev); } static struct i2c_driver st_accel_driver = { @@ -207,7 +187,6 @@ static struct i2c_driver st_accel_driver = { .acpi_match_table = ACPI_PTR(st_accel_acpi_match), }, .probe_new = st_accel_i2c_probe, - .remove = st_accel_i2c_remove, .id_table = st_accel_id_table, }; module_i2c_driver(st_accel_driver); diff --git a/drivers/iio/accel/st_accel_spi.c b/drivers/iio/accel/st_accel_spi.c index bb45d9ff95b8..b74a1c6d03de 100644 --- a/drivers/iio/accel/st_accel_spi.c +++ b/drivers/iio/accel/st_accel_spi.c @@ -127,27 +127,7 @@ static int st_accel_spi_probe(struct spi_device *spi) if (err) return err; - err = st_accel_common_probe(indio_dev); - if (err < 0) - goto st_accel_power_off; - - return 0; - -st_accel_power_off: - st_sensors_power_disable(indio_dev); - - return err; -} - -static int st_accel_spi_remove(struct spi_device *spi) -{ - struct iio_dev *indio_dev = spi_get_drvdata(spi); - - st_sensors_power_disable(indio_dev); - - st_accel_common_remove(indio_dev); - - return 0; + return st_accel_common_probe(indio_dev); } static const struct spi_device_id st_accel_id_table[] = { @@ -177,7 +157,6 @@ static struct spi_driver st_accel_driver = { .of_match_table = st_accel_of_match, }, .probe = st_accel_spi_probe, - .remove = st_accel_spi_remove, .id_table = st_accel_id_table, }; module_spi_driver(st_accel_driver); |