summaryrefslogtreecommitdiffstats
path: root/drivers/iio/accel
diff options
context:
space:
mode:
authorSalah Triki <salah.triki@gmail.com>2026-09-17 14:00:52 +0100
committerJonathan Cameron <jonathan.cameron@oss.qualcomm.com>2026-09-30 18:59:55 +0100
commite8dd273d199f5bbe295a8e3d8b777c5505b6b2e1 (patch)
tree767c7e109d3e6b9ec6549e99dfec9085d01318ad /drivers/iio/accel
downloadlinux-stable-e8dd273d199f5bbe295a8e3d8b777c5505b6b2e1.tar.gz
linux-stable-e8dd273d199f5bbe295a8e3d8b777c5505b6b2e1.zip
iio: proximity: isl29501: Fix return type of isl29501_register_writegrafted
isl29501_register_write() was returning a u32 instead of an int. Since the function returns negative error codes (such as -ERANGE or return values from i2c_smbus_write_byte_data()), returning an unsigned integer type prevents callers from correctly checking for negative error conditions. Fix this by changing the function return type from u32 to int. This was found through manual code review. Fixes: 1c28799257bc ("iio: light: isl29501: Add support for the ISL29501 ToF sensor.") Signed-off-by: Salah Triki <salah.triki@gmail.com> Reviewed-by: Joshua Crofts <joshua.crofts1@gmail.com> Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Diffstat (limited to 'drivers/iio/accel')
-rw-r--r--drivers/iio/accel/Kconfig708
-rw-r--r--drivers/iio/accel/Makefile90
-rw-r--r--drivers/iio/accel/adis16201.c303
-rw-r--r--drivers/iio/accel/adis16209.c313
-rw-r--r--drivers/iio/accel/adxl313.h114
-rw-r--r--drivers/iio/accel/adxl313_core.c1328
-rw-r--r--drivers/iio/accel/adxl313_i2c.c98
-rw-r--r--drivers/iio/accel/adxl313_spi.c124
-rw-r--r--drivers/iio/accel/adxl345.h126
-rw-r--r--drivers/iio/accel/adxl345_core.c2042
-rw-r--r--drivers/iio/accel/adxl345_i2c.c79
-rw-r--r--drivers/iio/accel/adxl345_spi.c96
-rw-r--r--drivers/iio/accel/adxl355.h39
-rw-r--r--drivers/iio/accel/adxl355_core.c833
-rw-r--r--drivers/iio/accel/adxl355_i2c.c66
-rw-r--r--drivers/iio/accel/adxl355_spi.c69
-rw-r--r--drivers/iio/accel/adxl367.c1497
-rw-r--r--drivers/iio/accel/adxl367.h23
-rw-r--r--drivers/iio/accel/adxl367_i2c.c88
-rw-r--r--drivers/iio/accel/adxl367_spi.c165
-rw-r--r--drivers/iio/accel/adxl372.c1335
-rw-r--r--drivers/iio/accel/adxl372.h33
-rw-r--r--drivers/iio/accel/adxl372_i2c.c74
-rw-r--r--drivers/iio/accel/adxl372_spi.c65
-rw-r--r--drivers/iio/accel/adxl380.c2007
-rw-r--r--drivers/iio/accel/adxl380.h38
-rw-r--r--drivers/iio/accel/adxl380_i2c.c67
-rw-r--r--drivers/iio/accel/adxl380_spi.c69
-rw-r--r--drivers/iio/accel/bma180.c1134
-rw-r--r--drivers/iio/accel/bma220.h28
-rw-r--r--drivers/iio/accel/bma220_core.c583
-rw-r--r--drivers/iio/accel/bma220_i2c.c68
-rw-r--r--drivers/iio/accel/bma220_spi.c59
-rw-r--r--drivers/iio/accel/bma400.h192
-rw-r--r--drivers/iio/accel/bma400_core.c1808
-rw-r--r--drivers/iio/accel/bma400_i2c.c55
-rw-r--r--drivers/iio/accel/bma400_spi.c114
-rw-r--r--drivers/iio/accel/bmc150-accel-core.c1868
-rw-r--r--drivers/iio/accel/bmc150-accel-i2c.c293
-rw-r--r--drivers/iio/accel/bmc150-accel-spi.c83
-rw-r--r--drivers/iio/accel/bmc150-accel.h97
-rw-r--r--drivers/iio/accel/bmi088-accel-core.c627
-rw-r--r--drivers/iio/accel/bmi088-accel-i2c.c69
-rw-r--r--drivers/iio/accel/bmi088-accel-spi.c97
-rw-r--r--drivers/iio/accel/bmi088-accel.h25
-rw-r--r--drivers/iio/accel/cros_ec_accel_legacy.c252
-rw-r--r--drivers/iio/accel/da280.c186
-rw-r--r--drivers/iio/accel/da311.c289
-rw-r--r--drivers/iio/accel/dmard06.c229
-rw-r--r--drivers/iio/accel/dmard09.c151
-rw-r--r--drivers/iio/accel/dmard10.c250
-rw-r--r--drivers/iio/accel/fxls8962af-core.c1318
-rw-r--r--drivers/iio/accel/fxls8962af-i2c.c59
-rw-r--r--drivers/iio/accel/fxls8962af-spi.c57
-rw-r--r--drivers/iio/accel/fxls8962af.h24
-rw-r--r--drivers/iio/accel/hid-sensor-accel-3d.c447
-rw-r--r--drivers/iio/accel/kionix-kx022a-i2c.c72
-rw-r--r--drivers/iio/accel/kionix-kx022a-spi.c72
-rw-r--r--drivers/iio/accel/kionix-kx022a.c1474
-rw-r--r--drivers/iio/accel/kionix-kx022a.h206
-rw-r--r--drivers/iio/accel/kxcjk-1013.c1684
-rw-r--r--drivers/iio/accel/kxsd9-i2c.c64
-rw-r--r--drivers/iio/accel/kxsd9-spi.c65
-rw-r--r--drivers/iio/accel/kxsd9.c511
-rw-r--r--drivers/iio/accel/kxsd9.h13
-rw-r--r--drivers/iio/accel/mc3230.c261
-rw-r--r--drivers/iio/accel/mma7455.h16
-rw-r--r--drivers/iio/accel/mma7455_core.c315
-rw-r--r--drivers/iio/accel/mma7455_i2c.c62
-rw-r--r--drivers/iio/accel/mma7455_spi.c50
-rw-r--r--drivers/iio/accel/mma7660.c295
-rw-r--r--drivers/iio/accel/mma8452.c1855
-rw-r--r--drivers/iio/accel/mma9551.c605
-rw-r--r--drivers/iio/accel/mma9551_core.c803
-rw-r--r--drivers/iio/accel/mma9551_core.h73
-rw-r--r--drivers/iio/accel/mma9553.c1240
-rw-r--r--drivers/iio/accel/msa311.c1295
-rw-r--r--drivers/iio/accel/mxc4005.c600
-rw-r--r--drivers/iio/accel/mxc6255.c192
-rw-r--r--drivers/iio/accel/sca3000.c1526
-rw-r--r--drivers/iio/accel/sca3300.c685
-rw-r--r--drivers/iio/accel/ssp_accel_sensor.c144
-rw-r--r--drivers/iio/accel/st_accel.h58
-rw-r--r--drivers/iio/accel/st_accel_buffer.c66
-rw-r--r--drivers/iio/accel/st_accel_core.c1622
-rw-r--r--drivers/iio/accel/st_accel_i2c.c218
-rw-r--r--drivers/iio/accel/st_accel_spi.c186
-rw-r--r--drivers/iio/accel/stk8312.c658
-rw-r--r--drivers/iio/accel/stk8ba50.c552
89 files changed, 39889 insertions, 0 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
new file mode 100644
index 000000000..4094299e2
--- /dev/null
+++ b/drivers/iio/accel/Kconfig
@@ -0,0 +1,708 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Accelerometer drivers
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Accelerometers"
+
+config ADIS16201
+ tristate "Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer"
+ depends on SPI
+ select IIO_ADIS_LIB
+ select IIO_ADIS_LIB_BUFFER if IIO_BUFFER
+ help
+ Say Y here to build support for Analog Devices adis16201 dual-axis
+ digital inclinometer and accelerometer.
+
+ To compile this driver as a module, say M here: the module will
+ be called adis16201.
+
+config ADIS16209
+ tristate "Analog Devices ADIS16209 Dual-Axis Digital Inclinometer and Accelerometer"
+ depends on SPI
+ select IIO_ADIS_LIB
+ select IIO_ADIS_LIB_BUFFER if IIO_BUFFER
+ help
+ Say Y here to build support for Analog Devices adis16209 dual-axis digital inclinometer
+ and accelerometer.
+
+ 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
+
+config ADXL345_I2C
+ tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer I2C Driver"
+ depends on !INPUT_ADXL34X
+ depends on I2C
+ select ADXL345
+ select REGMAP_I2C
+ help
+ Say Y here if you want to build support for the Analog Devices
+ ADXL345 or ADXL375 3-axis digital accelerometer.
+
+ To compile this driver as a module, choose M here: the module
+ will be called adxl345_i2c and you will also get adxl345_core
+ for the core module. INPUT_ADXL34X share compatibles with this
+ driver, do not add both modules to the kernel.
+
+config ADXL345_SPI
+ tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer SPI Driver"
+ depends on !INPUT_ADXL34X
+ depends on SPI
+ select ADXL345
+ select REGMAP_SPI
+ help
+ Say Y here if you want to build support for the Analog Devices
+ ADXL345 or ADXL375 3-axis digital accelerometer.
+
+ To compile this driver as a module, choose M here: the module
+ will be called adxl345_spi and you will also get adxl345_core
+ for the core module. INPUT_ADXL34X share compatibles with this
+ driver, do not add both modules to the kernel.
+
+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 ADXL367
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config ADXL367_SPI
+ tristate "Analog Devices ADXL367 3-Axis Accelerometer SPI Driver"
+ depends on SPI
+ select ADXL367
+ select REGMAP_SPI
+ help
+ Say yes here to add support for the Analog Devices ADXL367 triaxial
+ acceleration sensor.
+ To compile this driver as a module, choose M here: the
+ module will be called adxl367_spi.
+
+config ADXL367_I2C
+ tristate "Analog Devices ADXL367 3-Axis Accelerometer I2C Driver"
+ depends on I2C
+ select ADXL367
+ select REGMAP_I2C
+ help
+ Say yes here to add support for the Analog Devices ADXL367 triaxial
+ acceleration sensor.
+ To compile this driver as a module, choose M here: the
+ module will be called adxl367_i2c.
+
+config ADXL372
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config ADXL372_SPI
+ tristate "Analog Devices ADXL371/ADXL372 3-Axis Accelerometer SPI Driver"
+ depends on SPI
+ select ADXL372
+ select REGMAP_SPI
+ help
+ Say yes here to add support for the Analog Devices ADXL371/ADXL372
+ triaxial acceleration sensor.
+ To compile this driver as a module, choose M here: the
+ module will be called adxl372_spi.
+
+config ADXL372_I2C
+ tristate "Analog Devices ADXL371/ADXL372 3-Axis Accelerometer I2C Driver"
+ depends on I2C
+ select ADXL372
+ select REGMAP_I2C
+ help
+ Say yes here to add support for the Analog Devices ADXL371/ADXL372
+ triaxial acceleration sensor.
+ To compile this driver as a module, choose M here: the
+ module will be called adxl372_i2c.
+
+config ADXL380
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config ADXL380_SPI
+ tristate "Analog Devices ADXL380 3-Axis Accelerometer SPI Driver"
+ depends on SPI
+ select ADXL380
+ select REGMAP_SPI
+ help
+ Say yes here to add support for the Analog Devices ADXL380 triaxial
+ acceleration sensor.
+ To compile this driver as a module, choose M here: the
+ module will be called adxl380_spi.
+
+config ADXL380_I2C
+ tristate "Analog Devices ADXL380 3-Axis Accelerometer I2C Driver"
+ depends on I2C
+ select ADXL380
+ select REGMAP_I2C
+ help
+ Say yes here to add support for the Analog Devices ADXL380 triaxial
+ acceleration sensor.
+ To compile this driver as a module, choose M here: the
+ module will be called adxl380_i2c.
+
+config BMA180
+ tristate "Bosch BMA023/BMA1x0/BMA250 3-Axis Accelerometer Driver"
+ depends on I2C && INPUT_BMA150=n
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say Y here if you want to build a driver for the Bosch BMA023, BMA150
+ BMA180, BMA250 or SMB380 triaxial acceleration sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bma180.
+
+config BMA220
+ tristate "Bosch BMA220 3-Axis Accelerometer Driver"
+ depends on I2C || SPI
+ select REGMAP
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select BMA220_I2C if I2C
+ select BMA220_SPI if SPI
+ help
+ Say yes here to add support for the Bosch BMA220 triaxial
+ acceleration sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bma220_core and you will also get
+ bma220_i2c if I2C is enabled and bma220_spi if SPI is
+ enabled.
+
+config BMA220_I2C
+ tristate
+ select REGMAP_I2C
+ depends on BMA220
+
+config BMA220_SPI
+ tristate
+ select REGMAP_SPI
+ depends on BMA220
+
+config BMA400
+ tristate "Bosch BMA400 3-Axis Accelerometer Driver"
+ select REGMAP
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select BMA400_I2C if I2C
+ select BMA400_SPI if SPI
+ help
+ Say Y here if you want to build a driver for the Bosch BMA400
+ triaxial acceleration sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bma400_core and you will also get
+ bma400_i2c if I2C is enabled and bma400_spi if SPI is
+ enabled.
+
+config BMA400_I2C
+ tristate
+ select REGMAP_I2C
+ depends on BMA400
+
+config BMA400_SPI
+ tristate
+ select REGMAP_SPI
+ depends on BMA400
+
+config BMC150_ACCEL
+ tristate "Bosch BMC150 Accelerometer Driver"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select REGMAP
+ select BMC150_ACCEL_I2C if I2C
+ select BMC150_ACCEL_SPI if SPI
+ help
+ Say yes here to build support for the following Bosch accelerometers:
+ BMA222, BMA222E, BMA250E, BMA253, BMA254, BMA255, BMA280, BMC150, BMC156
+ BMI055.
+
+ Note that some of these are combo modules:
+ - BMC150/BMC156: accelerometer and magnetometer
+ - BMI055: accelerometer and gyroscope
+
+ This driver is only implementing accelerometer part, which has
+ its own address and register map.
+
+config BMC150_ACCEL_I2C
+ tristate
+ select REGMAP_I2C
+
+config BMC150_ACCEL_SPI
+ tristate
+ select REGMAP_SPI
+
+config BMI088_ACCEL
+ tristate "Bosch BMI088 Accelerometer Driver"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select REGMAP
+ select BMI088_ACCEL_SPI if SPI
+ select BMI088_ACCEL_I2C if I2C
+ help
+ Say yes here to build support for the following Bosch accelerometers:
+ BMI088, BMI085, BMI090L. Note that all of these are combo module that
+ include both accelerometer and gyroscope.
+
+ This driver only implements the accelerometer part, which has its own
+ address and register map. BMG160 provides the gyroscope driver.
+
+config BMI088_ACCEL_I2C
+ tristate
+ select REGMAP_I2C
+
+config BMI088_ACCEL_SPI
+ tristate
+ select REGMAP_SPI
+
+config DA280
+ tristate "MiraMEMS DA280 3-axis 14-bit digital accelerometer driver"
+ depends on I2C
+ help
+ Say yes here to build support for the MiraMEMS DA280 3-axis 14-bit
+ digital accelerometer.
+
+ To compile this driver as a module, choose M here: the
+ module will be called da280.
+
+config DA311
+ tristate "MiraMEMS DA311 3-axis 12-bit digital accelerometer driver"
+ depends on I2C
+ help
+ Say yes here to build support for the MiraMEMS DA311 3-axis 12-bit
+ digital accelerometer.
+
+ To compile this driver as a module, choose M here: the
+ module will be called da311.
+
+config DMARD06
+ tristate "Domintech DMARD06 Digital Accelerometer Driver"
+ depends on I2C
+ help
+ Say yes here to build support for the Domintech low-g tri-axial
+ digital accelerometers: DMARD05, DMARD06, DMARD07.
+
+ To compile this driver as a module, choose M here: the
+ module will be called dmard06.
+
+config DMARD09
+ tristate "Domintech DMARD09 3-axis Accelerometer Driver"
+ depends on I2C
+ help
+ Say yes here to get support for the Domintech DMARD09 3-axis
+ accelerometer.
+
+ Choosing M will build the driver as a module. If so, the module
+ will be called dmard09.
+
+config DMARD10
+ tristate "Domintech DMARD10 3-axis Accelerometer Driver"
+ depends on I2C
+ help
+ Say yes here to get support for the Domintech DMARD10 3-axis
+ accelerometer.
+
+ Choosing M will build the driver as a module. If so, the module
+ will be called dmard10.
+
+config FXLS8962AF
+ tristate
+ depends on I2C || !I2C # cannot be built-in for modular I2C
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
+
+config FXLS8962AF_I2C
+ tristate "NXP FXLS8962AF/FXLS8964AF Accelerometer I2C Driver"
+ depends on I2C
+ select FXLS8962AF
+ select REGMAP_I2C
+ help
+ Say yes here to build support for the NXP 3-axis automotive
+ accelerometer FXLS8962AF/FXLS8964AF with I2C support.
+
+ To compile this driver as a module, choose M here: the module
+ will be called fxls8962af_i2c.
+
+config FXLS8962AF_SPI
+ tristate "NXP FXLS8962AF/FXLS8964AF Accelerometer SPI Driver"
+ depends on SPI
+ depends on I2C || !I2C
+ select FXLS8962AF
+ select REGMAP_SPI
+ help
+ Say yes here to build support for the NXP 3-axis automotive
+ accelerometer FXLS8962AF/FXLS8964AF with SPI support.
+
+ To compile this driver as a module, choose M here: the module
+ will be called fxls8962af_spi.
+
+config HID_SENSOR_ACCEL_3D
+ depends on HID_SENSOR_HUB
+ select IIO_BUFFER
+ select HID_SENSOR_IIO_COMMON
+ select HID_SENSOR_IIO_TRIGGER
+ tristate "HID Accelerometers 3D"
+ help
+ Say yes here to build support for the HID SENSOR
+ accelerometers 3D.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hid-sensor-accel-3d.
+
+config IIO_CROS_EC_ACCEL_LEGACY
+ tristate "ChromeOS EC Legacy Accelerometer Sensor"
+ depends on IIO_CROS_EC_SENSORS_CORE
+ help
+ Say yes here to get support for accelerometers on Chromebook using
+ legacy EC firmware.
+ Sensor data is retrieved through IO memory.
+ Newer devices should use IIO_CROS_EC_SENSORS.
+
+config IIO_ST_ACCEL_3AXIS
+ tristate "STMicroelectronics accelerometers 3-Axis Driver"
+ depends on (I2C || SPI_MASTER) && SYSFS
+ depends on !SENSORS_LIS3_I2C
+ depends on !SENSORS_LIS3_SPI
+ select IIO_ST_SENSORS_CORE
+ select IIO_TRIGGERED_BUFFER if (IIO_BUFFER)
+ help
+ Say yes here to build support for STMicroelectronics accelerometers:
+ LSM303C, LSM303DLH, LSM303DLHC, LIS3DH, LSM330D, LSM330DL, LSM330DLC,
+ LIS331DLH, LSM303DL, LSM303DLM, LSM330, LIS2DH12, H3LIS331DL,
+ LNG2DM, LIS3DE, LIS2DE12, LIS2HH12
+
+ Also need to enable at least one of I2C and SPI interface drivers
+ below.
+
+config IIO_ST_ACCEL_I2C_3AXIS
+ tristate "STMicroelectronics accelerometers 3-Axis I2C Interface"
+ depends on I2C && IIO_ST_ACCEL_3AXIS
+ default I2C && IIO_ST_ACCEL_3AXIS
+ select IIO_ST_SENSORS_I2C
+ help
+ Build support for STMicroelectronics accelerometers I2C interface.
+
+ To compile this driver as a module, choose M here. The module
+ will be called st_accel_i2c.
+
+config IIO_ST_ACCEL_SPI_3AXIS
+ tristate "STMicroelectronics accelerometers 3-Axis SPI Interface"
+ depends on SPI_MASTER && IIO_ST_ACCEL_3AXIS
+ default SPI_MASTER && IIO_ST_ACCEL_3AXIS
+ select IIO_ST_SENSORS_SPI
+ help
+ Build support for STMicroelectronics accelerometers SPI interface.
+
+ To compile this driver as a module, choose M here. The module
+ will be called st_accel_spi.
+
+config IIO_KX022A
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config IIO_KX022A_SPI
+ tristate "Kionix KX022A tri-axis digital accelerometer SPI interface"
+ depends on SPI
+ select IIO_KX022A
+ select REGMAP_SPI
+ help
+ Enable support for the Kionix digital tri-axis accelerometers
+ connected to SPI interface. Supported devices are:
+ KX022A, KX132-1211, KX132ACR-LBZ
+
+config IIO_KX022A_I2C
+ tristate "Kionix KX022A tri-axis digital accelerometer I2C interface"
+ depends on I2C
+ select IIO_KX022A
+ select REGMAP_I2C
+ help
+ Enable support for the Kionix digital tri-axis accelerometers
+ connected to I2C interface. Supported devices are:
+ KX022A, KX132-1211, KX132ACR-LBZ
+
+config KXSD9
+ tristate "Kionix KXSD9 Accelerometer Driver"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to build support for the Kionix KXSD9 accelerometer.
+ It can be accessed using an (optional) SPI or I2C interface.
+
+ To compile this driver as a module, choose M here: the module
+ will be called kxsd9.
+
+config KXSD9_SPI
+ tristate "Kionix KXSD9 SPI transport"
+ depends on KXSD9
+ depends on SPI
+ default KXSD9
+ select REGMAP_SPI
+ help
+ Say yes here to enable the Kionix KXSD9 accelerometer
+ SPI transport channel.
+
+config KXSD9_I2C
+ tristate "Kionix KXSD9 I2C transport"
+ depends on KXSD9
+ depends on I2C
+ default KXSD9
+ select REGMAP_I2C
+ help
+ Say yes here to enable the Kionix KXSD9 accelerometer
+ I2C transport channel.
+
+config KXCJK1013
+ tristate "Kionix 3-Axis Accelerometer Driver"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say Y here if you want to build a driver for the Kionix KXCJK-1013
+ triaxial acceleration sensor. This driver also supports KXCJ9-1008,
+ KXTJ2-1009 and KXTF9.
+
+ To compile this driver as a module, choose M here: the module will
+ be called kxcjk-1013.
+
+config MC3230
+ tristate "mCube MC3230 Digital Accelerometer Driver"
+ depends on I2C
+ help
+ Say yes here to build support for the mCube MC3230 low-g tri-axial
+ digital accelerometer.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mc3230.
+
+config MMA7455
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config MMA7455_I2C
+ tristate "Freescale MMA7455L/MMA7456L Accelerometer I2C Driver"
+ depends on I2C
+ select MMA7455
+ select REGMAP_I2C
+ help
+ Say yes here to build support for the Freescale MMA7455L and
+ MMA7456L 3-axis accelerometer.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mma7455_i2c.
+
+config MMA7455_SPI
+ tristate "Freescale MMA7455L/MMA7456L Accelerometer SPI Driver"
+ depends on SPI_MASTER
+ select MMA7455
+ select REGMAP_SPI
+ help
+ Say yes here to build support for the Freescale MMA7455L and
+ MMA7456L 3-axis accelerometer.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mma7455_spi.
+
+config MMA7660
+ tristate "Freescale MMA7660FC 3-Axis Accelerometer Driver"
+ depends on I2C
+ help
+ Say yes here to get support for the Freescale MMA7660FC 3-Axis
+ accelerometer.
+
+ Choosing M will build the driver as a module. If so, the module
+ will be called mma7660.
+
+config MMA8452
+ tristate "Freescale / NXP MMA8452Q and similar Accelerometers Driver"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to build support for the following Freescale / NXP 3-axis
+ accelerometers: MMA8451Q, MMA8452Q, MMA8453Q, MMA8652FC, MMA8653FC,
+ FXLS8471Q.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mma8452.
+
+config MMA9551_CORE
+ tristate
+
+config MMA9551
+ tristate "Freescale MMA9551L Intelligent Motion-Sensing Platform Driver"
+ depends on I2C
+ select MMA9551_CORE
+
+ help
+ Say yes here to build support for the Freescale MMA9551L
+ Intelligent Motion-Sensing Platform Driver.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mma9551.
+
+config MMA9553
+ tristate "Freescale MMA9553L Intelligent Pedometer Platform Driver"
+ depends on I2C
+ select MMA9551_CORE
+ help
+ Say yes here to build support for the Freescale MMA9553L
+ Intelligent Pedometer Platform Driver.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mma9553.
+
+config MSA311
+ tristate "MEMSensing Digital 3-Axis Accelerometer Driver"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select REGMAP_I2C
+ help
+ Say yes here to build support for the MEMSensing MSA311
+ accelerometer driver.
+
+ To compile this driver as a module, choose M here: the module will be
+ called msa311.
+
+config MXC4005
+ tristate "Memsic MXC4005XC 3-Axis Accelerometer Driver"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ select REGMAP_I2C
+ help
+ Say yes here to build support for the Memsic MXC4005XC 3-axis
+ accelerometer.
+
+ To compile this driver as a module, choose M. The module will be
+ called mxc4005.
+
+config MXC6255
+ tristate "Memsic MXC6255 Orientation Sensing Accelerometer Driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say yes here to build support for the Memsic MXC6255 Orientation
+ Sensing Accelerometer Driver.
+
+ To compile this driver as a module, choose M here: the module will be
+ called mxc6255.
+
+config SCA3000
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
+ depends on SPI
+ tristate "VTI SCA3000 series accelerometers"
+ help
+ Say Y here to build support for the VTI SCA3000 series of SPI
+ accelerometers. These devices use a hardware ring buffer.
+
+ To compile this driver as a module, say M here: the module will be
+ called sca3000.
+
+config SCA3300
+ tristate "Murata SCA3300 3-Axis Accelerometer Driver"
+ depends on SPI
+ select CRC8
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to build support for Murata SCA3300 3-Axis
+ accelerometer.
+
+ To compile this driver as a module, choose M here: the module will be
+ called sca3300.
+
+config STK8312
+ tristate "Sensortek STK8312 3-Axis Accelerometer Driver"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to get support for the Sensortek STK8312 3-axis
+ accelerometer.
+
+ Choosing M will build the driver as a module. If so, the module
+ will be called stk8312.
+
+config STK8BA50
+ tristate "Sensortek STK8BA50 3-Axis Accelerometer Driver"
+ depends on I2C
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to get support for the Sensortek STK8BA50 3-axis
+ accelerometer.
+
+ Choosing M will build the driver as a module. If so, the module
+ will be called stk8ba50.
+
+endmenu
diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile
new file mode 100644
index 000000000..fa440a859
--- /dev/null
+++ b/drivers/iio/accel/Makefile
@@ -0,0 +1,90 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for industrial I/O accelerometer drivers
+#
+
+# 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_ADXL367) += adxl367.o
+obj-$(CONFIG_ADXL367_I2C) += adxl367_i2c.o
+obj-$(CONFIG_ADXL367_SPI) += adxl367_spi.o
+obj-$(CONFIG_ADXL372) += adxl372.o
+obj-$(CONFIG_ADXL372_I2C) += adxl372_i2c.o
+obj-$(CONFIG_ADXL372_SPI) += adxl372_spi.o
+obj-$(CONFIG_ADXL380) += adxl380.o
+obj-$(CONFIG_ADXL380_I2C) += adxl380_i2c.o
+obj-$(CONFIG_ADXL380_SPI) += adxl380_spi.o
+obj-$(CONFIG_BMA180) += bma180.o
+obj-$(CONFIG_BMA220) += bma220_core.o
+obj-$(CONFIG_BMA220_I2C) += bma220_i2c.o
+obj-$(CONFIG_BMA220_SPI) += bma220_spi.o
+obj-$(CONFIG_BMA400) += bma400_core.o
+obj-$(CONFIG_BMA400_I2C) += bma400_i2c.o
+obj-$(CONFIG_BMA400_SPI) += bma400_spi.o
+obj-$(CONFIG_BMC150_ACCEL) += bmc150-accel-core.o
+obj-$(CONFIG_BMC150_ACCEL_I2C) += bmc150-accel-i2c.o
+obj-$(CONFIG_BMC150_ACCEL_SPI) += bmc150-accel-spi.o
+obj-$(CONFIG_BMI088_ACCEL) += bmi088-accel-core.o
+obj-$(CONFIG_BMI088_ACCEL_I2C) += bmi088-accel-i2c.o
+obj-$(CONFIG_BMI088_ACCEL_SPI) += bmi088-accel-spi.o
+obj-$(CONFIG_DA280) += da280.o
+obj-$(CONFIG_DA311) += da311.o
+obj-$(CONFIG_DMARD06) += dmard06.o
+obj-$(CONFIG_DMARD09) += dmard09.o
+obj-$(CONFIG_DMARD10) += dmard10.o
+obj-$(CONFIG_FXLS8962AF) += fxls8962af-core.o
+obj-$(CONFIG_FXLS8962AF_I2C) += fxls8962af-i2c.o
+obj-$(CONFIG_FXLS8962AF_SPI) += fxls8962af-spi.o
+obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o
+obj-$(CONFIG_IIO_KX022A) += kionix-kx022a.o
+obj-$(CONFIG_IIO_KX022A_I2C) += kionix-kx022a-i2c.o
+obj-$(CONFIG_IIO_KX022A_SPI) += kionix-kx022a-spi.o
+obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o
+obj-$(CONFIG_KXSD9) += kxsd9.o
+obj-$(CONFIG_KXSD9_SPI) += kxsd9-spi.o
+obj-$(CONFIG_KXSD9_I2C) += kxsd9-i2c.o
+obj-$(CONFIG_MC3230) += mc3230.o
+
+obj-$(CONFIG_MMA7455) += mma7455_core.o
+obj-$(CONFIG_MMA7455_I2C) += mma7455_i2c.o
+obj-$(CONFIG_MMA7455_SPI) += mma7455_spi.o
+
+obj-$(CONFIG_MMA7660) += mma7660.o
+
+obj-$(CONFIG_MMA8452) += mma8452.o
+
+obj-$(CONFIG_MMA9551_CORE) += mma9551_core.o
+obj-$(CONFIG_MMA9551) += mma9551.o
+obj-$(CONFIG_MMA9553) += mma9553.o
+
+obj-$(CONFIG_MSA311) += msa311.o
+
+obj-$(CONFIG_MXC4005) += mxc4005.o
+obj-$(CONFIG_MXC6255) += mxc6255.o
+
+obj-$(CONFIG_SCA3000) += sca3000.o
+obj-$(CONFIG_SCA3300) += sca3300.o
+
+obj-$(CONFIG_STK8312) += stk8312.o
+obj-$(CONFIG_STK8BA50) += stk8ba50.o
+
+obj-$(CONFIG_IIO_CROS_EC_ACCEL_LEGACY) += cros_ec_accel_legacy.o
+
+obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_accel_sensor.o
+
+obj-$(CONFIG_IIO_ST_ACCEL_3AXIS) += st_accel.o
+st_accel-y := st_accel_core.o
+st_accel-$(CONFIG_IIO_BUFFER) += st_accel_buffer.o
+
+obj-$(CONFIG_IIO_ST_ACCEL_I2C_3AXIS) += st_accel_i2c.o
+obj-$(CONFIG_IIO_ST_ACCEL_SPI_3AXIS) += st_accel_spi.o
diff --git a/drivers/iio/accel/adis16201.c b/drivers/iio/accel/adis16201.c
new file mode 100644
index 000000000..ba0f97944
--- /dev/null
+++ b/drivers/iio/accel/adis16201.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer
+ *
+ * Copyright 2010 Analog Devices Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/imu/adis.h>
+
+#define ADIS16201_STARTUP_DELAY_MS 220
+#define ADIS16201_FLASH_CNT 0x00
+
+/* Data Output Register Information */
+#define ADIS16201_SUPPLY_OUT_REG 0x02
+#define ADIS16201_XACCL_OUT_REG 0x04
+#define ADIS16201_YACCL_OUT_REG 0x06
+#define ADIS16201_AUX_ADC_REG 0x08
+#define ADIS16201_TEMP_OUT_REG 0x0A
+#define ADIS16201_XINCL_OUT_REG 0x0C
+#define ADIS16201_YINCL_OUT_REG 0x0E
+
+/* Calibration Register Definition */
+#define ADIS16201_XACCL_OFFS_REG 0x10
+#define ADIS16201_YACCL_OFFS_REG 0x12
+#define ADIS16201_XACCL_SCALE_REG 0x14
+#define ADIS16201_YACCL_SCALE_REG 0x16
+#define ADIS16201_XINCL_OFFS_REG 0x18
+#define ADIS16201_YINCL_OFFS_REG 0x1A
+#define ADIS16201_XINCL_SCALE_REG 0x1C
+#define ADIS16201_YINCL_SCALE_REG 0x1E
+
+/* Alarm Register Definition */
+#define ADIS16201_ALM_MAG1_REG 0x20
+#define ADIS16201_ALM_MAG2_REG 0x22
+#define ADIS16201_ALM_SMPL1_REG 0x24
+#define ADIS16201_ALM_SMPL2_REG 0x26
+#define ADIS16201_ALM_CTRL_REG 0x28
+
+#define ADIS16201_AUX_DAC_REG 0x30
+#define ADIS16201_GPIO_CTRL_REG 0x32
+#define ADIS16201_SMPL_PRD_REG 0x36
+/* Operation, filter configuration */
+#define ADIS16201_AVG_CNT_REG 0x38
+#define ADIS16201_SLP_CNT_REG 0x3A
+
+/* Miscellaneous Control Register Definition */
+#define ADIS16201_MSC_CTRL_REG 0x34
+#define ADIS16201_MSC_CTRL_SELF_TEST_EN BIT(8)
+/* Data-ready enable: 1 = enabled, 0 = disabled */
+#define ADIS16201_MSC_CTRL_DATA_RDY_EN BIT(2)
+/* Data-ready polarity: 1 = active high, 0 = active low */
+#define ADIS16201_MSC_CTRL_ACTIVE_DATA_RDY_HIGH BIT(1)
+/* Data-ready line selection: 1 = DIO1, 0 = DIO0 */
+#define ADIS16201_MSC_CTRL_DATA_RDY_DIO1 BIT(0)
+
+/* Diagnostics System Status Register Definition */
+#define ADIS16201_DIAG_STAT_REG 0x3C
+#define ADIS16201_DIAG_STAT_ALARM2 BIT(9)
+#define ADIS16201_DIAG_STAT_ALARM1 BIT(8)
+#define ADIS16201_DIAG_STAT_SPI_FAIL_BIT 3
+#define ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT 2
+/* Power supply above 3.625 V */
+#define ADIS16201_DIAG_STAT_POWER_HIGH_BIT 1
+/* Power supply below 2.975 V */
+#define ADIS16201_DIAG_STAT_POWER_LOW_BIT 0
+
+/* System Command Register Definition */
+#define ADIS16201_GLOB_CMD_REG 0x3E
+#define ADIS16201_GLOB_CMD_SW_RESET BIT(7)
+#define ADIS16201_GLOB_CMD_FACTORY_RESET BIT(1)
+
+#define ADIS16201_ERROR_ACTIVE BIT(14)
+
+enum adis16201_scan {
+ ADIS16201_SCAN_ACC_X,
+ ADIS16201_SCAN_ACC_Y,
+ ADIS16201_SCAN_INCLI_X,
+ ADIS16201_SCAN_INCLI_Y,
+ ADIS16201_SCAN_SUPPLY,
+ ADIS16201_SCAN_AUX_ADC,
+ ADIS16201_SCAN_TEMP,
+};
+
+static const u8 adis16201_addresses[] = {
+ [ADIS16201_SCAN_ACC_X] = ADIS16201_XACCL_OFFS_REG,
+ [ADIS16201_SCAN_ACC_Y] = ADIS16201_YACCL_OFFS_REG,
+ [ADIS16201_SCAN_INCLI_X] = ADIS16201_XINCL_OFFS_REG,
+ [ADIS16201_SCAN_INCLI_Y] = ADIS16201_YINCL_OFFS_REG,
+};
+
+static int adis16201_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2,
+ long mask)
+{
+ struct adis *st = iio_priv(indio_dev);
+ int ret;
+ int bits;
+ u8 addr;
+ s16 val16;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return adis_single_conversion(indio_dev, chan,
+ ADIS16201_ERROR_ACTIVE, val);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ if (chan->channel == 0) {
+ /* Voltage base units are mV hence 1.22 mV */
+ *val = 1;
+ *val2 = 220000;
+ } else {
+ /* Voltage base units are mV hence 0.61 mV */
+ *val = 0;
+ *val2 = 610000;
+ }
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ *val = -470;
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ /*
+ * IIO base unit for sensitivity of accelerometer
+ * is milli g.
+ * 1 LSB represents 0.244 mg.
+ */
+ *val = 0;
+ *val2 = IIO_G_TO_M_S_2(462400);
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_INCLI:
+ *val = 0;
+ *val2 = 100000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case IIO_CHAN_INFO_OFFSET:
+ /*
+ * The raw ADC value is 1278 when the temperature
+ * is 25 degrees and the scale factor per milli
+ * degree Celsius is -470.
+ */
+ *val = 25000 / -470 - 1278;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ bits = 12;
+ break;
+ case IIO_INCLI:
+ bits = 9;
+ break;
+ default:
+ return -EINVAL;
+ }
+ addr = adis16201_addresses[chan->scan_index];
+ ret = adis_read_reg_16(st, addr, &val16);
+ if (ret)
+ return ret;
+
+ *val = sign_extend32(val16, bits - 1);
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int adis16201_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct adis *st = iio_priv(indio_dev);
+ int m;
+
+ if (mask != IIO_CHAN_INFO_CALIBBIAS)
+ return -EINVAL;
+
+ switch (chan->type) {
+ case IIO_ACCEL:
+ m = GENMASK(11, 0);
+ break;
+ case IIO_INCLI:
+ m = GENMASK(8, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return adis_write_reg_16(st, adis16201_addresses[chan->scan_index],
+ val & m);
+}
+
+static const struct iio_chan_spec adis16201_channels[] = {
+ ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT_REG, ADIS16201_SCAN_SUPPLY, 0,
+ 12),
+ ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT_REG, ADIS16201_SCAN_TEMP, 0, 12),
+ ADIS_ACCEL_CHAN(X, ADIS16201_XACCL_OUT_REG, ADIS16201_SCAN_ACC_X,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14),
+ ADIS_ACCEL_CHAN(Y, ADIS16201_YACCL_OUT_REG, ADIS16201_SCAN_ACC_Y,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14),
+ ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC_REG, ADIS16201_SCAN_AUX_ADC, 0, 12),
+ ADIS_INCLI_CHAN(X, ADIS16201_XINCL_OUT_REG, ADIS16201_SCAN_INCLI_X,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 12),
+ ADIS_INCLI_CHAN(Y, ADIS16201_YINCL_OUT_REG, ADIS16201_SCAN_INCLI_Y,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 12),
+ IIO_CHAN_SOFT_TIMESTAMP(7)
+};
+
+static const struct iio_info adis16201_info = {
+ .read_raw = adis16201_read_raw,
+ .write_raw = adis16201_write_raw,
+ .update_scan_mode = adis_update_scan_mode,
+};
+
+static const char * const adis16201_status_error_msgs[] = {
+ [ADIS16201_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure",
+ [ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT] = "Flash update failed",
+ [ADIS16201_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V",
+ [ADIS16201_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 2.975V",
+};
+
+static const struct adis_timeout adis16201_timeouts = {
+ .reset_ms = ADIS16201_STARTUP_DELAY_MS,
+ .sw_reset_ms = ADIS16201_STARTUP_DELAY_MS,
+ .self_test_ms = ADIS16201_STARTUP_DELAY_MS,
+};
+
+static const struct adis_data adis16201_data = {
+ .read_delay = 20,
+ .msc_ctrl_reg = ADIS16201_MSC_CTRL_REG,
+ .glob_cmd_reg = ADIS16201_GLOB_CMD_REG,
+ .diag_stat_reg = ADIS16201_DIAG_STAT_REG,
+
+ .self_test_mask = ADIS16201_MSC_CTRL_SELF_TEST_EN,
+ .self_test_reg = ADIS16201_MSC_CTRL_REG,
+ .self_test_no_autoclear = true,
+ .timeouts = &adis16201_timeouts,
+
+ .status_error_msgs = adis16201_status_error_msgs,
+ .status_error_mask = BIT(ADIS16201_DIAG_STAT_SPI_FAIL_BIT) |
+ BIT(ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT) |
+ BIT(ADIS16201_DIAG_STAT_POWER_HIGH_BIT) |
+ BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT),
+};
+
+static int adis16201_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct adis *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ indio_dev->name = spi->dev.driver->name;
+ indio_dev->info = &adis16201_info;
+
+ indio_dev->channels = adis16201_channels;
+ indio_dev->num_channels = ARRAY_SIZE(adis16201_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = adis_init(st, indio_dev, spi, &adis16201_data);
+ if (ret)
+ return ret;
+
+ ret = devm_adis_setup_buffer_and_trigger(st, indio_dev, NULL);
+ if (ret)
+ return ret;
+
+ ret = __adis_initial_startup(st);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static struct spi_driver adis16201_driver = {
+ .driver = {
+ .name = "adis16201",
+ },
+ .probe = adis16201_probe,
+};
+module_spi_driver(adis16201_driver);
+
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_DESCRIPTION("Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("spi:adis16201");
+MODULE_IMPORT_NS("IIO_ADISLIB");
diff --git a/drivers/iio/accel/adis16209.c b/drivers/iio/accel/adis16209.c
new file mode 100644
index 000000000..04e169f22
--- /dev/null
+++ b/drivers/iio/accel/adis16209.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ADIS16209 Dual-Axis Digital Inclinometer and Accelerometer
+ *
+ * Copyright 2010 Analog Devices Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/imu/adis.h>
+
+#define ADIS16209_STARTUP_DELAY_MS 220
+#define ADIS16209_FLASH_CNT_REG 0x00
+
+/* Data Output Register Definitions */
+#define ADIS16209_SUPPLY_OUT_REG 0x02
+#define ADIS16209_XACCL_OUT_REG 0x04
+#define ADIS16209_YACCL_OUT_REG 0x06
+/* Output, auxiliary ADC input */
+#define ADIS16209_AUX_ADC_REG 0x08
+/* Output, temperature */
+#define ADIS16209_TEMP_OUT_REG 0x0A
+/* Output, +/- 90 degrees X-axis inclination */
+#define ADIS16209_XINCL_OUT_REG 0x0C
+#define ADIS16209_YINCL_OUT_REG 0x0E
+/* Output, +/-180 vertical rotational position */
+#define ADIS16209_ROT_OUT_REG 0x10
+
+/*
+ * Calibration Register Definitions.
+ * Acceleration, inclination or rotation offset null.
+ */
+#define ADIS16209_XACCL_NULL_REG 0x12
+#define ADIS16209_YACCL_NULL_REG 0x14
+#define ADIS16209_XINCL_NULL_REG 0x16
+#define ADIS16209_YINCL_NULL_REG 0x18
+#define ADIS16209_ROT_NULL_REG 0x1A
+
+/* Alarm Register Definitions */
+#define ADIS16209_ALM_MAG1_REG 0x20
+#define ADIS16209_ALM_MAG2_REG 0x22
+#define ADIS16209_ALM_SMPL1_REG 0x24
+#define ADIS16209_ALM_SMPL2_REG 0x26
+#define ADIS16209_ALM_CTRL_REG 0x28
+
+#define ADIS16209_AUX_DAC_REG 0x30
+#define ADIS16209_GPIO_CTRL_REG 0x32
+#define ADIS16209_SMPL_PRD_REG 0x36
+#define ADIS16209_AVG_CNT_REG 0x38
+#define ADIS16209_SLP_CNT_REG 0x3A
+
+#define ADIS16209_MSC_CTRL_REG 0x34
+#define ADIS16209_MSC_CTRL_PWRUP_SELF_TEST BIT(10)
+#define ADIS16209_MSC_CTRL_SELF_TEST_EN BIT(8)
+#define ADIS16209_MSC_CTRL_DATA_RDY_EN BIT(2)
+/* Data-ready polarity: 1 = active high, 0 = active low */
+#define ADIS16209_MSC_CTRL_ACTIVE_HIGH BIT(1)
+#define ADIS16209_MSC_CTRL_DATA_RDY_DIO2 BIT(0)
+
+#define ADIS16209_STAT_REG 0x3C
+#define ADIS16209_STAT_ALARM2 BIT(9)
+#define ADIS16209_STAT_ALARM1 BIT(8)
+#define ADIS16209_STAT_SELFTEST_FAIL_BIT 5
+#define ADIS16209_STAT_SPI_FAIL_BIT 3
+#define ADIS16209_STAT_FLASH_UPT_FAIL_BIT 2
+/* Power supply above 3.625 V */
+#define ADIS16209_STAT_POWER_HIGH_BIT 1
+/* Power supply below 2.975 V */
+#define ADIS16209_STAT_POWER_LOW_BIT 0
+
+#define ADIS16209_CMD_REG 0x3E
+#define ADIS16209_CMD_SW_RESET BIT(7)
+#define ADIS16209_CMD_CLEAR_STAT BIT(4)
+#define ADIS16209_CMD_FACTORY_CAL BIT(1)
+
+#define ADIS16209_ERROR_ACTIVE BIT(14)
+
+enum adis16209_scan {
+ ADIS16209_SCAN_SUPPLY,
+ ADIS16209_SCAN_ACC_X,
+ ADIS16209_SCAN_ACC_Y,
+ ADIS16209_SCAN_AUX_ADC,
+ ADIS16209_SCAN_TEMP,
+ ADIS16209_SCAN_INCLI_X,
+ ADIS16209_SCAN_INCLI_Y,
+ ADIS16209_SCAN_ROT,
+};
+
+static const u8 adis16209_addresses[8][1] = {
+ [ADIS16209_SCAN_SUPPLY] = { },
+ [ADIS16209_SCAN_AUX_ADC] = { },
+ [ADIS16209_SCAN_ACC_X] = { ADIS16209_XACCL_NULL_REG },
+ [ADIS16209_SCAN_ACC_Y] = { ADIS16209_YACCL_NULL_REG },
+ [ADIS16209_SCAN_INCLI_X] = { ADIS16209_XINCL_NULL_REG },
+ [ADIS16209_SCAN_INCLI_Y] = { ADIS16209_YINCL_NULL_REG },
+ [ADIS16209_SCAN_ROT] = { },
+ [ADIS16209_SCAN_TEMP] = { },
+};
+
+static int adis16209_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct adis *st = iio_priv(indio_dev);
+ int m;
+
+ if (mask != IIO_CHAN_INFO_CALIBBIAS)
+ return -EINVAL;
+
+ switch (chan->type) {
+ case IIO_ACCEL:
+ case IIO_INCLI:
+ m = GENMASK(13, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return adis_write_reg_16(st, adis16209_addresses[chan->scan_index][0],
+ val & m);
+}
+
+static int adis16209_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2,
+ long mask)
+{
+ struct adis *st = iio_priv(indio_dev);
+ int ret;
+ int bits;
+ u8 addr;
+ s16 val16;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return adis_single_conversion(indio_dev, chan,
+ ADIS16209_ERROR_ACTIVE, val);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ *val = 0;
+ switch (chan->channel) {
+ case 0:
+ *val2 = 305180; /* 0.30518 mV */
+ break;
+ case 1:
+ *val2 = 610500; /* 0.6105 mV */
+ break;
+ default:
+ return -EINVAL;
+ }
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ *val = -470;
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ /*
+ * IIO base unit for sensitivity of accelerometer
+ * is milli g.
+ * 1 LSB represents 0.244 mg.
+ */
+ *val = 0;
+ *val2 = IIO_G_TO_M_S_2(244140);
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_INCLI:
+ case IIO_ROT:
+ /*
+ * IIO base units for rotation are degrees.
+ * 1 LSB represents 0.025 milli degrees.
+ */
+ *val = 0;
+ *val2 = 25000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case IIO_CHAN_INFO_OFFSET:
+ /*
+ * The raw ADC value is 0x4FE when the temperature
+ * is 45 degrees and the scale factor per milli
+ * degree Celsius is -470.
+ */
+ *val = 25000 / -470 - 0x4FE;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ bits = 14;
+ break;
+ default:
+ return -EINVAL;
+ }
+ addr = adis16209_addresses[chan->scan_index][0];
+ ret = adis_read_reg_16(st, addr, &val16);
+ if (ret)
+ return ret;
+
+ *val = sign_extend32(val16, bits - 1);
+ return IIO_VAL_INT;
+ }
+ return -EINVAL;
+}
+
+static const struct iio_chan_spec adis16209_channels[] = {
+ ADIS_SUPPLY_CHAN(ADIS16209_SUPPLY_OUT_REG, ADIS16209_SCAN_SUPPLY,
+ 0, 14),
+ ADIS_TEMP_CHAN(ADIS16209_TEMP_OUT_REG, ADIS16209_SCAN_TEMP, 0, 12),
+ ADIS_ACCEL_CHAN(X, ADIS16209_XACCL_OUT_REG, ADIS16209_SCAN_ACC_X,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14),
+ ADIS_ACCEL_CHAN(Y, ADIS16209_YACCL_OUT_REG, ADIS16209_SCAN_ACC_Y,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14),
+ ADIS_AUX_ADC_CHAN(ADIS16209_AUX_ADC_REG, ADIS16209_SCAN_AUX_ADC, 0, 12),
+ ADIS_INCLI_CHAN(X, ADIS16209_XINCL_OUT_REG, ADIS16209_SCAN_INCLI_X,
+ 0, 0, 14),
+ ADIS_INCLI_CHAN(Y, ADIS16209_YINCL_OUT_REG, ADIS16209_SCAN_INCLI_Y,
+ 0, 0, 14),
+ ADIS_ROT_CHAN(X, ADIS16209_ROT_OUT_REG, ADIS16209_SCAN_ROT, 0, 0, 14),
+ IIO_CHAN_SOFT_TIMESTAMP(8)
+};
+
+static const struct iio_info adis16209_info = {
+ .read_raw = adis16209_read_raw,
+ .write_raw = adis16209_write_raw,
+ .update_scan_mode = adis_update_scan_mode,
+};
+
+static const char * const adis16209_status_error_msgs[] = {
+ [ADIS16209_STAT_SELFTEST_FAIL_BIT] = "Self test failure",
+ [ADIS16209_STAT_SPI_FAIL_BIT] = "SPI failure",
+ [ADIS16209_STAT_FLASH_UPT_FAIL_BIT] = "Flash update failed",
+ [ADIS16209_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V",
+ [ADIS16209_STAT_POWER_LOW_BIT] = "Power supply below 2.975V",
+};
+
+static const struct adis_timeout adis16209_timeouts = {
+ .reset_ms = ADIS16209_STARTUP_DELAY_MS,
+ .self_test_ms = ADIS16209_STARTUP_DELAY_MS,
+ .sw_reset_ms = ADIS16209_STARTUP_DELAY_MS,
+};
+
+static const struct adis_data adis16209_data = {
+ .read_delay = 30,
+ .msc_ctrl_reg = ADIS16209_MSC_CTRL_REG,
+ .glob_cmd_reg = ADIS16209_CMD_REG,
+ .diag_stat_reg = ADIS16209_STAT_REG,
+
+ .self_test_mask = ADIS16209_MSC_CTRL_SELF_TEST_EN,
+ .self_test_reg = ADIS16209_MSC_CTRL_REG,
+ .self_test_no_autoclear = true,
+ .timeouts = &adis16209_timeouts,
+
+ .status_error_msgs = adis16209_status_error_msgs,
+ .status_error_mask = BIT(ADIS16209_STAT_SELFTEST_FAIL_BIT) |
+ BIT(ADIS16209_STAT_SPI_FAIL_BIT) |
+ BIT(ADIS16209_STAT_FLASH_UPT_FAIL_BIT) |
+ BIT(ADIS16209_STAT_POWER_HIGH_BIT) |
+ BIT(ADIS16209_STAT_POWER_LOW_BIT),
+};
+
+static int adis16209_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct adis *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ indio_dev->name = spi->dev.driver->name;
+ indio_dev->info = &adis16209_info;
+ indio_dev->channels = adis16209_channels;
+ indio_dev->num_channels = ARRAY_SIZE(adis16209_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = adis_init(st, indio_dev, spi, &adis16209_data);
+ if (ret)
+ return ret;
+
+ ret = devm_adis_setup_buffer_and_trigger(st, indio_dev, NULL);
+ if (ret)
+ return ret;
+
+ ret = __adis_initial_startup(st);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static struct spi_driver adis16209_driver = {
+ .driver = {
+ .name = "adis16209",
+ },
+ .probe = adis16209_probe,
+};
+module_spi_driver(adis16209_driver);
+
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_DESCRIPTION("Analog Devices ADIS16209 Dual-Axis Digital Inclinometer and Accelerometer");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("spi:adis16209");
+MODULE_IMPORT_NS("IIO_ADISLIB");
diff --git a/drivers/iio/accel/adxl313.h b/drivers/iio/accel/adxl313.h
new file mode 100644
index 000000000..75ef54b60
--- /dev/null
+++ b/drivers/iio/accel/adxl313.h
@@ -0,0 +1,114 @@
+/* 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_
+
+#include <linux/iio/iio.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_THRESH_INACT 0x25
+#define ADXL313_REG_TIME_INACT 0x26
+#define ADXL313_REG_ACT_INACT_CTL 0x27
+#define ADXL313_REG_BW_RATE 0x2C
+#define ADXL313_REG_POWER_CTL 0x2D
+#define ADXL313_REG_INT_ENABLE 0x2E
+#define ADXL313_REG_INT_MAP 0x2F
+#define ADXL313_REG_INT_SOURCE 0x30
+#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_DEVID0_ADXL312_314 0xE5
+#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 BIT(3)
+#define ADXL313_POWER_CTL_INACT_MSK GENMASK(5, 4)
+#define ADXL313_POWER_CTL_LINK BIT(5)
+#define ADXL313_POWER_CTL_AUTO_SLEEP BIT(4)
+
+#define ADXL313_RANGE_MSK GENMASK(1, 0)
+#define ADXL313_RANGE_MAX 3
+
+#define ADXL313_FULL_RES BIT(3)
+#define ADXL313_SPI_3WIRE BIT(6)
+#define ADXL313_I2C_DISABLE BIT(6)
+
+#define ADXL313_INT_OVERRUN BIT(0)
+#define ADXL313_INT_WATERMARK BIT(1)
+#define ADXL313_INT_INACTIVITY BIT(3)
+#define ADXL313_INT_ACTIVITY BIT(4)
+#define ADXL313_INT_DREADY BIT(7)
+
+/* FIFO entries: how many values are stored in the FIFO */
+#define ADXL313_REG_FIFO_STATUS_ENTRIES_MSK GENMASK(5, 0)
+/* FIFO samples: number of samples needed for watermark (FIFO mode) */
+#define ADXL313_REG_FIFO_CTL_SAMPLES_MSK GENMASK(4, 0)
+#define ADXL313_REG_FIFO_CTL_MODE_MSK GENMASK(7, 6)
+
+#define ADXL313_FIFO_BYPASS 0
+#define ADXL313_FIFO_STREAM 2
+
+#define ADXL313_FIFO_SIZE 32
+
+#define ADXL313_NUM_AXIS 3
+
+extern const struct regmap_access_table adxl312_readable_regs_table;
+extern const struct regmap_access_table adxl313_readable_regs_table;
+extern const struct regmap_access_table adxl314_readable_regs_table;
+
+extern const struct regmap_access_table adxl312_writable_regs_table;
+extern const struct regmap_access_table adxl313_writable_regs_table;
+extern const struct regmap_access_table adxl314_writable_regs_table;
+
+bool adxl313_is_volatile_reg(struct device *dev, unsigned int reg);
+
+enum adxl313_device_type {
+ ADXL312,
+ ADXL313,
+ ADXL314,
+};
+
+struct adxl313_data {
+ struct regmap *regmap;
+ const struct adxl313_chip_info *chip_info;
+ struct mutex lock; /* lock to protect transf_buf */
+ u8 watermark;
+ __le16 transf_buf __aligned(IIO_DMA_MINALIGN);
+ __le16 fifo_buf[ADXL313_NUM_AXIS * ADXL313_FIFO_SIZE + 1];
+};
+
+struct adxl313_chip_info {
+ const char *name;
+ enum adxl313_device_type type;
+ int scale_factor;
+ bool variable_range;
+ bool soft_reset;
+ int (*check_id)(struct device *dev, struct adxl313_data *data);
+};
+
+extern const struct adxl313_chip_info adxl31x_chip_info[];
+
+int adxl313_core_probe(struct device *dev,
+ struct regmap *regmap,
+ const struct adxl313_chip_info *chip_info,
+ 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 000000000..6dc918c4a
--- /dev/null
+++ b/drivers/iio/accel/adxl313_core.c
@@ -0,0 +1,1328 @@
+// 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/cleanup.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/kfifo_buf.h>
+
+#include "adxl313.h"
+
+#define ADXL313_INT_NONE U8_MAX
+#define ADXL313_INT1 1
+#define ADXL313_INT2 2
+
+#define ADXL313_REG_XYZ_BASE ADXL313_REG_DATA_AXIS(0)
+
+#define ADXL313_ACT_XYZ_EN GENMASK(6, 4)
+#define ADXL313_INACT_XYZ_EN GENMASK(2, 0)
+
+#define ADXL313_REG_ACT_ACDC_MSK BIT(7)
+#define ADXL313_REG_INACT_ACDC_MSK BIT(3)
+#define ADXL313_COUPLING_DC 0
+#define ADXL313_COUPLING_AC 1
+
+/* activity/inactivity */
+enum adxl313_activity_type {
+ ADXL313_ACTIVITY,
+ ADXL313_INACTIVITY,
+ ADXL313_ACTIVITY_AC,
+ ADXL313_INACTIVITY_AC,
+};
+
+static const unsigned int adxl313_act_int_reg[] = {
+ [ADXL313_ACTIVITY] = ADXL313_INT_ACTIVITY,
+ [ADXL313_INACTIVITY] = ADXL313_INT_INACTIVITY,
+ [ADXL313_ACTIVITY_AC] = ADXL313_INT_ACTIVITY,
+ [ADXL313_INACTIVITY_AC] = ADXL313_INT_INACTIVITY,
+};
+
+static const unsigned int adxl313_act_thresh_reg[] = {
+ [ADXL313_ACTIVITY] = ADXL313_REG_THRESH_ACT,
+ [ADXL313_INACTIVITY] = ADXL313_REG_THRESH_INACT,
+ [ADXL313_ACTIVITY_AC] = ADXL313_REG_THRESH_ACT,
+ [ADXL313_INACTIVITY_AC] = ADXL313_REG_THRESH_INACT,
+};
+
+static const unsigned int adxl313_act_acdc_msk[] = {
+ [ADXL313_ACTIVITY] = ADXL313_REG_ACT_ACDC_MSK,
+ [ADXL313_INACTIVITY] = ADXL313_REG_INACT_ACDC_MSK,
+ [ADXL313_ACTIVITY_AC] = ADXL313_REG_ACT_ACDC_MSK,
+ [ADXL313_INACTIVITY_AC] = ADXL313_REG_INACT_ACDC_MSK,
+};
+
+static const struct regmap_range adxl312_readable_reg_range[] = {
+ regmap_reg_range(ADXL313_REG_DEVID0, ADXL313_REG_DEVID0),
+ 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),
+};
+
+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 adxl312_readable_regs_table = {
+ .yes_ranges = adxl312_readable_reg_range,
+ .n_yes_ranges = ARRAY_SIZE(adxl312_readable_reg_range),
+};
+EXPORT_SYMBOL_NS_GPL(adxl312_readable_regs_table, "IIO_ADXL313");
+
+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_NS_GPL(adxl313_readable_regs_table, "IIO_ADXL313");
+
+const struct regmap_access_table adxl314_readable_regs_table = {
+ .yes_ranges = adxl312_readable_reg_range,
+ .n_yes_ranges = ARRAY_SIZE(adxl312_readable_reg_range),
+};
+EXPORT_SYMBOL_NS_GPL(adxl314_readable_regs_table, "IIO_ADXL313");
+
+bool adxl313_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADXL313_REG_DATA_AXIS(0):
+ case ADXL313_REG_DATA_AXIS(1):
+ case ADXL313_REG_DATA_AXIS(2):
+ case ADXL313_REG_DATA_AXIS(3):
+ case ADXL313_REG_DATA_AXIS(4):
+ case ADXL313_REG_DATA_AXIS(5):
+ case ADXL313_REG_FIFO_STATUS:
+ case ADXL313_REG_INT_SOURCE:
+ return true;
+ default:
+ return false;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(adxl313_is_volatile_reg, "IIO_ADXL313");
+
+static int adxl313_set_measure_en(struct adxl313_data *data, bool en)
+{
+ return regmap_assign_bits(data->regmap, ADXL313_REG_POWER_CTL,
+ ADXL313_POWER_CTL_MSK, en);
+}
+
+static int adxl312_check_id(struct device *dev,
+ struct adxl313_data *data)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, &regval);
+ if (ret)
+ return ret;
+
+ if (regval != ADXL313_DEVID0_ADXL312_314)
+ dev_warn(dev, "Invalid manufacturer ID: %#02x\n", regval);
+
+ return 0;
+}
+
+static int adxl313_check_id(struct device *dev,
+ struct adxl313_data *data)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, &regval);
+ if (ret)
+ return ret;
+
+ if (regval != ADXL313_DEVID0)
+ dev_warn(dev, "Invalid manufacturer ID: 0x%02x\n", regval);
+
+ /* Check DEVID1 and PARTID */
+ if (regval == ADXL313_DEVID0) {
+ ret = regmap_read(data->regmap, ADXL313_REG_DEVID1, &regval);
+ if (ret)
+ return ret;
+
+ if (regval != ADXL313_DEVID1)
+ dev_warn(dev, "Invalid mems ID: 0x%02x\n", regval);
+
+ ret = regmap_read(data->regmap, ADXL313_REG_PARTID, &regval);
+ if (ret)
+ return ret;
+
+ if (regval != ADXL313_PARTID)
+ dev_warn(dev, "Invalid device ID: 0x%02x\n", regval);
+ }
+
+ return 0;
+}
+
+const struct adxl313_chip_info adxl31x_chip_info[] = {
+ [ADXL312] = {
+ .name = "adxl312",
+ .type = ADXL312,
+ .scale_factor = 28425072,
+ .variable_range = true,
+ .soft_reset = false,
+ .check_id = &adxl312_check_id,
+ },
+ [ADXL313] = {
+ .name = "adxl313",
+ .type = ADXL313,
+ .scale_factor = 9576806,
+ .variable_range = true,
+ .soft_reset = true,
+ .check_id = &adxl313_check_id,
+ },
+ [ADXL314] = {
+ .name = "adxl314",
+ .type = ADXL314,
+ .scale_factor = 478858719,
+ .variable_range = false,
+ .soft_reset = false,
+ .check_id = &adxl312_check_id,
+ },
+};
+EXPORT_SYMBOL_NS_GPL(adxl31x_chip_info, "IIO_ADXL313");
+
+static const struct regmap_range adxl312_writable_reg_range[] = {
+ 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),
+};
+
+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 adxl312_writable_regs_table = {
+ .yes_ranges = adxl312_writable_reg_range,
+ .n_yes_ranges = ARRAY_SIZE(adxl312_writable_reg_range),
+};
+EXPORT_SYMBOL_NS_GPL(adxl312_writable_regs_table, "IIO_ADXL313");
+
+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_NS_GPL(adxl313_writable_regs_table, "IIO_ADXL313");
+
+const struct regmap_access_table adxl314_writable_regs_table = {
+ .yes_ranges = adxl312_writable_reg_range,
+ .n_yes_ranges = ARRAY_SIZE(adxl312_writable_reg_range),
+};
+EXPORT_SYMBOL_NS_GPL(adxl314_writable_regs_table, "IIO_ADXL313");
+
+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, reg, axis) { \
+ .type = IIO_ACCEL, \
+ .scan_index = (index), \
+ .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), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 13, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+}
+
+static const struct iio_event_spec adxl313_activity_events[] = {
+ {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ /* activity, AC bit set */
+ .type = IIO_EV_TYPE_MAG_ADAPTIVE,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
+ },
+};
+
+static const struct iio_event_spec adxl313_inactivity_events[] = {
+ {
+ /* inactivity */
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+ {
+ /* inactivity, AC bit set */
+ .type = IIO_EV_TYPE_MAG_ADAPTIVE,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+};
+
+enum adxl313_chans {
+ chan_x, chan_y, chan_z,
+};
+
+static const struct iio_chan_spec adxl313_channels[] = {
+ ADXL313_ACCEL_CHANNEL(0, chan_x, X),
+ ADXL313_ACCEL_CHANNEL(1, chan_y, Y),
+ ADXL313_ACCEL_CHANNEL(2, chan_z, Z),
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X_OR_Y_OR_Z,
+ .scan_index = -1, /* Fake channel for axis OR'ing */
+ .event_spec = adxl313_activity_events,
+ .num_event_specs = ARRAY_SIZE(adxl313_activity_events),
+ },
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X_AND_Y_AND_Z,
+ .scan_index = -1, /* Fake channel for axis AND'ing */
+ .event_spec = adxl313_inactivity_events,
+ .num_event_specs = ARRAY_SIZE(adxl313_inactivity_events),
+ },
+};
+
+static const unsigned long adxl313_scan_masks[] = {
+ BIT(chan_x) | BIT(chan_y) | BIT(chan_z),
+ 0
+};
+
+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;
+
+ guard(mutex)(&data->lock);
+
+ ret = regmap_bulk_read(data->regmap,
+ ADXL313_REG_DATA_AXIS(chan->address),
+ &data->transf_buf, sizeof(data->transf_buf));
+ if (ret)
+ return ret;
+
+ return le16_to_cpu(data->transf_buf);
+}
+
+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_set_inact_time_s(struct adxl313_data *data,
+ unsigned int val_s)
+{
+ unsigned int max_boundary = U8_MAX; /* by register size */
+ unsigned int val = min(val_s, max_boundary);
+
+ return regmap_write(data->regmap, ADXL313_REG_TIME_INACT, val);
+}
+
+/**
+ * adxl313_is_act_inact_ac() - Check if AC coupling is enabled.
+ * @data: The device data.
+ * @type: The activity or inactivity type.
+ *
+ * Provide a type of activity or inactivity, combined with either AC coupling
+ * set, or default to DC coupling. This function verifies if the combination is
+ * currently enabled or not.
+ *
+ * Return: if the provided activity type has AC coupling enabled or a negative
+ * error value.
+ */
+static int adxl313_is_act_inact_ac(struct adxl313_data *data,
+ enum adxl313_activity_type type)
+{
+ unsigned int regval;
+ bool coupling;
+ int ret;
+
+ ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval);
+ if (ret)
+ return ret;
+
+ coupling = adxl313_act_acdc_msk[type] & regval;
+
+ switch (type) {
+ case ADXL313_ACTIVITY:
+ case ADXL313_INACTIVITY:
+ return coupling == ADXL313_COUPLING_DC;
+ case ADXL313_ACTIVITY_AC:
+ case ADXL313_INACTIVITY_AC:
+ return coupling == ADXL313_COUPLING_AC;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl313_set_act_inact_ac(struct adxl313_data *data,
+ enum adxl313_activity_type type,
+ bool cmd_en)
+{
+ unsigned int act_inact_ac;
+
+ switch (type) {
+ case ADXL313_ACTIVITY_AC:
+ case ADXL313_INACTIVITY_AC:
+ act_inact_ac = ADXL313_COUPLING_AC && cmd_en;
+ break;
+ case ADXL313_ACTIVITY:
+ case ADXL313_INACTIVITY:
+ act_inact_ac = ADXL313_COUPLING_DC && cmd_en;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL,
+ adxl313_act_acdc_msk[type], act_inact_ac);
+}
+
+static int adxl313_is_act_inact_en(struct adxl313_data *data,
+ enum adxl313_activity_type type)
+{
+ unsigned int axis_ctrl;
+ unsigned int regval;
+ bool int_en;
+ int ret;
+
+ ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ /* Check if axis for activity are enabled */
+ switch (type) {
+ case ADXL313_ACTIVITY:
+ case ADXL313_ACTIVITY_AC:
+ if (!FIELD_GET(ADXL313_ACT_XYZ_EN, axis_ctrl))
+ return false;
+ break;
+ case ADXL313_INACTIVITY:
+ case ADXL313_INACTIVITY_AC:
+ if (!FIELD_GET(ADXL313_INACT_XYZ_EN, axis_ctrl))
+ return false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Check if specific interrupt is enabled */
+ ret = regmap_read(data->regmap, ADXL313_REG_INT_ENABLE, &regval);
+ if (ret)
+ return ret;
+
+ int_en = adxl313_act_int_reg[type] & regval;
+ if (!int_en)
+ return false;
+
+ /* Check if configured coupling matches provided type */
+ return adxl313_is_act_inact_ac(data, type);
+}
+
+static int adxl313_set_act_inact_linkbit(struct adxl313_data *data, bool en)
+{
+ int act_ac_en, inact_ac_en;
+ int act_en, inact_en;
+
+ act_en = adxl313_is_act_inact_en(data, ADXL313_ACTIVITY);
+ if (act_en < 0)
+ return act_en;
+
+ act_ac_en = adxl313_is_act_inact_en(data, ADXL313_ACTIVITY_AC);
+ if (act_ac_en < 0)
+ return act_ac_en;
+
+ inact_en = adxl313_is_act_inact_en(data, ADXL313_INACTIVITY);
+ if (inact_en < 0)
+ return inact_en;
+
+ inact_ac_en = adxl313_is_act_inact_en(data, ADXL313_INACTIVITY_AC);
+ if (inact_ac_en < 0)
+ return inact_ac_en;
+
+ act_en = act_en || act_ac_en;
+
+ inact_en = inact_en || inact_ac_en;
+
+ return regmap_assign_bits(data->regmap, ADXL313_REG_POWER_CTL,
+ ADXL313_POWER_CTL_AUTO_SLEEP | ADXL313_POWER_CTL_LINK,
+ en && act_en && inact_en);
+}
+
+static int adxl313_set_act_inact_en(struct adxl313_data *data,
+ enum adxl313_activity_type type,
+ bool cmd_en)
+{
+ unsigned int axis_ctrl;
+ unsigned int threshold;
+ unsigned int inact_time_s;
+ int ret;
+
+ if (cmd_en) {
+ /* When turning on, check if threshold is valid */
+ ret = regmap_read(data->regmap, adxl313_act_thresh_reg[type],
+ &threshold);
+ if (ret)
+ return ret;
+
+ if (!threshold) /* Just ignore the command if threshold is 0 */
+ return 0;
+
+ /* When turning on inactivity, check if inact time is valid */
+ if (type == ADXL313_INACTIVITY || type == ADXL313_INACTIVITY_AC) {
+ ret = regmap_read(data->regmap,
+ ADXL313_REG_TIME_INACT,
+ &inact_time_s);
+ if (ret)
+ return ret;
+
+ if (!inact_time_s)
+ return 0;
+ }
+ } else {
+ /*
+ * When turning off an activity, ensure that the correct
+ * coupling event is specified. This step helps prevent misuse -
+ * for example, if an AC-coupled activity is active and the
+ * current call attempts to turn off a DC-coupled activity, this
+ * inconsistency should be detected here.
+ */
+ if (adxl313_is_act_inact_ac(data, type) <= 0)
+ return 0;
+ }
+
+ /* Start modifying configuration registers */
+ ret = adxl313_set_measure_en(data, false);
+ if (ret)
+ return ret;
+
+ /* Enable axis according to the command */
+ switch (type) {
+ case ADXL313_ACTIVITY:
+ case ADXL313_ACTIVITY_AC:
+ axis_ctrl = ADXL313_ACT_XYZ_EN;
+ break;
+ case ADXL313_INACTIVITY:
+ case ADXL313_INACTIVITY_AC:
+ axis_ctrl = ADXL313_INACT_XYZ_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ret = regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL,
+ axis_ctrl, cmd_en);
+ if (ret)
+ return ret;
+
+ /* Update AC/DC-coupling according to the command */
+ ret = adxl313_set_act_inact_ac(data, type, cmd_en);
+ if (ret)
+ return ret;
+
+ /* Enable the interrupt line, according to the command */
+ ret = regmap_assign_bits(data->regmap, ADXL313_REG_INT_ENABLE,
+ adxl313_act_int_reg[type], cmd_en);
+ if (ret)
+ return ret;
+
+ /* Set link-bit and auto-sleep only when ACT and INACT are enabled */
+ ret = adxl313_set_act_inact_linkbit(data, cmd_en);
+ if (ret)
+ return ret;
+
+ return adxl313_set_measure_en(data, true);
+}
+
+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:
+ *val = 0;
+
+ *val2 = data->chip_info->scale_factor;
+
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ ret = regmap_read(data->regmap,
+ ADXL313_REG_OFS_AXIS(chan->address), &regval);
+ if (ret)
+ return ret;
+
+ /*
+ * 8-bit resolution at minimum range, 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, &regval);
+ 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 minimum range, 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 int adxl313_read_mag_config(struct adxl313_data *data,
+ enum iio_event_direction dir,
+ enum adxl313_activity_type type_act,
+ enum adxl313_activity_type type_inact)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return !!adxl313_is_act_inact_en(data, type_act);
+ case IIO_EV_DIR_FALLING:
+ return !!adxl313_is_act_inact_en(data, type_inact);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl313_write_mag_config(struct adxl313_data *data,
+ enum iio_event_direction dir,
+ enum adxl313_activity_type type_act,
+ enum adxl313_activity_type type_inact,
+ bool state)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl313_set_act_inact_en(data, type_act, state);
+ case IIO_EV_DIR_FALLING:
+ return adxl313_set_act_inact_en(data, type_inact, state);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl313_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 adxl313_data *data = iio_priv(indio_dev);
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl313_read_mag_config(data, dir,
+ ADXL313_ACTIVITY,
+ ADXL313_INACTIVITY);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl313_read_mag_config(data, dir,
+ ADXL313_ACTIVITY_AC,
+ ADXL313_INACTIVITY_AC);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl313_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct adxl313_data *data = iio_priv(indio_dev);
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl313_write_mag_config(data, dir,
+ ADXL313_ACTIVITY,
+ ADXL313_INACTIVITY,
+ state);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl313_write_mag_config(data, dir,
+ ADXL313_ACTIVITY_AC,
+ ADXL313_INACTIVITY_AC,
+ state);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl313_read_mag_value(struct adxl313_data *data,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ enum adxl313_activity_type type_act,
+ enum adxl313_activity_type type_inact,
+ int *val, int *val2)
+{
+ unsigned int threshold;
+ unsigned int period;
+ int ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_read(data->regmap,
+ adxl313_act_thresh_reg[type_act],
+ &threshold);
+ if (ret)
+ return ret;
+ *val = threshold * 15625;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ ret = regmap_read(data->regmap,
+ adxl313_act_thresh_reg[type_inact],
+ &threshold);
+ if (ret)
+ return ret;
+ *val = threshold * 15625;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ ret = regmap_read(data->regmap, ADXL313_REG_TIME_INACT,
+ &period);
+ if (ret)
+ return ret;
+ *val = period;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl313_write_mag_value(struct adxl313_data *data,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ enum adxl313_activity_type type_act,
+ enum adxl313_activity_type type_inact,
+ int val, int val2)
+{
+ unsigned int regval;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ /* Scale factor 15.625 mg/LSB */
+ regval = DIV_ROUND_CLOSEST(MICRO * val + val2, 15625);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return regmap_write(data->regmap,
+ adxl313_act_thresh_reg[type_act],
+ regval);
+ case IIO_EV_DIR_FALLING:
+ return regmap_write(data->regmap,
+ adxl313_act_thresh_reg[type_inact],
+ regval);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ return adxl313_set_inact_time_s(data, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl313_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct adxl313_data *data = iio_priv(indio_dev);
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl313_read_mag_value(data, dir, info,
+ ADXL313_ACTIVITY,
+ ADXL313_INACTIVITY,
+ val, val2);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl313_read_mag_value(data, dir, info,
+ ADXL313_ACTIVITY_AC,
+ ADXL313_INACTIVITY_AC,
+ val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl313_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct adxl313_data *data = iio_priv(indio_dev);
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl313_write_mag_value(data, dir, info,
+ ADXL313_ACTIVITY,
+ ADXL313_INACTIVITY,
+ val, val2);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl313_write_mag_value(data, dir, info,
+ ADXL313_ACTIVITY_AC,
+ ADXL313_INACTIVITY_AC,
+ val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl313_set_watermark(struct iio_dev *indio_dev, unsigned int value)
+{
+ struct adxl313_data *data = iio_priv(indio_dev);
+ int ret;
+
+ value = min(value, ADXL313_FIFO_SIZE - 1);
+
+ ret = adxl313_set_measure_en(data, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, ADXL313_REG_FIFO_CTL,
+ ADXL313_REG_FIFO_CTL_MODE_MSK, value);
+ if (ret)
+ return ret;
+
+ data->watermark = value;
+
+ ret = regmap_set_bits(data->regmap, ADXL313_REG_INT_ENABLE,
+ ADXL313_INT_WATERMARK);
+ if (ret)
+ return ret;
+
+ return adxl313_set_measure_en(data, true);
+}
+
+static int adxl313_get_samples(struct adxl313_data *data)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(data->regmap, ADXL313_REG_FIFO_STATUS, &regval);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(ADXL313_REG_FIFO_STATUS_ENTRIES_MSK, regval);
+}
+
+static int adxl313_fifo_transfer(struct adxl313_data *data, int samples)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < samples; i++) {
+ ret = regmap_bulk_read(data->regmap, ADXL313_REG_XYZ_BASE,
+ data->fifo_buf + (i * ADXL313_NUM_AXIS),
+ sizeof(data->fifo_buf[0]) * ADXL313_NUM_AXIS);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * adxl313_fifo_reset() - Reset the FIFO and interrupt status registers.
+ * @data: The device data.
+ *
+ * Reset the FIFO status registers. Reading out status registers clears the
+ * FIFO and interrupt configuration. Thus do not evaluate regmap return values.
+ * Ignore particular read register content. Register content is not processed
+ * any further. Therefore the function returns void.
+ */
+static void adxl313_fifo_reset(struct adxl313_data *data)
+{
+ unsigned int regval;
+ int samples;
+
+ adxl313_set_measure_en(data, false);
+
+ samples = adxl313_get_samples(data);
+ if (samples > 0)
+ adxl313_fifo_transfer(data, samples);
+
+ regmap_read(data->regmap, ADXL313_REG_INT_SOURCE, &regval);
+
+ adxl313_set_measure_en(data, true);
+}
+
+static int adxl313_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct adxl313_data *data = iio_priv(indio_dev);
+ int ret;
+
+ /* Set FIFO modes with measurement turned off, according to datasheet */
+ ret = adxl313_set_measure_en(data, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, ADXL313_REG_FIFO_CTL,
+ FIELD_PREP(ADXL313_REG_FIFO_CTL_SAMPLES_MSK, data->watermark) |
+ FIELD_PREP(ADXL313_REG_FIFO_CTL_MODE_MSK, ADXL313_FIFO_STREAM));
+ if (ret)
+ return ret;
+
+ return adxl313_set_measure_en(data, true);
+}
+
+static int adxl313_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct adxl313_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = adxl313_set_measure_en(data, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, ADXL313_REG_FIFO_CTL,
+ FIELD_PREP(ADXL313_REG_FIFO_CTL_MODE_MSK, ADXL313_FIFO_BYPASS));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, ADXL313_REG_INT_ENABLE, 0);
+ if (ret)
+ return ret;
+
+ return adxl313_set_measure_en(data, true);
+}
+
+static const struct iio_buffer_setup_ops adxl313_buffer_ops = {
+ .postenable = adxl313_buffer_postenable,
+ .predisable = adxl313_buffer_predisable,
+};
+
+static int adxl313_fifo_push(struct iio_dev *indio_dev, int samples)
+{
+ struct adxl313_data *data = iio_priv(indio_dev);
+ unsigned int i;
+ int ret;
+
+ ret = adxl313_fifo_transfer(data, samples);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ADXL313_NUM_AXIS * samples; i += ADXL313_NUM_AXIS)
+ iio_push_to_buffers(indio_dev, &data->fifo_buf[i]);
+
+ return 0;
+}
+
+static int adxl313_push_events(struct iio_dev *indio_dev, int int_stat)
+{
+ s64 ts = iio_get_time_ns(indio_dev);
+ struct adxl313_data *data = iio_priv(indio_dev);
+ unsigned int regval;
+ int ret = -ENOENT;
+
+ if (FIELD_GET(ADXL313_INT_ACTIVITY, int_stat)) {
+ ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ADXL313_REG_ACT_ACDC_MSK, regval)) {
+ /* AC coupled */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_MAG_ADAPTIVE,
+ IIO_EV_DIR_RISING),
+ ts);
+ if (ret)
+ return ret;
+ } else {
+ /* DC coupled, relying on THRESH */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ ts);
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (FIELD_GET(ADXL313_INT_INACTIVITY, int_stat)) {
+ ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ADXL313_REG_INACT_ACDC_MSK, regval)) {
+ /* AC coupled */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG_ADAPTIVE,
+ IIO_EV_DIR_FALLING),
+ ts);
+ if (ret)
+ return ret;
+ } else {
+ /* DC coupled, relying on THRESH */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_FALLING),
+ ts);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static irqreturn_t adxl313_irq_handler(int irq, void *p)
+{
+ struct iio_dev *indio_dev = p;
+ struct adxl313_data *data = iio_priv(indio_dev);
+ int samples, int_stat;
+
+ if (regmap_read(data->regmap, ADXL313_REG_INT_SOURCE, &int_stat))
+ return IRQ_NONE;
+
+ /*
+ * In cases of sensor events not handled (still not implemented) by
+ * this driver, the FIFO needs to be drained to become operational
+ * again. In general the sensor configuration only should issue events
+ * which were configured by this driver. Anyway a miss-configuration
+ * easily might end up in a hanging sensor FIFO.
+ */
+ if (adxl313_push_events(indio_dev, int_stat))
+ goto err_reset_fifo;
+
+ if (FIELD_GET(ADXL313_INT_WATERMARK, int_stat)) {
+ samples = adxl313_get_samples(data);
+ if (samples < 0)
+ goto err_reset_fifo;
+
+ if (adxl313_fifo_push(indio_dev, samples))
+ goto err_reset_fifo;
+ }
+
+ if (FIELD_GET(ADXL313_INT_OVERRUN, int_stat))
+ goto err_reset_fifo;
+
+ return IRQ_HANDLED;
+
+err_reset_fifo:
+ adxl313_fifo_reset(data);
+
+ return IRQ_HANDLED;
+}
+
+static int adxl313_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct adxl313_data *data = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(data->regmap, reg, readval);
+ return regmap_write(data->regmap, reg, writeval);
+}
+
+static const struct iio_info adxl313_info = {
+ .read_raw = adxl313_read_raw,
+ .write_raw = adxl313_write_raw,
+ .read_event_config = adxl313_read_event_config,
+ .write_event_config = adxl313_write_event_config,
+ .read_event_value = adxl313_read_event_value,
+ .write_event_value = adxl313_write_event_value,
+ .read_avail = adxl313_read_freq_avail,
+ .hwfifo_set_watermark = adxl313_set_watermark,
+ .debugfs_reg_access = &adxl313_reg_access,
+};
+
+static int adxl313_setup(struct device *dev, struct adxl313_data *data,
+ int (*setup)(struct device *, struct regmap *))
+{
+ int ret;
+
+ /*
+ * If sw reset available, ensures the device is in a consistent
+ * state after start up
+ */
+ if (data->chip_info->soft_reset) {
+ 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 = data->chip_info->check_id(dev, data);
+ if (ret)
+ return ret;
+
+ /* Sets the range to maximum, full resolution, if applicable */
+ if (data->chip_info->variable_range) {
+ ret = regmap_update_bits(data->regmap, ADXL313_REG_DATA_FORMAT,
+ ADXL313_RANGE_MSK,
+ FIELD_PREP(ADXL313_RANGE_MSK, ADXL313_RANGE_MAX));
+ 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 adxl313_set_measure_en(data, true);
+}
+
+static unsigned int adxl313_get_int_type(struct device *dev, int *irq)
+{
+ *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
+ if (*irq > 0)
+ return ADXL313_INT1;
+
+ *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
+ if (*irq > 0)
+ return ADXL313_INT2;
+
+ return ADXL313_INT_NONE;
+}
+
+/**
+ * adxl313_core_probe() - probe and setup for adxl313 accelerometer
+ * @dev: Driver model representation of the device
+ * @regmap: Register map of the device
+ * @chip_info: Structure containing device specific data
+ * @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 struct adxl313_chip_info *chip_info,
+ int (*setup)(struct device *, struct regmap *))
+{
+ struct adxl313_data *data;
+ struct iio_dev *indio_dev;
+ u8 int_line;
+ u8 int_map_msk;
+ int irq, ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->regmap = regmap;
+ data->chip_info = chip_info;
+
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret)
+ return ret;
+
+ indio_dev->name = chip_info->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);
+ indio_dev->available_scan_masks = adxl313_scan_masks;
+
+ ret = adxl313_setup(dev, data, setup);
+ if (ret)
+ return dev_err_probe(dev, ret, "ADXL313 setup failed\n");
+
+ int_line = adxl313_get_int_type(dev, &irq);
+ if (int_line == ADXL313_INT_NONE) {
+ /*
+ * FIFO_BYPASSED mode
+ *
+ * When no interrupt lines are specified, the driver falls back
+ * to use the sensor in FIFO_BYPASS mode. This means turning off
+ * internal FIFO and interrupt generation (since there is no
+ * line specified). Unmaskable interrupts such as overrun or
+ * data ready won't interfere. Even that a FIFO_STREAM mode w/o
+ * connected interrupt line might allow for obtaining raw
+ * measurements, a fallback to disable interrupts when no
+ * interrupt lines are connected seems to be the cleaner
+ * solution.
+ */
+ ret = regmap_write(data->regmap, ADXL313_REG_FIFO_CTL,
+ FIELD_PREP(ADXL313_REG_FIFO_CTL_MODE_MSK,
+ ADXL313_FIFO_BYPASS));
+ if (ret)
+ return ret;
+ } else {
+ /* FIFO_STREAM mode */
+ int_map_msk = ADXL313_INT_DREADY | ADXL313_INT_ACTIVITY |
+ ADXL313_INT_INACTIVITY | ADXL313_INT_WATERMARK |
+ ADXL313_INT_OVERRUN;
+ ret = regmap_assign_bits(data->regmap, ADXL313_REG_INT_MAP,
+ int_map_msk, int_line == ADXL313_INT2);
+ if (ret)
+ return ret;
+
+ /*
+ * Reset or configure the registers with reasonable default
+ * values. As having 0 in most cases may result in undesirable
+ * behavior if the interrupts are enabled.
+ */
+ ret = regmap_write(data->regmap, ADXL313_REG_ACT_INACT_CTL, 0x00);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, ADXL313_REG_TIME_INACT, 5);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, ADXL313_REG_THRESH_INACT, 0x4f);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, ADXL313_REG_THRESH_ACT, 0x52);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
+ &adxl313_buffer_ops);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ &adxl313_irq_handler,
+ IRQF_SHARED | IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(adxl313_core_probe, "IIO_ADXL313");
+
+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 000000000..2eea8d4d0
--- /dev/null
+++ b/drivers/iio/accel/adxl313_i2c.c
@@ -0,0 +1,98 @@
+// 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/module.h>
+#include <linux/regmap.h>
+
+#include "adxl313.h"
+
+static const struct regmap_config adxl31x_i2c_regmap_config[] = {
+ [ADXL312] = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &adxl312_readable_regs_table,
+ .wr_table = &adxl312_writable_regs_table,
+ .max_register = 0x39,
+ .volatile_reg = adxl313_is_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+ },
+ [ADXL313] = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &adxl313_readable_regs_table,
+ .wr_table = &adxl313_writable_regs_table,
+ .max_register = 0x39,
+ .volatile_reg = adxl313_is_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+ },
+ [ADXL314] = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &adxl314_readable_regs_table,
+ .wr_table = &adxl314_writable_regs_table,
+ .max_register = 0x39,
+ .volatile_reg = adxl313_is_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+ },
+};
+
+static const struct i2c_device_id adxl313_i2c_id[] = {
+ { .name = "adxl312", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL312] },
+ { .name = "adxl313", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL313] },
+ { .name = "adxl314", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL314] },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, adxl313_i2c_id);
+
+static const struct of_device_id adxl313_of_match[] = {
+ { .compatible = "adi,adxl312", .data = &adxl31x_chip_info[ADXL312] },
+ { .compatible = "adi,adxl313", .data = &adxl31x_chip_info[ADXL313] },
+ { .compatible = "adi,adxl314", .data = &adxl31x_chip_info[ADXL314] },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, adxl313_of_match);
+
+static int adxl313_i2c_probe(struct i2c_client *client)
+{
+ const struct adxl313_chip_info *chip_data;
+ struct device *dev = &client->dev;
+ struct regmap *regmap;
+
+ /*
+ * Retrieves device specific data as a pointer to a
+ * adxl313_chip_info structure
+ */
+ chip_data = i2c_get_match_data(client);
+
+ regmap = devm_regmap_init_i2c(client,
+ &adxl31x_i2c_regmap_config[chip_data->type]);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing i2c regmap\n");
+
+ return adxl313_core_probe(dev, regmap, chip_data, NULL);
+}
+
+static struct i2c_driver adxl313_i2c_driver = {
+ .driver = {
+ .name = "adxl313_i2c",
+ .of_match_table = adxl313_of_match,
+ },
+ .probe = 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");
+MODULE_IMPORT_NS("IIO_ADXL313");
diff --git a/drivers/iio/accel/adxl313_spi.c b/drivers/iio/accel/adxl313_spi.c
new file mode 100644
index 000000000..a61295de1
--- /dev/null
+++ b/drivers/iio/accel/adxl313_spi.c
@@ -0,0 +1,124 @@
+// 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/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+#include <linux/property.h>
+
+#include "adxl313.h"
+
+static const struct regmap_config adxl31x_spi_regmap_config[] = {
+ [ADXL312] = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &adxl312_readable_regs_table,
+ .wr_table = &adxl312_writable_regs_table,
+ .max_register = 0x39,
+ /* Setting bits 7 and 6 enables multiple-byte read */
+ .read_flag_mask = BIT(7) | BIT(6),
+ .volatile_reg = adxl313_is_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+ },
+ [ADXL313] = {
+ .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),
+ .volatile_reg = adxl313_is_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+ },
+ [ADXL314] = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &adxl314_readable_regs_table,
+ .wr_table = &adxl314_writable_regs_table,
+ .max_register = 0x39,
+ /* Setting bits 7 and 6 enables multiple-byte read */
+ .read_flag_mask = BIT(7) | BIT(6),
+ .volatile_reg = adxl313_is_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+ },
+};
+
+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 adxl313_chip_info *chip_data;
+ struct device *dev = &spi->dev;
+ struct regmap *regmap;
+ int ret;
+
+ spi->mode |= SPI_MODE_3;
+ ret = spi_setup(spi);
+ if (ret)
+ return ret;
+
+ chip_data = spi_get_device_match_data(spi);
+
+ regmap = devm_regmap_init_spi(spi,
+ &adxl31x_spi_regmap_config[chip_data->type]);
+
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing spi regmap\n");
+
+ return adxl313_core_probe(dev, regmap, chip_data, &adxl313_spi_setup);
+}
+
+static const struct spi_device_id adxl313_spi_id[] = {
+ { .name = "adxl312", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL312] },
+ { .name = "adxl313", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL313] },
+ { .name = "adxl314", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL314] },
+ { }
+};
+
+MODULE_DEVICE_TABLE(spi, adxl313_spi_id);
+
+static const struct of_device_id adxl313_of_match[] = {
+ { .compatible = "adi,adxl312", .data = &adxl31x_chip_info[ADXL312] },
+ { .compatible = "adi,adxl313", .data = &adxl31x_chip_info[ADXL313] },
+ { .compatible = "adi,adxl314", .data = &adxl31x_chip_info[ADXL314] },
+ { }
+};
+
+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");
+MODULE_IMPORT_NS("IIO_ADXL313");
diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h
new file mode 100644
index 000000000..9385affde
--- /dev/null
+++ b/drivers/iio/accel/adxl345.h
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ADXL345 3-Axis Digital Accelerometer
+ *
+ * Copyright (c) 2017 Eva Rachel Retuya <eraretuya@gmail.com>
+ */
+
+#ifndef _ADXL345_H_
+#define _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))
+
+/* Tap duration */
+#define ADXL345_REG_DUR 0x21
+/* Tap latency */
+#define ADXL345_REG_LATENT 0x22
+/* Tap window */
+#define ADXL345_REG_WINDOW 0x23
+/* Activity threshold */
+#define ADXL345_REG_THRESH_ACT 0x24
+/* Inactivity threshold */
+#define ADXL345_REG_THRESH_INACT 0x25
+/* Inactivity time */
+#define ADXL345_REG_TIME_INACT 0x26
+/* Axis enable control for activity and inactivity detection */
+#define ADXL345_REG_ACT_INACT_CTRL 0x27
+/* Free-fall threshold */
+#define ADXL345_REG_THRESH_FF 0x28
+/* Free-fall time */
+#define ADXL345_REG_TIME_FF 0x29
+/* Axis control for single tap or double tap */
+#define ADXL345_REG_TAP_AXIS 0x2A
+/* Source of single tap or double tap */
+#define ADXL345_REG_ACT_TAP_STATUS 0x2B
+/* Data rate and power mode control */
+#define ADXL345_REG_BW_RATE 0x2C
+#define ADXL345_REG_POWER_CTL 0x2D
+#define ADXL345_REG_INT_ENABLE 0x2E
+#define ADXL345_REG_INT_MAP 0x2F
+#define ADXL345_REG_INT_SOURCE 0x30
+#define ADXL345_REG_DATA_FORMAT 0x31
+#define ADXL345_REG_XYZ_BASE 0x32
+#define ADXL345_REG_DATA_AXIS(index) \
+ (ADXL345_REG_XYZ_BASE + (index) * sizeof(__le16))
+
+#define ADXL345_REG_FIFO_CTL 0x38
+#define ADXL345_FIFO_CTL_SAMPLES_MSK GENMASK(4, 0)
+/* 0: INT1, 1: INT2 */
+#define ADXL345_FIFO_CTL_TRIGGER_MSK BIT(5)
+#define ADXL345_FIFO_CTL_MODE_MSK GENMASK(7, 6)
+#define ADXL345_REG_FIFO_STATUS 0x39
+#define ADXL345_REG_FIFO_STATUS_MSK 0x3F
+
+#define ADXL345_INT_OVERRUN BIT(0)
+#define ADXL345_INT_WATERMARK BIT(1)
+#define ADXL345_INT_FREE_FALL BIT(2)
+#define ADXL345_INT_INACTIVITY BIT(3)
+#define ADXL345_INT_ACTIVITY BIT(4)
+#define ADXL345_INT_DOUBLE_TAP BIT(5)
+#define ADXL345_INT_SINGLE_TAP BIT(6)
+#define ADXL345_INT_DATA_READY BIT(7)
+
+/*
+ * BW_RATE bits - Bandwidth and output data rate. The default value is
+ * 0x0A, which translates to a 100 Hz output data rate
+ */
+#define ADXL345_BW_RATE_MSK GENMASK(3, 0)
+#define ADXL345_BW_LOW_POWER BIT(4)
+#define ADXL345_BASE_RATE_NANO_HZ 97656250LL
+
+#define ADXL345_POWER_CTL_WAKEUP GENMASK(1, 0)
+#define ADXL345_POWER_CTL_SLEEP BIT(2)
+#define ADXL345_POWER_CTL_MEASURE BIT(3)
+#define ADXL345_POWER_CTL_AUTO_SLEEP BIT(4)
+#define ADXL345_POWER_CTL_LINK BIT(5)
+
+/* Set the g range */
+#define ADXL345_DATA_FORMAT_RANGE GENMASK(1, 0)
+/* Data is left justified */
+#define ADXL345_DATA_FORMAT_JUSTIFY BIT(2)
+/* Up to 13-bits resolution */
+#define ADXL345_DATA_FORMAT_FULL_RES BIT(3)
+#define ADXL345_DATA_FORMAT_SPI_3WIRE BIT(6)
+#define ADXL345_DATA_FORMAT_SELF_TEST BIT(7)
+#define ADXL345_DATA_FORMAT_2G 0
+#define ADXL345_DATA_FORMAT_4G 1
+#define ADXL345_DATA_FORMAT_8G 2
+#define ADXL345_DATA_FORMAT_16G 3
+
+#define ADXL345_DEVID 0xE5
+#define ADXL345_FIFO_SIZE 32
+
+/*
+ * In full-resolution mode, scale factor is maintained at ~4 mg/LSB
+ * in all g ranges.
+ *
+ * At +/- 16g with 13-bit resolution, scale is computed as:
+ * (16 + 16) * 9.81 / (2^13 - 1) = 0.0383
+ */
+#define ADXL345_USCALE 38300
+
+/*
+ * The Datasheet lists a resolution of Resolution is ~49 mg per LSB. That's
+ * ~480mm/s**2 per LSB.
+ */
+#define ADXL375_USCALE 480000
+
+struct regmap;
+
+bool adxl345_is_volatile_reg(struct device *dev, unsigned int reg);
+
+struct adxl345_chip_info {
+ const char *name;
+ int uscale;
+};
+
+int adxl345_core_probe(struct device *dev, struct regmap *regmap,
+ bool fifo_delay_default,
+ int (*setup)(struct device*, struct regmap*));
+
+#endif /* _ADXL345_H_ */
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
new file mode 100644
index 000000000..6c9080d88
--- /dev/null
+++ b/drivers/iio/accel/adxl345_core.c
@@ -0,0 +1,2042 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ADXL345 3-Axis Digital Accelerometer IIO core driver
+ *
+ * Copyright (c) 2017 Eva Rachel Retuya <eraretuya@gmail.com>
+ *
+ * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/kfifo_buf.h>
+
+#include "adxl345.h"
+
+#define ADXL345_FIFO_BYPASS 0
+#define ADXL345_FIFO_FIFO 1
+#define ADXL345_FIFO_STREAM 2
+
+#define ADXL345_DIRS 3
+
+#define ADXL345_INT_NONE 0xff
+#define ADXL345_INT1 0
+#define ADXL345_INT2 1
+
+#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0)
+#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3)
+#define ADXL345_REG_TAP_SUPPRESS BIT(3)
+#define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK)
+
+#define ADXL345_TAP_Z_EN BIT(0)
+#define ADXL345_TAP_Y_EN BIT(1)
+#define ADXL345_TAP_X_EN BIT(2)
+#define ADXL345_REG_TAP_SUPPRESS BIT(3)
+
+#define ADXL345_INACT_Z_EN BIT(0)
+#define ADXL345_INACT_Y_EN BIT(1)
+#define ADXL345_INACT_X_EN BIT(2)
+#define ADXL345_REG_INACT_ACDC BIT(3)
+#define ADXL345_ACT_INACT_NO_AXIS_EN 0x00
+#define ADXL345_INACT_XYZ_EN (ADXL345_INACT_Z_EN | ADXL345_INACT_Y_EN | ADXL345_INACT_X_EN)
+
+#define ADXL345_ACT_Z_EN BIT(4)
+#define ADXL345_ACT_Y_EN BIT(5)
+#define ADXL345_ACT_X_EN BIT(6)
+#define ADXL345_REG_ACT_ACDC BIT(7)
+#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN)
+
+#define ADXL345_COUPLING_DC 0
+#define ADXL345_COUPLING_AC 1
+#define ADXL345_REG_NO_ACDC 0x00
+
+/* single/double tap */
+enum adxl345_tap_type {
+ ADXL345_SINGLE_TAP,
+ ADXL345_DOUBLE_TAP,
+};
+
+static const unsigned int adxl345_tap_int_reg[] = {
+ [ADXL345_SINGLE_TAP] = ADXL345_INT_SINGLE_TAP,
+ [ADXL345_DOUBLE_TAP] = ADXL345_INT_DOUBLE_TAP,
+};
+
+enum adxl345_tap_time_type {
+ ADXL345_TAP_TIME_LATENT,
+ ADXL345_TAP_TIME_WINDOW,
+ ADXL345_TAP_TIME_DUR,
+};
+
+static const unsigned int adxl345_tap_time_reg[] = {
+ [ADXL345_TAP_TIME_LATENT] = ADXL345_REG_LATENT,
+ [ADXL345_TAP_TIME_WINDOW] = ADXL345_REG_WINDOW,
+ [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR,
+};
+
+/* activity/inactivity */
+enum adxl345_activity_type {
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ ADXL345_INACTIVITY_FF,
+};
+
+static const unsigned int adxl345_act_int_reg[] = {
+ [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY,
+ [ADXL345_INACTIVITY] = ADXL345_INT_INACTIVITY,
+ [ADXL345_ACTIVITY_AC] = ADXL345_INT_ACTIVITY,
+ [ADXL345_INACTIVITY_AC] = ADXL345_INT_INACTIVITY,
+ [ADXL345_INACTIVITY_FF] = ADXL345_INT_FREE_FALL,
+};
+
+static const unsigned int adxl345_act_thresh_reg[] = {
+ [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT,
+ [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT,
+ [ADXL345_ACTIVITY_AC] = ADXL345_REG_THRESH_ACT,
+ [ADXL345_INACTIVITY_AC] = ADXL345_REG_THRESH_INACT,
+ [ADXL345_INACTIVITY_FF] = ADXL345_REG_THRESH_FF,
+};
+
+static const unsigned int adxl345_act_acdc_msk[] = {
+ [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC,
+ [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC,
+ [ADXL345_ACTIVITY_AC] = ADXL345_REG_ACT_ACDC,
+ [ADXL345_INACTIVITY_AC] = ADXL345_REG_INACT_ACDC,
+ [ADXL345_INACTIVITY_FF] = ADXL345_REG_NO_ACDC,
+};
+
+enum adxl345_odr {
+ ADXL345_ODR_0P10HZ = 0,
+ ADXL345_ODR_0P20HZ,
+ ADXL345_ODR_0P39HZ,
+ ADXL345_ODR_0P78HZ,
+ ADXL345_ODR_1P56HZ,
+ ADXL345_ODR_3P13HZ,
+ ADXL345_ODR_6P25HZ,
+ ADXL345_ODR_12P50HZ,
+ ADXL345_ODR_25HZ,
+ ADXL345_ODR_50HZ,
+ ADXL345_ODR_100HZ,
+ ADXL345_ODR_200HZ,
+ ADXL345_ODR_400HZ,
+ ADXL345_ODR_800HZ,
+ ADXL345_ODR_1600HZ,
+ ADXL345_ODR_3200HZ,
+};
+
+enum adxl345_range {
+ ADXL345_2G_RANGE = 0,
+ ADXL345_4G_RANGE,
+ ADXL345_8G_RANGE,
+ ADXL345_16G_RANGE,
+};
+
+/* Certain features recommend 12.5 Hz - 400 Hz ODR */
+static const int adxl345_odr_tbl[][2] = {
+ [ADXL345_ODR_0P10HZ] = { 0, 97000 },
+ [ADXL345_ODR_0P20HZ] = { 0, 195000 },
+ [ADXL345_ODR_0P39HZ] = { 0, 390000 },
+ [ADXL345_ODR_0P78HZ] = { 0, 781000 },
+ [ADXL345_ODR_1P56HZ] = { 1, 562000 },
+ [ADXL345_ODR_3P13HZ] = { 3, 125000 },
+ [ADXL345_ODR_6P25HZ] = { 6, 250000 },
+ [ADXL345_ODR_12P50HZ] = { 12, 500000 },
+ [ADXL345_ODR_25HZ] = { 25, 0 },
+ [ADXL345_ODR_50HZ] = { 50, 0 },
+ [ADXL345_ODR_100HZ] = { 100, 0 },
+ [ADXL345_ODR_200HZ] = { 200, 0 },
+ [ADXL345_ODR_400HZ] = { 400, 0 },
+ [ADXL345_ODR_800HZ] = { 800, 0 },
+ [ADXL345_ODR_1600HZ] = { 1600, 0 },
+ [ADXL345_ODR_3200HZ] = { 3200, 0 },
+};
+
+/*
+ * Full resolution frequency table:
+ * (g * 2 * 9.80665) / (2^(resolution) - 1)
+ *
+ * resolution := 13 (full)
+ * g := 2|4|8|16
+ *
+ * 2g at 13bit: 0.004789
+ * 4g at 13bit: 0.009578
+ * 8g at 13bit: 0.019156
+ * 16g at 16bit: 0.038312
+ */
+static const int adxl345_fullres_range_tbl[][2] = {
+ [ADXL345_2G_RANGE] = { 0, 4789 },
+ [ADXL345_4G_RANGE] = { 0, 9578 },
+ [ADXL345_8G_RANGE] = { 0, 19156 },
+ [ADXL345_16G_RANGE] = { 0, 38312 },
+};
+
+/* scaling */
+static const int adxl345_range_factor_tbl[] = {
+ [ADXL345_2G_RANGE] = 1,
+ [ADXL345_4G_RANGE] = 2,
+ [ADXL345_8G_RANGE] = 4,
+ [ADXL345_16G_RANGE] = 8,
+};
+
+struct adxl345_state {
+ const struct adxl345_chip_info *info;
+ struct regmap *regmap;
+ bool fifo_delay; /* delay: delay is needed for SPI */
+ u8 watermark;
+ u8 fifo_mode;
+
+ u8 inact_threshold;
+ u32 inact_time_ms;
+
+ u32 tap_duration_us;
+ u32 tap_latent_us;
+ u32 tap_window_us;
+
+ __le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN);
+};
+
+static const struct iio_event_spec adxl345_events[] = {
+ {
+ /* activity */
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type =
+ BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_SCALE) |
+ BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ /* activity, ac bit set */
+ .type = IIO_EV_TYPE_MAG_ADAPTIVE,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type =
+ BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_SCALE) |
+ BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ /* single tap */
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_SINGLETAP,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type =
+ BIT(IIO_EV_INFO_SCALE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_TIMEOUT),
+ },
+ {
+ /* double tap */
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_DOUBLETAP,
+ .mask_shared_by_type =
+ BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_SCALE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_RESET_TIMEOUT) |
+ BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
+ },
+};
+
+#define ADXL345_CHANNEL(index, reg, axis) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .address = (reg), \
+ .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_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = (index), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 13, \
+ .storagebits = 16, \
+ .endianness = IIO_LE, \
+ }, \
+ .event_spec = adxl345_events, \
+ .num_event_specs = ARRAY_SIZE(adxl345_events), \
+}
+
+enum adxl345_chans {
+ chan_x, chan_y, chan_z,
+};
+
+static const struct iio_event_spec adxl345_fake_chan_events[] = {
+ {
+ /* inactivity */
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type =
+ BIT(IIO_EV_INFO_SCALE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+ {
+ /* inactivity, AC bit set */
+ .type = IIO_EV_TYPE_MAG_ADAPTIVE,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type =
+ BIT(IIO_EV_INFO_SCALE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+};
+
+static const struct iio_chan_spec adxl345_channels[] = {
+ ADXL345_CHANNEL(0, chan_x, X),
+ ADXL345_CHANNEL(1, chan_y, Y),
+ ADXL345_CHANNEL(2, chan_z, Z),
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X_AND_Y_AND_Z,
+ .scan_index = -1, /* Fake channel */
+ .event_spec = adxl345_fake_chan_events,
+ .num_event_specs = ARRAY_SIZE(adxl345_fake_chan_events),
+ },
+};
+
+static const unsigned long adxl345_scan_masks[] = {
+ BIT(chan_x) | BIT(chan_y) | BIT(chan_z),
+ 0
+};
+
+bool adxl345_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADXL345_REG_DATA_AXIS(0):
+ case ADXL345_REG_DATA_AXIS(1):
+ case ADXL345_REG_DATA_AXIS(2):
+ case ADXL345_REG_DATA_AXIS(3):
+ case ADXL345_REG_DATA_AXIS(4):
+ case ADXL345_REG_DATA_AXIS(5):
+ case ADXL345_REG_ACT_TAP_STATUS:
+ case ADXL345_REG_FIFO_STATUS:
+ case ADXL345_REG_INT_SOURCE:
+ return true;
+ default:
+ return false;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(adxl345_is_volatile_reg, "IIO_ADXL345");
+
+/**
+ * adxl345_set_measure_en() - Enable and disable measuring.
+ *
+ * @st: The device data.
+ * @en: Enable measurements, else standby mode.
+ *
+ * For lowest power operation, standby mode can be used. In standby mode,
+ * current consumption is supposed to be reduced to 0.1uA (typical). In this
+ * mode no measurements are made. Placing the device into standby mode
+ * preserves the contents of FIFO.
+ *
+ * Return: Returns 0 if successful, or a negative error value.
+ */
+static int adxl345_set_measure_en(struct adxl345_state *st, bool en)
+{
+ return regmap_assign_bits(st->regmap, ADXL345_REG_POWER_CTL,
+ ADXL345_POWER_CTL_MEASURE, en);
+}
+
+/* activity / inactivity */
+
+static int adxl345_set_inact_threshold(struct adxl345_state *st,
+ unsigned int threshold)
+{
+ int ret;
+
+ st->inact_threshold = min(U8_MAX, threshold);
+
+ ret = regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY],
+ st->inact_threshold);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_INACTIVITY_FF],
+ st->inact_threshold);
+}
+
+static int adxl345_set_default_time(struct adxl345_state *st)
+{
+ int max_boundary = U8_MAX;
+ int min_boundary = 10;
+ enum adxl345_odr odr;
+ unsigned int regval;
+ unsigned int val;
+ int ret;
+
+ /* Generated inactivity time based on ODR */
+ ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, &regval);
+ if (ret)
+ return ret;
+
+ odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval);
+ val = clamp(max_boundary - adxl345_odr_tbl[odr][0],
+ min_boundary, max_boundary);
+ st->inact_time_ms = MILLI * val;
+
+ /* Inactivity time in s */
+ return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val);
+}
+
+static int adxl345_set_inactivity_time(struct adxl345_state *st, u32 val_int)
+{
+ st->inact_time_ms = MILLI * val_int;
+
+ return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val_int);
+}
+
+static int adxl345_set_freefall_time(struct adxl345_state *st, u32 val_fract)
+{
+ /*
+ * Datasheet max. value is 255 * 5000 us = 1.275000 seconds.
+ *
+ * Recommended values between 100ms and 350ms (0x14 to 0x46)
+ */
+ st->inact_time_ms = DIV_ROUND_UP(val_fract, MILLI);
+
+ return regmap_write(st->regmap, ADXL345_REG_TIME_FF,
+ DIV_ROUND_CLOSEST(val_fract, 5));
+}
+
+/**
+ * adxl345_set_inact_time - Configure inactivity time explicitly or by ODR.
+ * @st: The sensor state instance.
+ * @val_int: The inactivity time, integer part.
+ * @val_fract: The inactivity time, fractional part when val_int is 0.
+ *
+ * Inactivity time can be configured between 1 and 255 seconds. If a user sets
+ * val_s to 0, a default inactivity time is calculated automatically (since 0 is
+ * also invalid and undefined by the sensor).
+ *
+ * In such cases, power consumption should be considered: the inactivity period
+ * should be shorter at higher sampling frequencies and longer at lower ones.
+ * Specifically, for frequencies above 255 Hz, the default is set to 10 seconds;
+ * for frequencies below 10 Hz, it defaults to 255 seconds.
+ *
+ * The calculation method subtracts the integer part of the configured sample
+ * frequency from 255 to estimate the inactivity time in seconds. Sub-Hertz
+ * values are ignored in this approximation. Since the recommended output data
+ * rates (ODRs) for features like activity/inactivity detection, sleep modes,
+ * and free fall range between 12.5 Hz and 400 Hz, frequencies outside this
+ * range will either use the defined boundary defaults or require explicit
+ * configuration via val_s.
+ *
+ * Return: 0 or error value.
+ */
+static int adxl345_set_inact_time(struct adxl345_state *st, u32 val_int,
+ u32 val_fract)
+{
+ if (val_int > 0) {
+ /* Time >= 1s, inactivity */
+ return adxl345_set_inactivity_time(st, val_int);
+ } else if (val_int == 0) {
+ if (val_fract > 0) {
+ /* Time < 1s, free-fall */
+ return adxl345_set_freefall_time(st, val_fract);
+ } else if (val_fract == 0) {
+ /* Time == 0.0s */
+ return adxl345_set_default_time(st);
+ }
+ }
+
+ /* Do not support negative or wrong input. */
+ return -EINVAL;
+}
+
+/**
+ * adxl345_is_act_inact_ac() - Verify if AC or DC coupling is currently enabled.
+ *
+ * @st: The device data.
+ * @type: The activity or inactivity type.
+ *
+ * Given a type of activity / inactivity combined with either AC coupling set or
+ * default to DC, this function verifies if the combination is currently
+ * configured, hence enabled or not.
+ *
+ * Return: true if configured coupling matches the provided type, else a negative
+ * error value.
+ */
+static int adxl345_is_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type)
+{
+ unsigned int regval;
+ bool coupling;
+ int ret;
+
+ if (type == ADXL345_INACTIVITY_FF)
+ return true;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &regval);
+ if (ret)
+ return ret;
+
+ coupling = adxl345_act_acdc_msk[type] & regval;
+
+ switch (type) {
+ case ADXL345_ACTIVITY:
+ case ADXL345_INACTIVITY:
+ return coupling == ADXL345_COUPLING_DC;
+ case ADXL345_ACTIVITY_AC:
+ case ADXL345_INACTIVITY_AC:
+ return coupling == ADXL345_COUPLING_AC;
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * adxl345_set_act_inact_ac() - Configure AC coupling or DC coupling.
+ *
+ * @st: The device data.
+ * @type: Provide a type of activity or inactivity.
+ * @cmd_en: enable or disable AC coupling.
+ *
+ * Enables AC coupling or DC coupling depending on the provided type argument.
+ * Note: Activity and inactivity can be either AC coupled or DC coupled not
+ * both at the same time.
+ *
+ * Return: 0 if successful, else error value.
+ */
+static int adxl345_set_act_inact_ac(struct adxl345_state *st,
+ enum adxl345_activity_type type,
+ bool cmd_en)
+{
+ unsigned int act_inact_ac;
+
+ if (type == ADXL345_ACTIVITY_AC || type == ADXL345_INACTIVITY_AC)
+ act_inact_ac = ADXL345_COUPLING_AC && cmd_en;
+ else
+ act_inact_ac = ADXL345_COUPLING_DC && cmd_en;
+
+ /*
+ * A setting of false selects dc-coupled operation, and a setting of
+ * true enables ac-coupled operation. In dc-coupled operation, the
+ * current acceleration magnitude is compared directly with
+ * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine
+ * whether activity or inactivity is detected.
+ *
+ * In ac-coupled operation for activity detection, the acceleration
+ * value at the start of activity detection is taken as a reference
+ * value. New samples of acceleration are then compared to this
+ * reference value, and if the magnitude of the difference exceeds the
+ * ADXL345_REG_THRESH_ACT value, the device triggers an activity
+ * interrupt.
+ *
+ * Similarly, in ac-coupled operation for inactivity detection, a
+ * reference value is used for comparison and is updated whenever the
+ * device exceeds the inactivity threshold. After the reference value
+ * is selected, the device compares the magnitude of the difference
+ * between the reference value and the current acceleration with
+ * ADXL345_REG_THRESH_INACT. If the difference is less than the value in
+ * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the
+ * device is considered inactive and the inactivity interrupt is
+ * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G]
+ *
+ * In a conclusion, the first acceleration snapshot sample which hit the
+ * threshold in a particular direction is always taken as acceleration
+ * reference value to that direction. Since for the hardware activity
+ * and inactivity depend on the x/y/z axis, so do ac and dc coupling.
+ * Note, this sw driver always enables or disables all three x/y/z axis
+ * for detection via act_axis_ctrl and inact_axis_ctrl, respectively.
+ * Where in dc-coupling samples are compared against the thresholds, in
+ * ac-coupling measurement difference to the first acceleration
+ * reference value are compared against the threshold. So, ac-coupling
+ * allows for a bit more dynamic compensation depending on the initial
+ * sample.
+ */
+ return regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
+ adxl345_act_acdc_msk[type], act_inact_ac);
+}
+
+static int adxl345_is_act_inact_en(struct adxl345_state *st,
+ enum adxl345_activity_type type)
+{
+ unsigned int axis_ctrl;
+ unsigned int regval;
+ bool int_en, en;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ /* Check if axis for activity are enabled */
+ switch (type) {
+ case ADXL345_ACTIVITY:
+ case ADXL345_ACTIVITY_AC:
+ en = FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl);
+ if (!en)
+ return false;
+ break;
+ case ADXL345_INACTIVITY:
+ case ADXL345_INACTIVITY_AC:
+ en = FIELD_GET(ADXL345_INACT_XYZ_EN, axis_ctrl);
+ if (!en)
+ return false;
+ break;
+ case ADXL345_INACTIVITY_FF:
+ en = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Check if specific interrupt is enabled */
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, &regval);
+ if (ret)
+ return ret;
+
+ int_en = adxl345_act_int_reg[type] & regval;
+ if (!int_en)
+ return false;
+
+ /* Check if configured coupling matches provided type */
+ return adxl345_is_act_inact_ac(st, type);
+}
+
+static int adxl345_set_act_inact_linkbit(struct adxl345_state *st,
+ enum adxl345_activity_type type,
+ bool en)
+{
+ int act_ac_en, inact_ac_en;
+ int act_en, inact_en;
+
+ act_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY);
+ if (act_en < 0)
+ return act_en;
+
+ act_ac_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY_AC);
+ if (act_ac_en < 0)
+ return act_ac_en;
+
+ if (type == ADXL345_INACTIVITY_FF) {
+ inact_en = false;
+ } else {
+ inact_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY);
+ if (inact_en < 0)
+ return inact_en;
+
+ inact_ac_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY_AC);
+ if (inact_ac_en < 0)
+ return inact_ac_en;
+
+ inact_en = inact_en || inact_ac_en;
+ }
+
+ act_en = act_en || act_ac_en;
+
+ return regmap_assign_bits(st->regmap, ADXL345_REG_POWER_CTL,
+ ADXL345_POWER_CTL_INACT_MSK,
+ en && act_en && inact_en);
+}
+
+static int adxl345_set_act_inact_en(struct adxl345_state *st,
+ enum adxl345_activity_type type,
+ bool cmd_en)
+{
+ unsigned int axis_ctrl;
+ unsigned int threshold;
+ unsigned int period;
+ int ret;
+
+ if (cmd_en) {
+ /* When turning on, check if threshold is valid */
+ if (type == ADXL345_ACTIVITY || type == ADXL345_ACTIVITY_AC) {
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[type],
+ &threshold);
+ if (ret)
+ return ret;
+ } else {
+ threshold = st->inact_threshold;
+ }
+
+ if (!threshold) /* Just ignore the command if threshold is 0 */
+ return 0;
+
+ /* When turning on inactivity, check if inact time is valid */
+ if (type == ADXL345_INACTIVITY || type == ADXL345_INACTIVITY_AC) {
+ ret = regmap_read(st->regmap,
+ ADXL345_REG_TIME_INACT,
+ &period);
+ if (ret)
+ return ret;
+
+ if (!period)
+ return 0;
+ }
+ } else {
+ /*
+ * When turning off an activity, ensure that the correct
+ * coupling event is specified. This step helps prevent misuse -
+ * for example, if an AC-coupled activity is active and the
+ * current call attempts to turn off a DC-coupled activity, this
+ * inconsistency should be detected here.
+ */
+ if (adxl345_is_act_inact_ac(st, type) <= 0)
+ return 0;
+ }
+
+ /* Start modifying configuration registers */
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ /* Enable axis according to the command */
+ switch (type) {
+ case ADXL345_ACTIVITY:
+ case ADXL345_ACTIVITY_AC:
+ axis_ctrl = ADXL345_ACT_XYZ_EN;
+ break;
+ case ADXL345_INACTIVITY:
+ case ADXL345_INACTIVITY_AC:
+ axis_ctrl = ADXL345_INACT_XYZ_EN;
+ break;
+ case ADXL345_INACTIVITY_FF:
+ axis_ctrl = ADXL345_ACT_INACT_NO_AXIS_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL,
+ axis_ctrl, cmd_en);
+ if (ret)
+ return ret;
+
+ /* Update AC/DC-coupling according to the command */
+ ret = adxl345_set_act_inact_ac(st, type, cmd_en);
+ if (ret)
+ return ret;
+
+ /* Enable the interrupt line, according to the command */
+ ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ adxl345_act_int_reg[type], cmd_en);
+ if (ret)
+ return ret;
+
+ /* Set link-bit and auto-sleep only when ACT and INACT are enabled */
+ ret = adxl345_set_act_inact_linkbit(st, type, cmd_en);
+ if (ret)
+ return ret;
+
+ return adxl345_set_measure_en(st, true);
+}
+
+/* tap */
+
+static int _adxl345_set_tap_int(struct adxl345_state *st,
+ enum adxl345_tap_type type, bool state)
+{
+ unsigned int int_map = 0x00;
+ unsigned int tap_threshold;
+ bool axis_valid;
+ bool singletap_args_valid = false;
+ bool doubletap_args_valid = false;
+ bool en = false;
+ u32 axis_ctrl;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ axis_valid = FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) > 0;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_THRESH_TAP, &tap_threshold);
+ if (ret)
+ return ret;
+
+ /*
+ * Note: A value of 0 for threshold and/or dur may result in undesirable
+ * behavior if single tap/double tap interrupts are enabled.
+ */
+ singletap_args_valid = tap_threshold > 0 && st->tap_duration_us > 0;
+
+ if (type == ADXL345_SINGLE_TAP) {
+ en = axis_valid && singletap_args_valid;
+ } else {
+ /* doubletap: Window must be equal or greater than latent! */
+ doubletap_args_valid = st->tap_latent_us > 0 &&
+ st->tap_window_us > 0 &&
+ st->tap_window_us >= st->tap_latent_us;
+
+ en = axis_valid && singletap_args_valid && doubletap_args_valid;
+ }
+
+ if (state && en)
+ int_map |= adxl345_tap_int_reg[type];
+
+ return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ adxl345_tap_int_reg[type], int_map);
+}
+
+static int adxl345_is_tap_en(struct adxl345_state *st,
+ enum iio_modifier axis,
+ enum adxl345_tap_type type, bool *en)
+{
+ unsigned int regval;
+ u32 axis_ctrl;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
+ if (ret)
+ return ret;
+
+ /* Verify if axis is enabled for the tap detection. */
+ switch (axis) {
+ case IIO_MOD_X:
+ *en = FIELD_GET(ADXL345_TAP_X_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Y:
+ *en = FIELD_GET(ADXL345_TAP_Y_EN, axis_ctrl);
+ break;
+ case IIO_MOD_Z:
+ *en = FIELD_GET(ADXL345_TAP_Z_EN, axis_ctrl);
+ break;
+ default:
+ *en = false;
+ return -EINVAL;
+ }
+
+ if (*en) {
+ /*
+ * If axis allow for tap detection, verify if the interrupt is
+ * enabled for tap detection.
+ */
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, &regval);
+ if (ret)
+ return ret;
+
+ *en = adxl345_tap_int_reg[type] & regval;
+ }
+
+ return 0;
+}
+
+static int adxl345_set_singletap_en(struct adxl345_state *st,
+ enum iio_modifier axis, bool en)
+{
+ int ret;
+ u32 axis_ctrl;
+
+ switch (axis) {
+ case IIO_MOD_X:
+ axis_ctrl = ADXL345_TAP_X_EN;
+ break;
+ case IIO_MOD_Y:
+ axis_ctrl = ADXL345_TAP_Y_EN;
+ break;
+ case IIO_MOD_Z:
+ axis_ctrl = ADXL345_TAP_Z_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (en)
+ ret = regmap_set_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+ axis_ctrl);
+ else
+ ret = regmap_clear_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+ axis_ctrl);
+ if (ret)
+ return ret;
+
+ return _adxl345_set_tap_int(st, ADXL345_SINGLE_TAP, en);
+}
+
+static int adxl345_set_doubletap_en(struct adxl345_state *st, bool en)
+{
+ int ret;
+
+ /*
+ * Generally suppress detection of spikes during the latency period as
+ * double taps here, this is fully optional for double tap detection
+ */
+ ret = regmap_assign_bits(st->regmap, ADXL345_REG_TAP_AXIS,
+ ADXL345_REG_TAP_SUPPRESS, en);
+ if (ret)
+ return ret;
+
+ return _adxl345_set_tap_int(st, ADXL345_DOUBLE_TAP, en);
+}
+
+static int _adxl345_set_tap_time(struct adxl345_state *st,
+ enum adxl345_tap_time_type type, u32 val_us)
+{
+ unsigned int regval;
+
+ switch (type) {
+ case ADXL345_TAP_TIME_WINDOW:
+ st->tap_window_us = val_us;
+ break;
+ case ADXL345_TAP_TIME_LATENT:
+ st->tap_latent_us = val_us;
+ break;
+ case ADXL345_TAP_TIME_DUR:
+ st->tap_duration_us = val_us;
+ break;
+ }
+
+ /*
+ * The scale factor is 1250us / LSB for tap_window_us and tap_latent_us.
+ * For tap_duration_us the scale factor is 625us / LSB.
+ */
+ if (type == ADXL345_TAP_TIME_DUR)
+ regval = DIV_ROUND_CLOSEST(val_us, 625);
+ else
+ regval = DIV_ROUND_CLOSEST(val_us, 1250);
+
+ return regmap_write(st->regmap, adxl345_tap_time_reg[type], regval);
+}
+
+static int adxl345_set_tap_duration(struct adxl345_state *st, u32 val_int,
+ u32 val_fract_us)
+{
+ /*
+ * Max value is 255 * 625 us = 0.159375 seconds
+ *
+ * Note: the scaling is similar to the scaling in the ADXL380
+ */
+ if (val_int || val_fract_us > 159375)
+ return -EINVAL;
+
+ return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_DUR, val_fract_us);
+}
+
+static int adxl345_set_tap_window(struct adxl345_state *st, u32 val_int,
+ u32 val_fract_us)
+{
+ /*
+ * Max value is 255 * 1250 us = 0.318750 seconds
+ *
+ * Note: the scaling is similar to the scaling in the ADXL380
+ */
+ if (val_int || val_fract_us > 318750)
+ return -EINVAL;
+
+ return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_WINDOW, val_fract_us);
+}
+
+static int adxl345_set_tap_latent(struct adxl345_state *st, u32 val_int,
+ u32 val_fract_us)
+{
+ /*
+ * Max value is 255 * 1250 us = 0.318750 seconds
+ *
+ * Note: the scaling is similar to the scaling in the ADXL380
+ */
+ if (val_int || val_fract_us > 318750)
+ return -EINVAL;
+
+ return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_LATENT, val_fract_us);
+}
+
+static int adxl345_find_odr(struct adxl345_state *st, int val,
+ int val2, enum adxl345_odr *odr)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(adxl345_odr_tbl); i++) {
+ if (val == adxl345_odr_tbl[i][0] &&
+ val2 == adxl345_odr_tbl[i][1]) {
+ *odr = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr)
+{
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE,
+ ADXL345_BW_RATE_MSK,
+ FIELD_PREP(ADXL345_BW_RATE_MSK, odr));
+ if (ret)
+ return ret;
+
+ /* update inactivity time by ODR */
+ return adxl345_set_inact_time(st, 0, 0);
+}
+
+static int adxl345_find_range(struct adxl345_state *st, int val, int val2,
+ enum adxl345_range *range)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(adxl345_fullres_range_tbl); i++) {
+ if (val == adxl345_fullres_range_tbl[i][0] &&
+ val2 == adxl345_fullres_range_tbl[i][1]) {
+ *range = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range)
+{
+ unsigned int act_threshold, inact_threshold;
+ unsigned int range_old;
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, &regval);
+ if (ret)
+ return ret;
+ range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
+
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[ADXL345_ACTIVITY],
+ &act_threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
+ ADXL345_DATA_FORMAT_RANGE,
+ FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range));
+ if (ret)
+ return ret;
+
+ act_threshold = act_threshold * adxl345_range_factor_tbl[range_old]
+ / adxl345_range_factor_tbl[range];
+ act_threshold = min(U8_MAX, max(1, act_threshold));
+
+ inact_threshold = st->inact_threshold;
+ inact_threshold = inact_threshold * adxl345_range_factor_tbl[range_old]
+ / adxl345_range_factor_tbl[range];
+ inact_threshold = min(U8_MAX, max(1, inact_threshold));
+
+ ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY],
+ act_threshold);
+ if (ret)
+ return ret;
+
+ return adxl345_set_inact_threshold(st, inact_threshold);
+}
+
+static int adxl345_read_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_SCALE:
+ *vals = (int *)adxl345_fullres_range_tbl;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = ARRAY_SIZE(adxl345_fullres_range_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = (int *)adxl345_odr_tbl;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = ARRAY_SIZE(adxl345_odr_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ }
+
+ return -EINVAL;
+}
+
+static int adxl345_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+ __le16 accel;
+ unsigned int regval;
+ enum adxl345_odr odr;
+ enum adxl345_range range;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ /*
+ * 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
+ */
+ ret = regmap_bulk_read(st->regmap,
+ ADXL345_REG_DATA_AXIS(chan->address),
+ &accel, sizeof(accel));
+ if (ret)
+ return ret;
+
+ *val = sign_extend32(le16_to_cpu(accel), 12);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, &regval);
+ if (ret)
+ return ret;
+ range = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval);
+ *val = adxl345_fullres_range_tbl[range][0];
+ *val2 = adxl345_fullres_range_tbl[range][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ ret = regmap_read(st->regmap,
+ ADXL345_REG_OFS_AXIS(chan->address), &regval);
+ if (ret)
+ return ret;
+ /*
+ * 8-bit resolution at +/- 2g, that is 4x accel data scale
+ * factor
+ */
+ *val = sign_extend32(regval, 7) * 4;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, &regval);
+ if (ret)
+ return ret;
+ odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval);
+ *val = adxl345_odr_tbl[odr][0];
+ *val2 = adxl345_odr_tbl[odr][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ return -EINVAL;
+}
+
+static int adxl345_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+ enum adxl345_range range;
+ enum adxl345_odr odr;
+ int ret;
+
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBBIAS:
+ /*
+ * 8-bit resolution at +/- 2g, that is 4x accel data scale
+ * factor
+ */
+ ret = regmap_write(st->regmap,
+ ADXL345_REG_OFS_AXIS(chan->address),
+ val / 4);
+ if (ret)
+ return ret;
+ break;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = adxl345_find_odr(st, val, val2, &odr);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_odr(st, odr);
+ if (ret)
+ return ret;
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ ret = adxl345_find_range(st, val, val2, &range);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_range(st, range);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return adxl345_set_measure_en(st, true);
+}
+
+static int adxl345_read_mag_config(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return !!adxl345_is_act_inact_en(st, type_act);
+ case IIO_EV_DIR_FALLING:
+ return !!adxl345_is_act_inact_en(st, type_inact);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_write_mag_config(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact,
+ bool state)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl345_set_act_inact_en(st, type_act, state);
+ case IIO_EV_DIR_FALLING:
+ return adxl345_set_act_inact_en(st, type_inact, state);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_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 adxl345_state *st = iio_priv(indio_dev);
+ bool int_en;
+ int ret;
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl345_read_mag_config(st, dir,
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl345_read_mag_config(st, dir,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC);
+ case IIO_EV_TYPE_GESTURE:
+ switch (dir) {
+ case IIO_EV_DIR_SINGLETAP:
+ ret = adxl345_is_tap_en(st, chan->channel2,
+ ADXL345_SINGLE_TAP, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ case IIO_EV_DIR_DOUBLETAP:
+ ret = adxl345_is_tap_en(st, chan->channel2,
+ ADXL345_DOUBLE_TAP, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl345_write_mag_config(st, dir,
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
+ state);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl345_write_mag_config(st, dir,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ state);
+ case IIO_EV_TYPE_GESTURE:
+ switch (dir) {
+ case IIO_EV_DIR_SINGLETAP:
+ return adxl345_set_singletap_en(st, chan->channel2, state);
+ case IIO_EV_DIR_DOUBLETAP:
+ return adxl345_set_doubletap_en(st, state);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_read_mag_value(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact,
+ int *val, int *val2)
+{
+ unsigned int threshold;
+ int ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = regmap_read(st->regmap,
+ adxl345_act_thresh_reg[type_act],
+ &threshold);
+ if (ret)
+ return ret;
+ *val = 62500 * threshold;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ *val = 62500 * st->inact_threshold;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ *val = st->inact_time_ms;
+ *val2 = MILLI;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_write_mag_value(struct adxl345_state *st,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ enum adxl345_activity_type type_act,
+ enum adxl345_activity_type type_inact,
+ int val, int val2)
+{
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ /* Scaling factor 62.5mg/LSB, i.e. ~16g corresponds to 0xff */
+ val = DIV_ROUND_CLOSEST(val * MICRO + val2, 62500);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return regmap_write(st->regmap,
+ adxl345_act_thresh_reg[type_act],
+ val);
+ case IIO_EV_DIR_FALLING:
+ return adxl345_set_inact_threshold(st, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ return adxl345_set_inact_time(st, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+ unsigned int tap_threshold;
+ int ret;
+
+ /*
+ * The event threshold LSB is fixed at 62.5 mg/LSB
+ * 0.0625 * 9.80665 = 0.612915625 m/s^2
+ */
+ if (info == IIO_EV_INFO_SCALE) {
+ *val = 0;
+ *val2 = 612915;
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return adxl345_read_mag_value(st, dir, info,
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
+ val, val2);
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ return adxl345_read_mag_value(st, dir, info,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ val, val2);
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = regmap_read(st->regmap, ADXL345_REG_THRESH_TAP,
+ &tap_threshold);
+ if (ret)
+ return ret;
+ *val = sign_extend32(tap_threshold, 7);
+ return IIO_VAL_INT;
+ case IIO_EV_INFO_TIMEOUT:
+ *val = st->tap_duration_us;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ *val = st->tap_window_us;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ *val = st->tap_latent_us;
+ *val2 = MICRO;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl345_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ if (info == IIO_EV_INFO_SCALE)
+ return -EINVAL;
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ ret = adxl345_write_mag_value(st, dir, info,
+ ADXL345_ACTIVITY,
+ ADXL345_INACTIVITY,
+ val, val2);
+ if (ret)
+ return ret;
+ break;
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ ret = adxl345_write_mag_value(st, dir, info,
+ ADXL345_ACTIVITY_AC,
+ ADXL345_INACTIVITY_AC,
+ val, val2);
+ if (ret)
+ return ret;
+ break;
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP,
+ min(val, 0xFF));
+ if (ret)
+ return ret;
+ break;
+ case IIO_EV_INFO_TIMEOUT:
+ ret = adxl345_set_tap_duration(st, val, val2);
+ if (ret)
+ return ret;
+ break;
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ ret = adxl345_set_tap_window(st, val, val2);
+ if (ret)
+ return ret;
+ break;
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ ret = adxl345_set_tap_latent(st, val, val2);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return adxl345_set_measure_en(st, true);
+}
+
+static int adxl345_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static int adxl345_set_watermark(struct iio_dev *indio_dev, unsigned int value)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+ const unsigned int fifo_mask = 0x1F, watermark_mask = 0x02;
+ int ret;
+
+ value = min(value, ADXL345_FIFO_SIZE - 1);
+
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_FIFO_CTL, fifo_mask, value);
+ if (ret)
+ return ret;
+
+ st->watermark = value;
+ return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE,
+ watermark_mask, ADXL345_INT_WATERMARK);
+}
+
+static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBBIAS:
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static void adxl345_powerdown(void *ptr)
+{
+ struct adxl345_state *st = ptr;
+
+ adxl345_set_measure_en(st, false);
+}
+
+static int adxl345_set_fifo(struct adxl345_state *st)
+{
+ unsigned int intio;
+ int ret;
+
+ /* FIFO should only be configured while in standby mode */
+ ret = adxl345_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_INT_MAP, &intio);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL345_REG_FIFO_CTL,
+ FIELD_PREP(ADXL345_FIFO_CTL_SAMPLES_MSK,
+ st->watermark) |
+ FIELD_PREP(ADXL345_FIFO_CTL_TRIGGER_MSK, intio) |
+ FIELD_PREP(ADXL345_FIFO_CTL_MODE_MSK,
+ st->fifo_mode));
+ if (ret)
+ return ret;
+
+ return adxl345_set_measure_en(st, true);
+}
+
+/**
+ * adxl345_get_samples() - Read number of FIFO entries.
+ * @st: The initialized state instance of this driver.
+ *
+ * The sensor does not support treating any axis individually, or exclude them
+ * from measuring.
+ *
+ * Return: negative error, or value.
+ */
+static int adxl345_get_samples(struct adxl345_state *st)
+{
+ unsigned int regval = 0;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_FIFO_STATUS, &regval);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(ADXL345_REG_FIFO_STATUS_MSK, regval);
+}
+
+/**
+ * adxl345_fifo_transfer() - Read samples number of elements.
+ * @st: The instance of the state object of this sensor.
+ * @samples: The number of lines in the FIFO referred to as fifo_entry.
+ *
+ * It is recommended that a multiple-byte read of all registers be performed to
+ * prevent a change in data between reads of sequential registers. That is to
+ * read out the data registers X0, X1, Y0, Y1, Z0, Z1, i.e. 6 bytes at once.
+ *
+ * Return: 0 or error value.
+ */
+static int adxl345_fifo_transfer(struct adxl345_state *st, int samples)
+{
+ int i, ret = 0;
+
+ for (i = 0; i < samples; i++) {
+ ret = regmap_bulk_read(st->regmap, ADXL345_REG_XYZ_BASE,
+ st->fifo_buf + (i * ADXL345_DIRS),
+ sizeof(st->fifo_buf[0]) * ADXL345_DIRS);
+ if (ret)
+ return ret;
+
+ /*
+ * To ensure that the FIFO has completely popped, there must be at least 5
+ * us between the end of reading the data registers, signified by the
+ * transition to register 0x38 from 0x37 or the CS pin going high, and the
+ * start of new reads of the FIFO or reading the FIFO_STATUS register. For
+ * SPI operation at 1.5 MHz or lower, the register addressing portion of the
+ * transmission is sufficient delay to ensure the FIFO has completely
+ * popped. It is necessary for SPI operation greater than 1.5 MHz to
+ * de-assert the CS pin to ensure a total of 5 us, which is at most 3.4 us
+ * at 5 MHz operation.
+ */
+ if (st->fifo_delay && samples > 1)
+ udelay(3);
+ }
+ return ret;
+}
+
+/**
+ * adxl345_fifo_reset() - Empty the FIFO in error condition.
+ * @st: The instance to the state object of the sensor.
+ *
+ * Read all elements of the FIFO. Reading the interrupt source register
+ * resets the sensor.
+ */
+static void adxl345_fifo_reset(struct adxl345_state *st)
+{
+ int regval;
+ int samples;
+
+ adxl345_set_measure_en(st, false);
+
+ samples = adxl345_get_samples(st);
+ if (samples > 0)
+ adxl345_fifo_transfer(st, samples);
+
+ regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &regval);
+
+ adxl345_set_measure_en(st, true);
+}
+
+static int adxl345_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+
+ st->fifo_mode = ADXL345_FIFO_STREAM;
+ return adxl345_set_fifo(st);
+}
+
+static int adxl345_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+ int ret;
+
+ st->fifo_mode = ADXL345_FIFO_BYPASS;
+ ret = adxl345_set_fifo(st);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
+}
+
+static const struct iio_buffer_setup_ops adxl345_buffer_ops = {
+ .postenable = adxl345_buffer_postenable,
+ .predisable = adxl345_buffer_predisable,
+};
+
+static int adxl345_fifo_push(struct iio_dev *indio_dev,
+ int samples)
+{
+ struct adxl345_state *st = iio_priv(indio_dev);
+ int i, ret;
+
+ if (samples <= 0)
+ return -EINVAL;
+
+ ret = adxl345_fifo_transfer(st, samples);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ADXL345_DIRS * samples; i += ADXL345_DIRS)
+ iio_push_to_buffers(indio_dev, &st->fifo_buf[i]);
+
+ return 0;
+}
+
+static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat,
+ enum iio_modifier act_dir,
+ enum iio_modifier tap_dir)
+{
+ s64 ts = iio_get_time_ns(indio_dev);
+ struct adxl345_state *st = iio_priv(indio_dev);
+ unsigned int regval;
+ int samples;
+ int ret = -ENOENT;
+
+ if (FIELD_GET(ADXL345_INT_SINGLE_TAP, int_stat)) {
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, tap_dir,
+ IIO_EV_TYPE_GESTURE,
+ IIO_EV_DIR_SINGLETAP),
+ ts);
+ if (ret)
+ return ret;
+ }
+
+ if (FIELD_GET(ADXL345_INT_DOUBLE_TAP, int_stat)) {
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, tap_dir,
+ IIO_EV_TYPE_GESTURE,
+ IIO_EV_DIR_DOUBLETAP),
+ ts);
+ if (ret)
+ return ret;
+ }
+
+ if (FIELD_GET(ADXL345_INT_ACTIVITY, int_stat)) {
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &regval);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ADXL345_REG_ACT_ACDC, regval)) {
+ /* AC coupled */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir,
+ IIO_EV_TYPE_MAG_ADAPTIVE,
+ IIO_EV_DIR_RISING),
+ ts);
+
+ } else {
+ /* DC coupled, relying on THRESH */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ ts);
+ }
+ if (ret)
+ return ret;
+ }
+
+ if (FIELD_GET(ADXL345_INT_INACTIVITY, int_stat)) {
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &regval);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ADXL345_REG_INACT_ACDC, regval)) {
+ /* AC coupled */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG_ADAPTIVE,
+ IIO_EV_DIR_FALLING),
+ ts);
+ } else {
+ /* DC coupled, relying on THRESH */
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_FALLING),
+ ts);
+ }
+ if (ret)
+ return ret;
+ }
+
+ if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) {
+ ret = iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_FALLING),
+ ts);
+ if (ret)
+ return ret;
+ }
+
+ if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) {
+ samples = adxl345_get_samples(st);
+ if (samples < 0)
+ return -EINVAL;
+
+ if (adxl345_fifo_push(indio_dev, samples) < 0)
+ return -EINVAL;
+
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/**
+ * adxl345_irq_handler() - Handle irqs of the ADXL345.
+ * @irq: The irq being handled.
+ * @p: The struct iio_device pointer for the device.
+ *
+ * Return: The interrupt was handled.
+ */
+static irqreturn_t adxl345_irq_handler(int irq, void *p)
+{
+ struct iio_dev *indio_dev = p;
+ struct adxl345_state *st = iio_priv(indio_dev);
+ unsigned int regval;
+ enum iio_modifier tap_dir = IIO_NO_MOD;
+ enum iio_modifier act_dir = IIO_NO_MOD;
+ u32 axis_ctrl;
+ int int_stat;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl);
+ if (ret)
+ return IRQ_NONE;
+
+ if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) ||
+ FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl)) {
+ ret = regmap_read(st->regmap, ADXL345_REG_ACT_TAP_STATUS, &regval);
+ if (ret)
+ return IRQ_NONE;
+
+ if (FIELD_GET(ADXL345_TAP_Z_EN, regval))
+ tap_dir = IIO_MOD_Z;
+ else if (FIELD_GET(ADXL345_TAP_Y_EN, regval))
+ tap_dir = IIO_MOD_Y;
+ else if (FIELD_GET(ADXL345_TAP_X_EN, regval))
+ tap_dir = IIO_MOD_X;
+
+ if (FIELD_GET(ADXL345_ACT_Z_EN, regval))
+ act_dir = IIO_MOD_Z;
+ else if (FIELD_GET(ADXL345_ACT_Y_EN, regval))
+ act_dir = IIO_MOD_Y;
+ else if (FIELD_GET(ADXL345_ACT_X_EN, regval))
+ act_dir = IIO_MOD_X;
+ }
+
+ if (regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &int_stat))
+ return IRQ_NONE;
+
+ if (adxl345_push_event(indio_dev, int_stat, act_dir, tap_dir))
+ goto err;
+
+ if (FIELD_GET(ADXL345_INT_OVERRUN, int_stat))
+ goto err;
+
+ return IRQ_HANDLED;
+
+err:
+ adxl345_fifo_reset(st);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_info adxl345_info = {
+ .read_raw = adxl345_read_raw,
+ .write_raw = adxl345_write_raw,
+ .read_avail = adxl345_read_avail,
+ .write_raw_get_fmt = adxl345_write_raw_get_fmt,
+ .read_event_config = adxl345_read_event_config,
+ .write_event_config = adxl345_write_event_config,
+ .read_event_value = adxl345_read_event_value,
+ .write_event_value = adxl345_write_event_value,
+ .debugfs_reg_access = &adxl345_reg_access,
+ .hwfifo_set_watermark = adxl345_set_watermark,
+};
+
+static int adxl345_get_int_line(struct device *dev, int *irq)
+{
+ *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
+ if (*irq > 0)
+ return ADXL345_INT1;
+
+ *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
+ if (*irq > 0)
+ return ADXL345_INT2;
+
+ return ADXL345_INT_NONE;
+}
+
+/**
+ * adxl345_core_probe() - Probe and setup for the accelerometer.
+ * @dev: Driver model representation of the device
+ * @regmap: Regmap instance for the device
+ * @fifo_delay_default: Using FIFO with SPI needs delay
+ * @setup: Setup routine to be executed right before the standard device
+ * setup
+ *
+ * For SPI operation greater than 1.6 MHz, it is necessary to deassert the CS
+ * pin to ensure a total delay of 5 us; otherwise, the delay is not sufficient.
+ * The total delay necessary for 5 MHz operation is at most 3.4 us. This is not
+ * a concern when using I2C mode because the communication rate is low enough
+ * to ensure a sufficient delay between FIFO reads.
+ * Ref: "Retrieving Data from FIFO", p. 21 of 36, Data Sheet ADXL345 Rev. G
+ *
+ * Return: 0 on success, negative errno on error
+ */
+int adxl345_core_probe(struct device *dev, struct regmap *regmap,
+ bool fifo_delay_default,
+ int (*setup)(struct device*, struct regmap*))
+{
+ struct adxl345_state *st;
+ struct iio_dev *indio_dev;
+ u32 regval;
+ u8 intio = ADXL345_INT1;
+ unsigned int data_format_mask = (ADXL345_DATA_FORMAT_RANGE |
+ ADXL345_DATA_FORMAT_JUSTIFY |
+ ADXL345_DATA_FORMAT_FULL_RES |
+ ADXL345_DATA_FORMAT_SELF_TEST);
+ unsigned int tap_threshold;
+ int irq;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->regmap = regmap;
+ st->info = device_get_match_data(dev);
+ if (!st->info)
+ return -ENODEV;
+ st->fifo_delay = fifo_delay_default;
+
+ /* Init with reasonable values */
+ tap_threshold = 48; /* 48 [0x30] -> ~3g */
+ st->tap_duration_us = 16; /* 16 [0x10] -> .010 */
+ st->tap_window_us = 64; /* 64 [0x40] -> .080 */
+ st->tap_latent_us = 16; /* 16 [0x10] -> .020 */
+
+ indio_dev->name = st->info->name;
+ indio_dev->info = &adxl345_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = adxl345_channels;
+ indio_dev->num_channels = ARRAY_SIZE(adxl345_channels);
+ indio_dev->available_scan_masks = adxl345_scan_masks;
+
+ /*
+ * Using I2C at 100kHz would limit the maximum ODR to 200Hz, operation
+ * at an output rate above the recommended maximum may result in
+ * undesired behavior.
+ */
+ ret = adxl345_set_odr(st, ADXL345_ODR_200HZ);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_range(st, ADXL345_16G_RANGE);
+ if (ret)
+ return ret;
+
+ /* Reset interrupts at start up */
+ ret = regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00);
+ if (ret)
+ return ret;
+
+ if (setup) {
+ /* Perform optional initial bus specific configuration */
+ ret = setup(dev, st->regmap);
+ if (ret)
+ return ret;
+
+ /* Enable full-resolution mode */
+ ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT,
+ data_format_mask,
+ ADXL345_DATA_FORMAT_FULL_RES);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set data range\n");
+
+ } else {
+ /* Enable full-resolution mode (init all data_format bits) */
+ ret = regmap_write(st->regmap, ADXL345_REG_DATA_FORMAT,
+ ADXL345_DATA_FORMAT_FULL_RES);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set data range\n");
+ }
+
+ ret = regmap_read(st->regmap, ADXL345_REG_DEVID, &regval);
+ if (ret)
+ return dev_err_probe(dev, ret, "Error reading device ID\n");
+
+ if (regval != ADXL345_DEVID)
+ return dev_err_probe(dev, -ENODEV, "Invalid device ID: %x, expected %x\n",
+ regval, ADXL345_DEVID);
+
+ /* Enable measurement mode */
+ ret = adxl345_set_measure_en(st, true);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable measurement mode\n");
+
+ ret = devm_add_action_or_reset(dev, adxl345_powerdown, st);
+ if (ret)
+ return ret;
+
+ intio = adxl345_get_int_line(dev, &irq);
+ if (intio != ADXL345_INT_NONE) {
+ /*
+ * In the INT map register, bits set to 0 route their
+ * corresponding interrupts to the INT1 pin, while bits set to 1
+ * route them to the INT2 pin. The intio should handle this
+ * mapping accordingly.
+ */
+ ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_MAP,
+ U8_MAX, intio);
+ if (ret)
+ return ret;
+
+ /*
+ * Initialized with sensible default values to streamline
+ * sensor operation. These defaults are partly derived from
+ * the previous input driver for the ADXL345 and partly
+ * based on the recommendations provided in the datasheet.
+ */
+ ret = regmap_write(st->regmap, ADXL345_REG_ACT_INACT_CTRL, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_ACT, 6);
+ if (ret)
+ return ret;
+
+ ret = adxl345_set_inact_threshold(st, 4);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, tap_threshold);
+ if (ret)
+ return ret;
+
+ /* FIFO_STREAM mode is going to be activated later */
+ ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &adxl345_buffer_ops);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ &adxl345_irq_handler,
+ IRQF_SHARED | IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+ } else {
+ ret = regmap_write(st->regmap, ADXL345_REG_FIFO_CTL,
+ FIELD_PREP(ADXL345_FIFO_CTL_MODE_MSK,
+ ADXL345_FIFO_BYPASS));
+ if (ret)
+ return ret;
+ }
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(adxl345_core_probe, "IIO_ADXL345");
+
+MODULE_AUTHOR("Eva Rachel Retuya <eraretuya@gmail.com>");
+MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer core driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/adxl345_i2c.c b/drivers/iio/accel/adxl345_i2c.c
new file mode 100644
index 000000000..511fb6104
--- /dev/null
+++ b/drivers/iio/accel/adxl345_i2c.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ADXL345 3-Axis Digital Accelerometer I2C driver
+ *
+ * Copyright (c) 2017 Eva Rachel Retuya <eraretuya@gmail.com>
+ *
+ * 7-bit I2C slave address: 0x1D (ALT ADDRESS pin tied to VDDIO) or
+ * 0x53 (ALT ADDRESS pin grounded)
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "adxl345.h"
+
+static const struct regmap_config adxl345_i2c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_reg = adxl345_is_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int adxl345_i2c_probe(struct i2c_client *client)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_i2c(client, &adxl345_i2c_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(regmap), "Error initializing regmap\n");
+
+ return adxl345_core_probe(&client->dev, regmap, false, NULL);
+}
+
+static const struct adxl345_chip_info adxl345_i2c_info = {
+ .name = "adxl345",
+ .uscale = ADXL345_USCALE,
+};
+
+static const struct adxl345_chip_info adxl375_i2c_info = {
+ .name = "adxl375",
+ .uscale = ADXL375_USCALE,
+};
+
+static const struct i2c_device_id adxl345_i2c_id[] = {
+ { .name = "adxl345", .driver_data = (kernel_ulong_t)&adxl345_i2c_info },
+ { .name = "adxl375", .driver_data = (kernel_ulong_t)&adxl375_i2c_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adxl345_i2c_id);
+
+static const struct of_device_id adxl345_of_match[] = {
+ { .compatible = "adi,adxl345", .data = &adxl345_i2c_info },
+ { .compatible = "adi,adxl375", .data = &adxl375_i2c_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adxl345_of_match);
+
+static const struct acpi_device_id adxl345_acpi_match[] = {
+ { "ADS0345", (kernel_ulong_t)&adxl345_i2c_info },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, adxl345_acpi_match);
+
+static struct i2c_driver adxl345_i2c_driver = {
+ .driver = {
+ .name = "adxl345_i2c",
+ .of_match_table = adxl345_of_match,
+ .acpi_match_table = adxl345_acpi_match,
+ },
+ .probe = adxl345_i2c_probe,
+ .id_table = adxl345_i2c_id,
+};
+module_i2c_driver(adxl345_i2c_driver);
+
+MODULE_AUTHOR("Eva Rachel Retuya <eraretuya@gmail.com>");
+MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer I2C driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_ADXL345");
diff --git a/drivers/iio/accel/adxl345_spi.c b/drivers/iio/accel/adxl345_spi.c
new file mode 100644
index 000000000..4fa1b04f7
--- /dev/null
+++ b/drivers/iio/accel/adxl345_spi.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ADXL345 3-Axis Digital Accelerometer SPI driver
+ *
+ * Copyright (c) 2017 Eva Rachel Retuya <eraretuya@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "adxl345.h"
+
+#define ADXL345_MAX_SPI_FREQ_HZ 5000000
+#define ADXL345_MAX_FREQ_NO_FIFO_DELAY 1500000
+
+static const struct regmap_config adxl345_spi_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ /* Setting bits 7 and 6 enables multiple-byte read */
+ .read_flag_mask = BIT(7) | BIT(6),
+ .volatile_reg = adxl345_is_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int adxl345_spi_setup(struct device *dev, struct regmap *regmap)
+{
+ return regmap_write(regmap, ADXL345_REG_DATA_FORMAT, ADXL345_DATA_FORMAT_SPI_3WIRE);
+}
+
+static int adxl345_spi_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+ bool needs_delay;
+
+ /* Bail out if max_speed_hz exceeds 5 MHz */
+ if (spi->max_speed_hz > ADXL345_MAX_SPI_FREQ_HZ)
+ return dev_err_probe(&spi->dev, -EINVAL, "SPI CLK, %d Hz exceeds 5 MHz\n",
+ spi->max_speed_hz);
+
+ regmap = devm_regmap_init_spi(spi, &adxl345_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&spi->dev, PTR_ERR(regmap), "Error initializing regmap\n");
+
+ needs_delay = spi->max_speed_hz > ADXL345_MAX_FREQ_NO_FIFO_DELAY;
+ if (spi->mode & SPI_3WIRE)
+ return adxl345_core_probe(&spi->dev, regmap, needs_delay, adxl345_spi_setup);
+ else
+ return adxl345_core_probe(&spi->dev, regmap, needs_delay, NULL);
+}
+
+static const struct adxl345_chip_info adxl345_spi_info = {
+ .name = "adxl345",
+ .uscale = ADXL345_USCALE,
+};
+
+static const struct adxl345_chip_info adxl375_spi_info = {
+ .name = "adxl375",
+ .uscale = ADXL375_USCALE,
+};
+
+static const struct spi_device_id adxl345_spi_id[] = {
+ { .name = "adxl345", .driver_data = (kernel_ulong_t)&adxl345_spi_info },
+ { .name = "adxl375", .driver_data = (kernel_ulong_t)&adxl375_spi_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adxl345_spi_id);
+
+static const struct of_device_id adxl345_of_match[] = {
+ { .compatible = "adi,adxl345", .data = &adxl345_spi_info },
+ { .compatible = "adi,adxl375", .data = &adxl375_spi_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adxl345_of_match);
+
+static const struct acpi_device_id adxl345_acpi_match[] = {
+ { "ADS0345", (kernel_ulong_t)&adxl345_spi_info },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, adxl345_acpi_match);
+
+static struct spi_driver adxl345_spi_driver = {
+ .driver = {
+ .name = "adxl345_spi",
+ .of_match_table = adxl345_of_match,
+ .acpi_match_table = adxl345_acpi_match,
+ },
+ .probe = adxl345_spi_probe,
+ .id_table = adxl345_spi_id,
+};
+module_spi_driver(adxl345_spi_driver);
+
+MODULE_AUTHOR("Eva Rachel Retuya <eraretuya@gmail.com>");
+MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer SPI driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_ADXL345");
diff --git a/drivers/iio/accel/adxl355.h b/drivers/iio/accel/adxl355.h
new file mode 100644
index 000000000..061e66dc7
--- /dev/null
+++ b/drivers/iio/accel/adxl355.h
@@ -0,0 +1,39 @@
+/* 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>
+
+enum adxl355_device_type {
+ ADXL355,
+ ADXL359,
+};
+
+struct adxl355_fractional_type {
+ int integer;
+ int decimal;
+};
+
+struct device;
+
+struct adxl355_chip_info {
+ const char *name;
+ u8 part_id;
+ struct adxl355_fractional_type accel_scale;
+ struct adxl355_fractional_type temp_offset;
+};
+
+extern const struct regmap_access_table adxl355_readable_regs_tbl;
+extern const struct regmap_access_table adxl355_writeable_regs_tbl;
+extern const struct adxl355_chip_info adxl35x_chip_info[];
+
+int adxl355_core_probe(struct device *dev, struct regmap *regmap,
+ const struct adxl355_chip_info *chip_info);
+
+#endif /* _ADXL355_H_ */
diff --git a/drivers/iio/accel/adxl355_core.c b/drivers/iio/accel/adxl355_core.c
new file mode 100644
index 000000000..080c9f6f9
--- /dev/null
+++ b/drivers/iio/accel/adxl355_core.c
@@ -0,0 +1,833 @@
+// 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/property.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <linux/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_BASE_ADDR_SHADOW_REG 0x50
+#define ADXL355_SHADOW_REG_COUNT 5
+
+#define ADXL355_DEVID_AD_VAL 0xAD
+#define ADXL355_DEVID_MST_VAL 0x1D
+#define ADXL355_PARTID_VAL 0xED
+#define ADXL359_PARTID_VAL 0xE9
+#define ADXL355_RESET_CODE 0x52
+
+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_NS_GPL(adxl355_readable_regs_tbl, "IIO_ADXL355");
+
+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_NS_GPL(adxl355_writeable_regs_tbl, "IIO_ADXL355");
+
+const struct adxl355_chip_info adxl35x_chip_info[] = {
+ [ADXL355] = {
+ .name = "adxl355",
+ .part_id = ADXL355_PARTID_VAL,
+ /*
+ * At +/- 2g with 20-bit resolution, scale is given in datasheet
+ * as 3.9ug/LSB = 0.0000039 * 9.80665 = 0.00003824593 m/s^2.
+ */
+ .accel_scale = {
+ .integer = 0,
+ .decimal = 38245,
+ },
+ /*
+ * 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.
+ */
+ .temp_offset = {
+ .integer = -2111,
+ .decimal = 250000,
+ },
+ },
+ [ADXL359] = {
+ .name = "adxl359",
+ .part_id = ADXL359_PARTID_VAL,
+ /*
+ * At +/- 10g with 20-bit resolution, scale is given in datasheet
+ * as 19.5ug/LSB = 0.0000195 * 9.80665 = 0.0.00019122967 m/s^2.
+ */
+ .accel_scale = {
+ .integer = 0,
+ .decimal = 191229,
+ },
+ /*
+ * The datasheet defines an intercept of 1852 LSB at 25 degC
+ * and a slope of -9.05 LSB/C. The following formula can be used
+ * to find the temperature:
+ * Temp = ((RAW - 1852)/(-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 -2079.25.
+ */
+ .temp_offset = {
+ .integer = -2079,
+ .decimal = 250000,
+ },
+ },
+};
+EXPORT_SYMBOL_NS_GPL(adxl35x_chip_info, "IIO_ADXL355");
+
+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 {
+ const struct adxl355_chip_info *chip_info;
+ 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];
+ aligned_s64 ts;
+ } buffer;
+ } __aligned(IIO_DMA_MINALIGN);
+};
+
+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 retries = 5; /* the number is chosen based on empirical reasons */
+ int ret;
+ u8 *shadow_regs __free(kfree) = kzalloc(ADXL355_SHADOW_REG_COUNT, GFP_KERNEL);
+
+ if (!shadow_regs)
+ return -ENOMEM;
+
+ ret = regmap_read(data->regmap, ADXL355_DEVID_AD_REG, &regval);
+ 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, &regval);
+ 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, &regval);
+ if (ret)
+ return ret;
+
+ if (regval != ADXL355_PARTID_VAL)
+ dev_warn(data->dev, "Invalid DEV ID 0x%02x\n", regval);
+
+ /* Read shadow registers to be compared after reset */
+ ret = regmap_bulk_read(data->regmap,
+ ADXL355_BASE_ADDR_SHADOW_REG,
+ shadow_regs, ADXL355_SHADOW_REG_COUNT);
+ if (ret)
+ return ret;
+
+ do {
+ if (--retries == 0)
+ return dev_err_probe(data->dev, -EIO, "Shadow registers mismatch\n");
+
+ /*
+ * 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;
+
+ /* Wait at least 5ms after software reset */
+ fsleep(5 * USEC_PER_MSEC);
+
+ /* Read shadow registers for comparison */
+ ret = regmap_bulk_read(data->regmap,
+ ADXL355_BASE_ADDR_SHADOW_REG,
+ data->buffer.buf,
+ ADXL355_SHADOW_REG_COUNT);
+ if (ret)
+ return ret;
+ } while (memcmp(shadow_regs, data->buffer.buf,
+ ADXL355_SHADOW_REG_COUNT));
+
+ 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) {
+ case IIO_TEMP:
+ /*
+ * Temperature scale is -110.497238.
+ * See the detailed explanation in adxl35x_chip_info
+ * definition above.
+ */
+ *val = -110;
+ *val2 = 497238;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ *val = data->chip_info->accel_scale.integer;
+ *val2 = data->chip_info->accel_scale.decimal;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ *val = data->chip_info->temp_offset.integer;
+ *val2 = data->chip_info->temp_offset.decimal;
+ 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
+ * acceleration 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_ts(indio_dev, &data->buffer,
+ sizeof(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 = 'u',
+ .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_NO_THREAD, "adxl355_irq", data->dready_trig);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_trigger_register(data->dev, data->dready_trig);
+ if (ret)
+ return dev_err_probe(data->dev, ret, "iio trigger register failed\n");
+
+ indio_dev->trig = iio_trigger_get(data->dready_trig);
+
+ return 0;
+}
+
+int adxl355_core_probe(struct device *dev, struct regmap *regmap,
+ const struct adxl355_chip_info *chip_info)
+{
+ 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;
+ data->chip_info = chip_info;
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret)
+ return ret;
+
+ indio_dev->name = chip_info->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)
+ return dev_err_probe(dev, ret, "ADXL355 setup failed\n");
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &adxl355_trigger_handler, NULL);
+ if (ret)
+ return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n");
+
+ irq = fwnode_irq_get_byname(dev_fwnode(dev), "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_NS_GPL(adxl355_core_probe, "IIO_ADXL355");
+
+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 000000000..e533a7aab
--- /dev/null
+++ b/drivers/iio/accel/adxl355_i2c.c
@@ -0,0 +1,66 @@
+// 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/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;
+ struct device *dev = &client->dev;
+ const struct adxl355_chip_info *chip_data;
+
+ chip_data = i2c_get_match_data(client);
+ if (!chip_data)
+ return -ENODEV;
+
+ regmap = devm_regmap_init_i2c(client, &adxl355_i2c_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing i2c regmap\n");
+
+ return adxl355_core_probe(dev, regmap, chip_data);
+}
+
+static const struct i2c_device_id adxl355_i2c_id[] = {
+ { .name = "adxl355", .driver_data = (kernel_ulong_t)&adxl35x_chip_info[ADXL355] },
+ { .name = "adxl359", .driver_data = (kernel_ulong_t)&adxl35x_chip_info[ADXL359] },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adxl355_i2c_id);
+
+static const struct of_device_id adxl355_of_match[] = {
+ { .compatible = "adi,adxl355", .data = &adxl35x_chip_info[ADXL355] },
+ { .compatible = "adi,adxl359", .data = &adxl35x_chip_info[ADXL359] },
+ { }
+};
+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 = 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");
+MODULE_IMPORT_NS("IIO_ADXL355");
diff --git a/drivers/iio/accel/adxl355_spi.c b/drivers/iio/accel/adxl355_spi.c
new file mode 100644
index 000000000..d2209ca8c
--- /dev/null
+++ b/drivers/iio/accel/adxl355_spi.c
@@ -0,0 +1,69 @@
+// 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/regmap.h>
+#include <linux/spi/spi.h>
+#include <linux/property.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 adxl355_chip_info *chip_data;
+ struct device *dev = &spi->dev;
+ struct regmap *regmap;
+
+ chip_data = spi_get_device_match_data(spi);
+ if (!chip_data)
+ return -EINVAL;
+
+ regmap = devm_regmap_init_spi(spi, &adxl355_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing spi regmap\n");
+
+ return adxl355_core_probe(dev, regmap, chip_data);
+}
+
+static const struct spi_device_id adxl355_spi_id[] = {
+ { .name = "adxl355", .driver_data = (kernel_ulong_t)&adxl35x_chip_info[ADXL355] },
+ { .name = "adxl359", .driver_data = (kernel_ulong_t)&adxl35x_chip_info[ADXL359] },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adxl355_spi_id);
+
+static const struct of_device_id adxl355_of_match[] = {
+ { .compatible = "adi,adxl355", .data = &adxl35x_chip_info[ADXL355] },
+ { .compatible = "adi,adxl359", .data = &adxl35x_chip_info[ADXL359] },
+ { }
+};
+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");
+MODULE_IMPORT_NS("IIO_ADXL355");
diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c
new file mode 100644
index 000000000..4ff1c7a09
--- /dev/null
+++ b/drivers/iio/accel/adxl367.c
@@ -0,0 +1,1497 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Analog Devices, Inc.
+ * Author: Cosmin Tanislav <cosmin.tanislav@analog.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.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>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/unaligned.h>
+
+#include "adxl367.h"
+
+#define ADXL367_REG_DEVID 0x00
+#define ADXL367_DEVID_AD 0xAD
+
+#define ADXL367_REG_STATUS 0x0B
+#define ADXL367_STATUS_INACT_MASK BIT(5)
+#define ADXL367_STATUS_ACT_MASK BIT(4)
+#define ADXL367_STATUS_FIFO_FULL_MASK BIT(2)
+
+#define ADXL367_FIFO_ENT_H_MASK GENMASK(1, 0)
+
+#define ADXL367_REG_X_DATA_H 0x0E
+#define ADXL367_REG_Y_DATA_H 0x10
+#define ADXL367_REG_Z_DATA_H 0x12
+#define ADXL367_REG_TEMP_DATA_H 0x14
+#define ADXL367_REG_EX_ADC_DATA_H 0x16
+#define ADXL367_DATA_MASK GENMASK(15, 2)
+
+#define ADXL367_TEMP_25C 165
+#define ADXL367_TEMP_PER_C 54
+
+#define ADXL367_VOLTAGE_OFFSET 8192
+#define ADXL367_VOLTAGE_MAX_MV 1000
+#define ADXL367_VOLTAGE_MAX_RAW GENMASK(13, 0)
+
+#define ADXL367_REG_RESET 0x1F
+#define ADXL367_RESET_CODE 0x52
+
+#define ADXL367_REG_THRESH_ACT_H 0x20
+#define ADXL367_REG_THRESH_INACT_H 0x23
+#define ADXL367_THRESH_MAX GENMASK(12, 0)
+#define ADXL367_THRESH_VAL_H_MASK GENMASK(12, 6)
+#define ADXL367_THRESH_H_MASK GENMASK(6, 0)
+#define ADXL367_THRESH_VAL_L_MASK GENMASK(5, 0)
+#define ADXL367_THRESH_L_MASK GENMASK(7, 2)
+
+#define ADXL367_REG_TIME_ACT 0x22
+#define ADXL367_REG_TIME_INACT_H 0x25
+#define ADXL367_TIME_ACT_MAX GENMASK(7, 0)
+#define ADXL367_TIME_INACT_MAX GENMASK(15, 0)
+#define ADXL367_TIME_INACT_VAL_H_MASK GENMASK(15, 8)
+#define ADXL367_TIME_INACT_H_MASK GENMASK(7, 0)
+#define ADXL367_TIME_INACT_VAL_L_MASK GENMASK(7, 0)
+#define ADXL367_TIME_INACT_L_MASK GENMASK(7, 0)
+
+#define ADXL367_REG_ACT_INACT_CTL 0x27
+#define ADXL367_ACT_EN_MASK GENMASK(1, 0)
+#define ADXL367_ACT_LINKLOOP_MASK GENMASK(5, 4)
+
+#define ADXL367_REG_FIFO_CTL 0x28
+#define ADXL367_FIFO_CTL_FORMAT_MASK GENMASK(6, 3)
+#define ADXL367_FIFO_CTL_MODE_MASK GENMASK(1, 0)
+
+#define ADXL367_REG_FIFO_SAMPLES 0x29
+#define ADXL367_FIFO_SIZE 512
+#define ADXL367_FIFO_MAX_WATERMARK 511
+
+#define ADXL367_SAMPLES_VAL_H_MASK BIT(8)
+#define ADXL367_SAMPLES_H_MASK BIT(2)
+#define ADXL367_SAMPLES_VAL_L_MASK GENMASK(7, 0)
+#define ADXL367_SAMPLES_L_MASK GENMASK(7, 0)
+
+#define ADXL367_REG_INT1_MAP 0x2A
+#define ADXL367_INT_INACT_MASK BIT(5)
+#define ADXL367_INT_ACT_MASK BIT(4)
+#define ADXL367_INT_FIFO_WATERMARK_MASK BIT(2)
+
+#define ADXL367_REG_FILTER_CTL 0x2C
+#define ADXL367_FILTER_CTL_RANGE_MASK GENMASK(7, 6)
+#define ADXL367_2G_RANGE_1G 4095
+#define ADXL367_2G_RANGE_100MG 409
+#define ADXL367_FILTER_CTL_ODR_MASK GENMASK(2, 0)
+
+#define ADXL367_REG_POWER_CTL 0x2D
+#define ADXL367_POWER_CTL_MODE_MASK GENMASK(1, 0)
+
+#define ADXL367_REG_ADC_CTL 0x3C
+#define ADXL367_REG_TEMP_CTL 0x3D
+#define ADXL367_ADC_EN_MASK BIT(0)
+
+enum adxl367_range {
+ ADXL367_2G_RANGE,
+ ADXL367_4G_RANGE,
+ ADXL367_8G_RANGE,
+};
+
+enum adxl367_fifo_mode {
+ ADXL367_FIFO_MODE_DISABLED = 0b00,
+ ADXL367_FIFO_MODE_STREAM = 0b10,
+};
+
+enum adxl367_fifo_format {
+ ADXL367_FIFO_FORMAT_XYZ,
+ ADXL367_FIFO_FORMAT_X,
+ ADXL367_FIFO_FORMAT_Y,
+ ADXL367_FIFO_FORMAT_Z,
+ ADXL367_FIFO_FORMAT_XYZT,
+ ADXL367_FIFO_FORMAT_XT,
+ ADXL367_FIFO_FORMAT_YT,
+ ADXL367_FIFO_FORMAT_ZT,
+ ADXL367_FIFO_FORMAT_XYZA,
+ ADXL367_FIFO_FORMAT_XA,
+ ADXL367_FIFO_FORMAT_YA,
+ ADXL367_FIFO_FORMAT_ZA,
+};
+
+enum adxl367_op_mode {
+ ADXL367_OP_STANDBY = 0b00,
+ ADXL367_OP_MEASURE = 0b10,
+};
+
+enum adxl367_act_proc_mode {
+ ADXL367_LOOPED = 0b11,
+};
+
+enum adxl367_act_en_mode {
+ ADXL367_ACT_DISABLED = 0b00,
+ ADCL367_ACT_REF_ENABLED = 0b11,
+};
+
+enum adxl367_activity_type {
+ ADXL367_ACTIVITY,
+ ADXL367_INACTIVITY,
+};
+
+enum adxl367_odr {
+ ADXL367_ODR_12P5HZ,
+ ADXL367_ODR_25HZ,
+ ADXL367_ODR_50HZ,
+ ADXL367_ODR_100HZ,
+ ADXL367_ODR_200HZ,
+ ADXL367_ODR_400HZ,
+};
+
+struct adxl367_state {
+ const struct adxl367_ops *ops;
+ void *context;
+
+ struct device *dev;
+ struct regmap *regmap;
+
+ /*
+ * Synchronize access to members of driver state, and ensure atomicity
+ * of consecutive regmap operations.
+ */
+ struct mutex lock;
+
+ enum adxl367_odr odr;
+ enum adxl367_range range;
+
+ unsigned int act_threshold;
+ unsigned int act_time_ms;
+ unsigned int inact_threshold;
+ unsigned int inact_time_ms;
+
+ unsigned int fifo_set_size;
+ unsigned int fifo_watermark;
+
+ __be16 fifo_buf[ADXL367_FIFO_SIZE] __aligned(IIO_DMA_MINALIGN);
+ __be16 sample_buf;
+ u8 act_threshold_buf[2];
+ u8 inact_time_buf[2];
+ u8 status_buf[3];
+};
+
+static const unsigned int adxl367_threshold_h_reg_tbl[] = {
+ [ADXL367_ACTIVITY] = ADXL367_REG_THRESH_ACT_H,
+ [ADXL367_INACTIVITY] = ADXL367_REG_THRESH_INACT_H,
+};
+
+static const unsigned int adxl367_act_en_shift_tbl[] = {
+ [ADXL367_ACTIVITY] = 0,
+ [ADXL367_INACTIVITY] = 2,
+};
+
+static const unsigned int adxl367_act_int_mask_tbl[] = {
+ [ADXL367_ACTIVITY] = ADXL367_INT_ACT_MASK,
+ [ADXL367_INACTIVITY] = ADXL367_INT_INACT_MASK,
+};
+
+static const int adxl367_samp_freq_tbl[][2] = {
+ [ADXL367_ODR_12P5HZ] = {12, 500000},
+ [ADXL367_ODR_25HZ] = {25, 0},
+ [ADXL367_ODR_50HZ] = {50, 0},
+ [ADXL367_ODR_100HZ] = {100, 0},
+ [ADXL367_ODR_200HZ] = {200, 0},
+ [ADXL367_ODR_400HZ] = {400, 0},
+};
+
+/* (g * 2) * 9.80665 * 1000000 / (2^14 - 1) */
+static const int adxl367_range_scale_tbl[][2] = {
+ [ADXL367_2G_RANGE] = {0, 2394347},
+ [ADXL367_4G_RANGE] = {0, 4788695},
+ [ADXL367_8G_RANGE] = {0, 9577391},
+};
+
+static const int adxl367_range_scale_factor_tbl[] = {
+ [ADXL367_2G_RANGE] = 1,
+ [ADXL367_4G_RANGE] = 2,
+ [ADXL367_8G_RANGE] = 4,
+};
+
+enum {
+ ADXL367_X_CHANNEL_INDEX,
+ ADXL367_Y_CHANNEL_INDEX,
+ ADXL367_Z_CHANNEL_INDEX,
+ ADXL367_TEMP_CHANNEL_INDEX,
+ ADXL367_EX_ADC_CHANNEL_INDEX
+};
+
+#define ADXL367_X_CHANNEL_MASK BIT(ADXL367_X_CHANNEL_INDEX)
+#define ADXL367_Y_CHANNEL_MASK BIT(ADXL367_Y_CHANNEL_INDEX)
+#define ADXL367_Z_CHANNEL_MASK BIT(ADXL367_Z_CHANNEL_INDEX)
+#define ADXL367_TEMP_CHANNEL_MASK BIT(ADXL367_TEMP_CHANNEL_INDEX)
+#define ADXL367_EX_ADC_CHANNEL_MASK BIT(ADXL367_EX_ADC_CHANNEL_INDEX)
+
+static const enum adxl367_fifo_format adxl367_fifo_formats[] = {
+ ADXL367_FIFO_FORMAT_X,
+ ADXL367_FIFO_FORMAT_Y,
+ ADXL367_FIFO_FORMAT_Z,
+ ADXL367_FIFO_FORMAT_XT,
+ ADXL367_FIFO_FORMAT_YT,
+ ADXL367_FIFO_FORMAT_ZT,
+ ADXL367_FIFO_FORMAT_XA,
+ ADXL367_FIFO_FORMAT_YA,
+ ADXL367_FIFO_FORMAT_ZA,
+ ADXL367_FIFO_FORMAT_XYZ,
+ ADXL367_FIFO_FORMAT_XYZT,
+ ADXL367_FIFO_FORMAT_XYZA,
+};
+
+static const unsigned long adxl367_channel_masks[] = {
+ ADXL367_X_CHANNEL_MASK,
+ ADXL367_Y_CHANNEL_MASK,
+ ADXL367_Z_CHANNEL_MASK,
+ ADXL367_X_CHANNEL_MASK | ADXL367_TEMP_CHANNEL_MASK,
+ ADXL367_Y_CHANNEL_MASK | ADXL367_TEMP_CHANNEL_MASK,
+ ADXL367_Z_CHANNEL_MASK | ADXL367_TEMP_CHANNEL_MASK,
+ ADXL367_X_CHANNEL_MASK | ADXL367_EX_ADC_CHANNEL_MASK,
+ ADXL367_Y_CHANNEL_MASK | ADXL367_EX_ADC_CHANNEL_MASK,
+ ADXL367_Z_CHANNEL_MASK | ADXL367_EX_ADC_CHANNEL_MASK,
+ ADXL367_X_CHANNEL_MASK | ADXL367_Y_CHANNEL_MASK | ADXL367_Z_CHANNEL_MASK,
+ ADXL367_X_CHANNEL_MASK | ADXL367_Y_CHANNEL_MASK | ADXL367_Z_CHANNEL_MASK |
+ ADXL367_TEMP_CHANNEL_MASK,
+ ADXL367_X_CHANNEL_MASK | ADXL367_Y_CHANNEL_MASK | ADXL367_Z_CHANNEL_MASK |
+ ADXL367_EX_ADC_CHANNEL_MASK,
+ 0,
+};
+
+static int adxl367_set_measure_en(struct adxl367_state *st, bool en)
+{
+ enum adxl367_op_mode op_mode = en ? ADXL367_OP_MEASURE
+ : ADXL367_OP_STANDBY;
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL367_REG_POWER_CTL,
+ ADXL367_POWER_CTL_MODE_MASK,
+ FIELD_PREP(ADXL367_POWER_CTL_MODE_MASK,
+ op_mode));
+ if (ret)
+ return ret;
+
+ /*
+ * Wait for acceleration output to settle after entering
+ * measure mode.
+ */
+ if (en)
+ msleep(100);
+
+ return 0;
+}
+
+static void adxl367_scale_act_thresholds(struct adxl367_state *st,
+ enum adxl367_range old_range,
+ enum adxl367_range new_range)
+{
+ st->act_threshold = st->act_threshold
+ * adxl367_range_scale_factor_tbl[old_range]
+ / adxl367_range_scale_factor_tbl[new_range];
+ st->inact_threshold = st->inact_threshold
+ * adxl367_range_scale_factor_tbl[old_range]
+ / adxl367_range_scale_factor_tbl[new_range];
+}
+
+static int _adxl367_set_act_threshold(struct adxl367_state *st,
+ enum adxl367_activity_type act,
+ unsigned int threshold)
+{
+ u8 reg = adxl367_threshold_h_reg_tbl[act];
+ int ret;
+
+ if (threshold > ADXL367_THRESH_MAX)
+ return -EINVAL;
+
+ st->act_threshold_buf[0] = FIELD_PREP(ADXL367_THRESH_H_MASK,
+ FIELD_GET(ADXL367_THRESH_VAL_H_MASK,
+ threshold));
+ st->act_threshold_buf[1] = FIELD_PREP(ADXL367_THRESH_L_MASK,
+ FIELD_GET(ADXL367_THRESH_VAL_L_MASK,
+ threshold));
+
+ ret = regmap_bulk_write(st->regmap, reg, st->act_threshold_buf,
+ sizeof(st->act_threshold_buf));
+ if (ret)
+ return ret;
+
+ if (act == ADXL367_ACTIVITY)
+ st->act_threshold = threshold;
+ else
+ st->inact_threshold = threshold;
+
+ return 0;
+}
+
+static int adxl367_set_act_threshold(struct adxl367_state *st,
+ enum adxl367_activity_type act,
+ unsigned int threshold)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl367_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = _adxl367_set_act_threshold(st, act, threshold);
+ if (ret)
+ return ret;
+
+ return adxl367_set_measure_en(st, true);
+}
+
+static int adxl367_set_act_proc_mode(struct adxl367_state *st,
+ enum adxl367_act_proc_mode mode)
+{
+ return regmap_update_bits(st->regmap, ADXL367_REG_ACT_INACT_CTL,
+ ADXL367_ACT_LINKLOOP_MASK,
+ FIELD_PREP(ADXL367_ACT_LINKLOOP_MASK,
+ mode));
+}
+
+static int adxl367_set_act_interrupt_en(struct adxl367_state *st,
+ enum adxl367_activity_type act,
+ bool en)
+{
+ unsigned int mask = adxl367_act_int_mask_tbl[act];
+
+ return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP,
+ mask, en ? mask : 0);
+}
+
+static int adxl367_get_act_interrupt_en(struct adxl367_state *st,
+ enum adxl367_activity_type act,
+ bool *en)
+{
+ unsigned int mask = adxl367_act_int_mask_tbl[act];
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL367_REG_INT1_MAP, &val);
+ if (ret)
+ return ret;
+
+ *en = !!(val & mask);
+
+ return 0;
+}
+
+static int adxl367_set_act_en(struct adxl367_state *st,
+ enum adxl367_activity_type act,
+ enum adxl367_act_en_mode en)
+{
+ unsigned int ctl_shift = adxl367_act_en_shift_tbl[act];
+
+ return regmap_update_bits(st->regmap, ADXL367_REG_ACT_INACT_CTL,
+ ADXL367_ACT_EN_MASK << ctl_shift,
+ en << ctl_shift);
+}
+
+static int adxl367_set_fifo_watermark_interrupt_en(struct adxl367_state *st,
+ bool en)
+{
+ return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP,
+ ADXL367_INT_FIFO_WATERMARK_MASK,
+ en ? ADXL367_INT_FIFO_WATERMARK_MASK : 0);
+}
+
+static int adxl367_get_fifo_mode(struct adxl367_state *st,
+ enum adxl367_fifo_mode *fifo_mode)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL367_REG_FIFO_CTL, &val);
+ if (ret)
+ return ret;
+
+ *fifo_mode = FIELD_GET(ADXL367_FIFO_CTL_MODE_MASK, val);
+
+ return 0;
+}
+
+static int adxl367_set_fifo_mode(struct adxl367_state *st,
+ enum adxl367_fifo_mode fifo_mode)
+{
+ return regmap_update_bits(st->regmap, ADXL367_REG_FIFO_CTL,
+ ADXL367_FIFO_CTL_MODE_MASK,
+ FIELD_PREP(ADXL367_FIFO_CTL_MODE_MASK,
+ fifo_mode));
+}
+
+static int adxl367_set_fifo_format(struct adxl367_state *st,
+ enum adxl367_fifo_format fifo_format)
+{
+ return regmap_update_bits(st->regmap, ADXL367_REG_FIFO_CTL,
+ ADXL367_FIFO_CTL_FORMAT_MASK,
+ FIELD_PREP(ADXL367_FIFO_CTL_FORMAT_MASK,
+ fifo_format));
+}
+
+static int adxl367_set_fifo_watermark(struct adxl367_state *st,
+ unsigned int fifo_watermark)
+{
+ unsigned int fifo_samples = fifo_watermark * st->fifo_set_size;
+ unsigned int fifo_samples_h, fifo_samples_l;
+ int ret;
+
+ if (fifo_samples > ADXL367_FIFO_MAX_WATERMARK)
+ fifo_samples = ADXL367_FIFO_MAX_WATERMARK;
+
+ fifo_samples /= st->fifo_set_size;
+
+ fifo_samples_h = FIELD_PREP(ADXL367_SAMPLES_H_MASK,
+ FIELD_GET(ADXL367_SAMPLES_VAL_H_MASK,
+ fifo_samples));
+ fifo_samples_l = FIELD_PREP(ADXL367_SAMPLES_L_MASK,
+ FIELD_GET(ADXL367_SAMPLES_VAL_L_MASK,
+ fifo_samples));
+
+ ret = regmap_update_bits(st->regmap, ADXL367_REG_FIFO_CTL,
+ ADXL367_SAMPLES_H_MASK, fifo_samples_h);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL367_REG_FIFO_SAMPLES,
+ ADXL367_SAMPLES_L_MASK, fifo_samples_l);
+ if (ret)
+ return ret;
+
+ st->fifo_watermark = fifo_watermark;
+
+ return 0;
+}
+
+static int adxl367_set_range(struct iio_dev *indio_dev,
+ enum adxl367_range range)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl367_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL367_REG_FILTER_CTL,
+ ADXL367_FILTER_CTL_RANGE_MASK,
+ FIELD_PREP(ADXL367_FILTER_CTL_RANGE_MASK,
+ range));
+ if (ret)
+ return ret;
+
+ adxl367_scale_act_thresholds(st, st->range, range);
+
+ /* Activity thresholds depend on range */
+ ret = _adxl367_set_act_threshold(st, ADXL367_ACTIVITY,
+ st->act_threshold);
+ if (ret)
+ return ret;
+
+ ret = _adxl367_set_act_threshold(st, ADXL367_INACTIVITY,
+ st->inact_threshold);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_measure_en(st, true);
+ if (ret)
+ return ret;
+
+ st->range = range;
+
+ return 0;
+}
+
+static int adxl367_time_ms_to_samples(struct adxl367_state *st, unsigned int ms)
+{
+ int freq_hz = adxl367_samp_freq_tbl[st->odr][0];
+ int freq_microhz = adxl367_samp_freq_tbl[st->odr][1];
+ /* Scale to decihertz to prevent precision loss in 12.5Hz case. */
+ int freq_dhz = freq_hz * 10 + freq_microhz / 100000;
+
+ return DIV_ROUND_CLOSEST(ms * freq_dhz, 10000);
+}
+
+static int _adxl367_set_act_time_ms(struct adxl367_state *st, unsigned int ms)
+{
+ unsigned int val = adxl367_time_ms_to_samples(st, ms);
+ int ret;
+
+ if (val > ADXL367_TIME_ACT_MAX)
+ val = ADXL367_TIME_ACT_MAX;
+
+ ret = regmap_write(st->regmap, ADXL367_REG_TIME_ACT, val);
+ if (ret)
+ return ret;
+
+ st->act_time_ms = ms;
+
+ return 0;
+}
+
+static int _adxl367_set_inact_time_ms(struct adxl367_state *st, unsigned int ms)
+{
+ unsigned int val = adxl367_time_ms_to_samples(st, ms);
+ int ret;
+
+ if (val > ADXL367_TIME_INACT_MAX)
+ val = ADXL367_TIME_INACT_MAX;
+
+ st->inact_time_buf[0] = FIELD_PREP(ADXL367_TIME_INACT_H_MASK,
+ FIELD_GET(ADXL367_TIME_INACT_VAL_H_MASK,
+ val));
+ st->inact_time_buf[1] = FIELD_PREP(ADXL367_TIME_INACT_L_MASK,
+ FIELD_GET(ADXL367_TIME_INACT_VAL_L_MASK,
+ val));
+
+ ret = regmap_bulk_write(st->regmap, ADXL367_REG_TIME_INACT_H,
+ st->inact_time_buf, sizeof(st->inact_time_buf));
+ if (ret)
+ return ret;
+
+ st->inact_time_ms = ms;
+
+ return 0;
+}
+
+static int adxl367_set_act_time_ms(struct adxl367_state *st,
+ enum adxl367_activity_type act,
+ unsigned int ms)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl367_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ if (act == ADXL367_ACTIVITY)
+ ret = _adxl367_set_act_time_ms(st, ms);
+ else
+ ret = _adxl367_set_inact_time_ms(st, ms);
+
+ if (ret)
+ return ret;
+
+ return adxl367_set_measure_en(st, true);
+}
+
+static int _adxl367_set_odr(struct adxl367_state *st, enum adxl367_odr odr)
+{
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL367_REG_FILTER_CTL,
+ ADXL367_FILTER_CTL_ODR_MASK,
+ FIELD_PREP(ADXL367_FILTER_CTL_ODR_MASK,
+ odr));
+ if (ret)
+ return ret;
+
+ st->odr = odr;
+
+ /* Activity timers depend on ODR */
+ ret = _adxl367_set_act_time_ms(st, st->act_time_ms);
+ if (ret)
+ return ret;
+
+ return _adxl367_set_inact_time_ms(st, st->inact_time_ms);
+}
+
+static int adxl367_set_odr(struct iio_dev *indio_dev, enum adxl367_odr odr)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl367_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = _adxl367_set_odr(st, odr);
+ if (ret)
+ return ret;
+
+ return adxl367_set_measure_en(st, true);
+}
+
+static int adxl367_set_temp_adc_en(struct adxl367_state *st, unsigned int reg,
+ bool en)
+{
+ return regmap_update_bits(st->regmap, reg, ADXL367_ADC_EN_MASK,
+ en ? ADXL367_ADC_EN_MASK : 0);
+}
+
+static int adxl367_set_temp_adc_reg_en(struct adxl367_state *st,
+ unsigned int reg, bool en)
+{
+ int ret;
+
+ switch (reg) {
+ case ADXL367_REG_TEMP_DATA_H:
+ ret = adxl367_set_temp_adc_en(st, ADXL367_REG_TEMP_CTL, en);
+ break;
+ case ADXL367_REG_EX_ADC_DATA_H:
+ ret = adxl367_set_temp_adc_en(st, ADXL367_REG_ADC_CTL, en);
+ break;
+ default:
+ return 0;
+ }
+
+ if (ret)
+ return ret;
+
+ if (en)
+ msleep(100);
+
+ return 0;
+}
+
+static int adxl367_set_temp_adc_mask_en(struct adxl367_state *st,
+ const unsigned long *active_scan_mask,
+ bool en)
+{
+ if (*active_scan_mask & ADXL367_TEMP_CHANNEL_MASK)
+ return adxl367_set_temp_adc_en(st, ADXL367_REG_TEMP_CTL, en);
+ else if (*active_scan_mask & ADXL367_EX_ADC_CHANNEL_MASK)
+ return adxl367_set_temp_adc_en(st, ADXL367_REG_ADC_CTL, en);
+
+ return 0;
+}
+
+static int adxl367_find_odr(struct adxl367_state *st, int val, int val2,
+ enum adxl367_odr *odr)
+{
+ size_t size = ARRAY_SIZE(adxl367_samp_freq_tbl);
+ int i;
+
+ for (i = 0; i < size; i++)
+ if (val == adxl367_samp_freq_tbl[i][0] &&
+ val2 == adxl367_samp_freq_tbl[i][1])
+ break;
+
+ if (i == size)
+ return -EINVAL;
+
+ *odr = i;
+
+ return 0;
+}
+
+static int adxl367_find_range(struct adxl367_state *st, int val, int val2,
+ enum adxl367_range *range)
+{
+ size_t size = ARRAY_SIZE(adxl367_range_scale_tbl);
+ int i;
+
+ for (i = 0; i < size; i++)
+ if (val == adxl367_range_scale_tbl[i][0] &&
+ val2 == adxl367_range_scale_tbl[i][1])
+ break;
+
+ if (i == size)
+ return -EINVAL;
+
+ *range = i;
+
+ return 0;
+}
+
+static int adxl367_read_sample(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ u16 sample;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl367_set_temp_adc_reg_en(st, chan->address, true);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(st->regmap, chan->address, &st->sample_buf,
+ sizeof(st->sample_buf));
+ if (ret)
+ return ret;
+
+ sample = FIELD_GET(ADXL367_DATA_MASK, be16_to_cpu(st->sample_buf));
+ *val = sign_extend32(sample, chan->scan_type.realbits - 1);
+
+ ret = adxl367_set_temp_adc_reg_en(st, chan->address, false);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+}
+
+static int adxl367_get_status(struct adxl367_state *st, u8 *status,
+ u16 *fifo_entries)
+{
+ int ret;
+
+ /* Read STATUS, FIFO_ENT_L and FIFO_ENT_H */
+ ret = regmap_bulk_read(st->regmap, ADXL367_REG_STATUS,
+ st->status_buf, sizeof(st->status_buf));
+ if (ret)
+ return ret;
+
+ st->status_buf[2] &= ADXL367_FIFO_ENT_H_MASK;
+
+ *status = st->status_buf[0];
+ *fifo_entries = get_unaligned_le16(&st->status_buf[1]);
+
+ return 0;
+}
+
+static bool adxl367_push_event(struct iio_dev *indio_dev, u8 status)
+{
+ unsigned int ev_dir;
+
+ if (FIELD_GET(ADXL367_STATUS_ACT_MASK, status))
+ ev_dir = IIO_EV_DIR_RISING;
+ else if (FIELD_GET(ADXL367_STATUS_INACT_MASK, status))
+ ev_dir = IIO_EV_DIR_FALLING;
+ else
+ return false;
+
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_THRESH, ev_dir),
+ iio_get_time_ns(indio_dev));
+
+ return true;
+}
+
+static bool adxl367_push_fifo_data(struct iio_dev *indio_dev, u8 status,
+ u16 fifo_entries)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ int ret;
+ int i;
+
+ if (!FIELD_GET(ADXL367_STATUS_FIFO_FULL_MASK, status))
+ return false;
+
+ fifo_entries -= fifo_entries % st->fifo_set_size;
+
+ ret = st->ops->read_fifo(st->context, st->fifo_buf, fifo_entries);
+ if (ret) {
+ dev_err(st->dev, "Failed to read FIFO: %d\n", ret);
+ return true;
+ }
+
+ for (i = 0; i < fifo_entries; i += st->fifo_set_size)
+ iio_push_to_buffers(indio_dev, &st->fifo_buf[i]);
+
+ return true;
+}
+
+static irqreturn_t adxl367_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct adxl367_state *st = iio_priv(indio_dev);
+ u16 fifo_entries;
+ bool handled;
+ u8 status;
+ int ret;
+
+ ret = adxl367_get_status(st, &status, &fifo_entries);
+ if (ret)
+ return IRQ_NONE;
+
+ handled = adxl367_push_event(indio_dev, status);
+ handled |= adxl367_push_fifo_data(indio_dev, status, fifo_entries);
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int adxl367_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int writeval,
+ unsigned int *readval)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+ else
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static int adxl367_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+ ret = adxl367_read_sample(indio_dev, chan, val);
+ iio_device_release_direct(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ACCEL: {
+ guard(mutex)(&st->lock);
+ *val = adxl367_range_scale_tbl[st->range][0];
+ *val2 = adxl367_range_scale_tbl[st->range][1];
+ return IIO_VAL_INT_PLUS_NANO;
+ }
+ case IIO_TEMP:
+ *val = 1000;
+ *val2 = ADXL367_TEMP_PER_C;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_VOLTAGE:
+ *val = ADXL367_VOLTAGE_MAX_MV;
+ *val2 = ADXL367_VOLTAGE_MAX_RAW;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_TEMP:
+ *val = 25 * ADXL367_TEMP_PER_C - ADXL367_TEMP_25C;
+ return IIO_VAL_INT;
+ case IIO_VOLTAGE:
+ *val = ADXL367_VOLTAGE_OFFSET;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ guard(mutex)(&st->lock);
+ *val = adxl367_samp_freq_tbl[st->odr][0];
+ *val2 = adxl367_samp_freq_tbl[st->odr][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl367_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ enum adxl367_odr odr;
+
+ ret = adxl367_find_odr(st, val, val2, &odr);
+ if (ret)
+ return ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = adxl367_set_odr(indio_dev, odr);
+ iio_device_release_direct(indio_dev);
+ return ret;
+ }
+ case IIO_CHAN_INFO_SCALE: {
+ enum adxl367_range range;
+
+ ret = adxl367_find_range(st, val, val2, &range);
+ if (ret)
+ return ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = adxl367_set_range(indio_dev, range);
+ iio_device_release_direct(indio_dev);
+ return ret;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl367_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long info)
+{
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
+static int adxl367_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long info)
+{
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ *vals = (int *)adxl367_range_scale_tbl;
+ *type = IIO_VAL_INT_PLUS_NANO;
+ *length = ARRAY_SIZE(adxl367_range_scale_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = (int *)adxl367_samp_freq_tbl;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = ARRAY_SIZE(adxl367_samp_freq_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl367_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+ switch (info) {
+ case IIO_EV_INFO_VALUE: {
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ *val = st->act_threshold;
+ return IIO_VAL_INT;
+ case IIO_EV_DIR_FALLING:
+ *val = st->inact_threshold;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ }
+ case IIO_EV_INFO_PERIOD:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ *val = st->act_time_ms;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ *val = st->inact_time_ms;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl367_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ if (val < 0)
+ return -EINVAL;
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl367_set_act_threshold(st, ADXL367_ACTIVITY, val);
+ case IIO_EV_DIR_FALLING:
+ return adxl367_set_act_threshold(st, ADXL367_INACTIVITY, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ if (val < 0)
+ return -EINVAL;
+
+ val = val * 1000 + DIV_ROUND_UP(val2, 1000);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl367_set_act_time_ms(st, ADXL367_ACTIVITY, val);
+ case IIO_EV_DIR_FALLING:
+ return adxl367_set_act_time_ms(st, ADXL367_INACTIVITY, val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl367_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 adxl367_state *st = iio_priv(indio_dev);
+ bool en;
+ int ret;
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = adxl367_get_act_interrupt_en(st, ADXL367_ACTIVITY, &en);
+ return ret ?: en;
+ case IIO_EV_DIR_FALLING:
+ ret = adxl367_get_act_interrupt_en(st, ADXL367_INACTIVITY, &en);
+ return ret ?: en;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int __adxl367_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ enum adxl367_activity_type act;
+ int ret;
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ act = ADXL367_ACTIVITY;
+ break;
+ case IIO_EV_DIR_FALLING:
+ act = ADXL367_INACTIVITY;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl367_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_act_interrupt_en(st, act, state);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_act_en(st, act, state ? ADCL367_ACT_REF_ENABLED
+ : ADXL367_ACT_DISABLED);
+ if (ret)
+ return ret;
+
+ return adxl367_set_measure_en(st, true);
+}
+
+static int adxl367_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ int ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = __adxl367_write_event_config(indio_dev, chan, type, dir, state);
+ iio_device_release_direct(indio_dev);
+ return ret;
+}
+
+static ssize_t adxl367_get_fifo_enabled(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct adxl367_state *st = iio_priv(dev_to_iio_dev(dev));
+ enum adxl367_fifo_mode fifo_mode;
+ int ret;
+
+ ret = adxl367_get_fifo_mode(st, &fifo_mode);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", fifo_mode != ADXL367_FIFO_MODE_DISABLED);
+}
+
+static ssize_t adxl367_get_fifo_watermark(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct adxl367_state *st = iio_priv(dev_to_iio_dev(dev));
+ unsigned int fifo_watermark;
+
+ guard(mutex)(&st->lock);
+ fifo_watermark = st->fifo_watermark;
+
+ return sysfs_emit(buf, "%d\n", fifo_watermark);
+}
+
+IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1");
+IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max,
+ __stringify(ADXL367_FIFO_MAX_WATERMARK));
+static IIO_DEVICE_ATTR(hwfifo_watermark, 0444,
+ adxl367_get_fifo_watermark, NULL, 0);
+static IIO_DEVICE_ATTR(hwfifo_enabled, 0444,
+ adxl367_get_fifo_enabled, NULL, 0);
+
+static const struct iio_dev_attr *adxl367_fifo_attributes[] = {
+ &iio_dev_attr_hwfifo_watermark_min,
+ &iio_dev_attr_hwfifo_watermark_max,
+ &iio_dev_attr_hwfifo_watermark,
+ &iio_dev_attr_hwfifo_enabled,
+ NULL,
+};
+
+static int adxl367_set_watermark(struct iio_dev *indio_dev, unsigned int val)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ int ret;
+
+ if (val > ADXL367_FIFO_MAX_WATERMARK)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl367_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_fifo_watermark(st, val);
+ if (ret)
+ return ret;
+
+ return adxl367_set_measure_en(st, true);
+}
+
+static bool adxl367_find_mask_fifo_format(const unsigned long *scan_mask,
+ enum adxl367_fifo_format *fifo_format)
+{
+ size_t size = ARRAY_SIZE(adxl367_fifo_formats);
+ int i;
+
+ for (i = 0; i < size; i++)
+ if (*scan_mask == adxl367_channel_masks[i])
+ break;
+
+ if (i == size)
+ return false;
+
+ *fifo_format = adxl367_fifo_formats[i];
+
+ return true;
+}
+
+static int adxl367_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *active_scan_mask)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ enum adxl367_fifo_format fifo_format;
+ int ret;
+
+ if (!adxl367_find_mask_fifo_format(active_scan_mask, &fifo_format))
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl367_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_fifo_format(st, fifo_format);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_measure_en(st, true);
+ if (ret)
+ return ret;
+
+ st->fifo_set_size = bitmap_weight(active_scan_mask,
+ iio_get_masklength(indio_dev));
+
+ return 0;
+}
+
+static int adxl367_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl367_set_temp_adc_mask_en(st, indio_dev->active_scan_mask,
+ true);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_fifo_watermark_interrupt_en(st, true);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_fifo_mode(st, ADXL367_FIFO_MODE_STREAM);
+ if (ret)
+ return ret;
+
+ return adxl367_set_measure_en(st, true);
+}
+
+static int adxl367_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct adxl367_state *st = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl367_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_fifo_mode(st, ADXL367_FIFO_MODE_DISABLED);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_fifo_watermark_interrupt_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_measure_en(st, true);
+ if (ret)
+ return ret;
+
+ return adxl367_set_temp_adc_mask_en(st, indio_dev->active_scan_mask,
+ false);
+}
+
+static const struct iio_buffer_setup_ops adxl367_buffer_ops = {
+ .postenable = adxl367_buffer_postenable,
+ .predisable = adxl367_buffer_predisable,
+};
+
+static const struct iio_info adxl367_info = {
+ .read_raw = adxl367_read_raw,
+ .write_raw = adxl367_write_raw,
+ .write_raw_get_fmt = adxl367_write_raw_get_fmt,
+ .read_avail = adxl367_read_avail,
+ .read_event_config = adxl367_read_event_config,
+ .write_event_config = adxl367_write_event_config,
+ .read_event_value = adxl367_read_event_value,
+ .write_event_value = adxl367_write_event_value,
+ .debugfs_reg_access = adxl367_reg_access,
+ .hwfifo_set_watermark = adxl367_set_watermark,
+ .update_scan_mode = adxl367_update_scan_mode,
+};
+
+static const struct iio_event_spec adxl367_events[] = {
+ {
+ .type = IIO_EV_TYPE_MAG_REFERENCED,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_PERIOD) |
+ BIT(IIO_EV_INFO_VALUE),
+ },
+ {
+ .type = IIO_EV_TYPE_MAG_REFERENCED,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_PERIOD) |
+ BIT(IIO_EV_INFO_VALUE),
+ },
+};
+
+#define ADXL367_ACCEL_CHANNEL(index, reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = (reg), \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_all_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .event_spec = adxl367_events, \
+ .num_event_specs = ARRAY_SIZE(adxl367_events), \
+ .scan_index = (index), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 14, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+}
+
+#define ADXL367_CHANNEL(index, reg, _type) { \
+ .type = (_type), \
+ .address = (reg), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_OFFSET) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = (index), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 14, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+}
+
+static const struct iio_chan_spec adxl367_channels[] = {
+ ADXL367_ACCEL_CHANNEL(ADXL367_X_CHANNEL_INDEX, ADXL367_REG_X_DATA_H, X),
+ ADXL367_ACCEL_CHANNEL(ADXL367_Y_CHANNEL_INDEX, ADXL367_REG_Y_DATA_H, Y),
+ ADXL367_ACCEL_CHANNEL(ADXL367_Z_CHANNEL_INDEX, ADXL367_REG_Z_DATA_H, Z),
+ ADXL367_CHANNEL(ADXL367_TEMP_CHANNEL_INDEX, ADXL367_REG_TEMP_DATA_H,
+ IIO_TEMP),
+ ADXL367_CHANNEL(ADXL367_EX_ADC_CHANNEL_INDEX, ADXL367_REG_EX_ADC_DATA_H,
+ IIO_VOLTAGE),
+};
+
+static int adxl367_verify_devid(struct adxl367_state *st)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL367_REG_DEVID, &val);
+ if (ret)
+ return dev_err_probe(st->dev, ret, "Failed to read dev id\n");
+
+ if (val != ADXL367_DEVID_AD)
+ return dev_err_probe(st->dev, -ENODEV,
+ "Invalid dev id 0x%02X, expected 0x%02X\n",
+ val, ADXL367_DEVID_AD);
+
+ return 0;
+}
+
+static int adxl367_setup(struct adxl367_state *st)
+{
+ int ret;
+
+ ret = _adxl367_set_act_threshold(st, ADXL367_ACTIVITY,
+ ADXL367_2G_RANGE_1G);
+ if (ret)
+ return ret;
+
+ ret = _adxl367_set_act_threshold(st, ADXL367_INACTIVITY,
+ ADXL367_2G_RANGE_100MG);
+ if (ret)
+ return ret;
+
+ ret = adxl367_set_act_proc_mode(st, ADXL367_LOOPED);
+ if (ret)
+ return ret;
+
+ ret = _adxl367_set_odr(st, ADXL367_ODR_400HZ);
+ if (ret)
+ return ret;
+
+ ret = _adxl367_set_act_time_ms(st, 10);
+ if (ret)
+ return ret;
+
+ ret = _adxl367_set_inact_time_ms(st, 10000);
+ if (ret)
+ return ret;
+
+ return adxl367_set_measure_en(st, true);
+}
+
+int adxl367_probe(struct device *dev, const struct adxl367_ops *ops,
+ void *context, struct regmap *regmap, int irq)
+{
+ static const char * const regulator_names[] = { "vdd", "vddio" };
+ struct iio_dev *indio_dev;
+ struct adxl367_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->dev = dev;
+ st->regmap = regmap;
+ st->context = context;
+ st->ops = ops;
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ indio_dev->channels = adxl367_channels;
+ indio_dev->num_channels = ARRAY_SIZE(adxl367_channels);
+ indio_dev->available_scan_masks = adxl367_channel_masks;
+ indio_dev->name = "adxl367";
+ indio_dev->info = &adxl367_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = devm_regulator_bulk_get_enable(st->dev,
+ ARRAY_SIZE(regulator_names),
+ regulator_names);
+ if (ret)
+ return dev_err_probe(st->dev, ret,
+ "Failed to get regulators\n");
+
+ ret = regmap_write(st->regmap, ADXL367_REG_RESET, ADXL367_RESET_CODE);
+ if (ret)
+ return ret;
+
+ fsleep(15000);
+
+ ret = adxl367_verify_devid(st);
+ if (ret)
+ return ret;
+
+ ret = adxl367_setup(st);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_kfifo_buffer_setup_ext(st->dev, indio_dev,
+ &adxl367_buffer_ops,
+ adxl367_fifo_attributes);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(st->dev, irq, NULL,
+ adxl367_irq_handler, IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(adxl367_probe, "IIO_ADXL367");
+
+MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL367 3-axis accelerometer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/adxl367.h b/drivers/iio/accel/adxl367.h
new file mode 100644
index 000000000..4a4262214
--- /dev/null
+++ b/drivers/iio/accel/adxl367.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2021 Analog Devices, Inc.
+ * Author: Cosmin Tanislav <cosmin.tanislav@analog.com>
+ */
+
+#ifndef _ADXL367_H_
+#define _ADXL367_H_
+
+#include <linux/types.h>
+
+struct device;
+struct regmap;
+
+struct adxl367_ops {
+ int (*read_fifo)(void *context, __be16 *fifo_buf,
+ unsigned int fifo_entries);
+};
+
+int adxl367_probe(struct device *dev, const struct adxl367_ops *ops,
+ void *context, struct regmap *regmap, int irq);
+
+#endif /* _ADXL367_H_ */
diff --git a/drivers/iio/accel/adxl367_i2c.c b/drivers/iio/accel/adxl367_i2c.c
new file mode 100644
index 000000000..42e747f5d
--- /dev/null
+++ b/drivers/iio/accel/adxl367_i2c.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Analog Devices, Inc.
+ * Author: Cosmin Tanislav <cosmin.tanislav@analog.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "adxl367.h"
+
+#define ADXL367_I2C_FIFO_DATA 0x18
+
+struct adxl367_i2c_state {
+ struct regmap *regmap;
+};
+
+static bool adxl367_readable_noinc_reg(struct device *dev, unsigned int reg)
+{
+ return reg == ADXL367_I2C_FIFO_DATA;
+}
+
+static int adxl367_i2c_read_fifo(void *context, __be16 *fifo_buf,
+ unsigned int fifo_entries)
+{
+ struct adxl367_i2c_state *st = context;
+
+ return regmap_noinc_read(st->regmap, ADXL367_I2C_FIFO_DATA, fifo_buf,
+ fifo_entries * sizeof(*fifo_buf));
+}
+
+static const struct regmap_config adxl367_i2c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .readable_noinc_reg = adxl367_readable_noinc_reg,
+};
+
+static const struct adxl367_ops adxl367_i2c_ops = {
+ .read_fifo = adxl367_i2c_read_fifo,
+};
+
+static int adxl367_i2c_probe(struct i2c_client *client)
+{
+ struct adxl367_i2c_state *st;
+ struct regmap *regmap;
+
+ st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_i2c(client, &adxl367_i2c_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ st->regmap = regmap;
+
+ return adxl367_probe(&client->dev, &adxl367_i2c_ops, st, regmap,
+ client->irq);
+}
+
+static const struct i2c_device_id adxl367_i2c_id[] = {
+ { .name = "adxl367" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adxl367_i2c_id);
+
+static const struct of_device_id adxl367_of_match[] = {
+ { .compatible = "adi,adxl367" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adxl367_of_match);
+
+static struct i2c_driver adxl367_i2c_driver = {
+ .driver = {
+ .name = "adxl367_i2c",
+ .of_match_table = adxl367_of_match,
+ },
+ .probe = adxl367_i2c_probe,
+ .id_table = adxl367_i2c_id,
+};
+
+module_i2c_driver(adxl367_i2c_driver);
+
+MODULE_IMPORT_NS("IIO_ADXL367");
+MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL367 3-axis accelerometer I2C driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/adxl367_spi.c b/drivers/iio/accel/adxl367_spi.c
new file mode 100644
index 000000000..1d1418452
--- /dev/null
+++ b/drivers/iio/accel/adxl367_spi.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Analog Devices, Inc.
+ * Author: Cosmin Tanislav <cosmin.tanislav@analog.com>
+ */
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include <linux/iio/iio.h>
+
+#include "adxl367.h"
+
+#define ADXL367_SPI_WRITE_COMMAND 0x0A
+#define ADXL367_SPI_READ_COMMAND 0x0B
+#define ADXL367_SPI_FIFO_COMMAND 0x0D
+
+struct adxl367_spi_state {
+ struct spi_device *spi;
+
+ struct spi_message reg_write_msg;
+ struct spi_transfer reg_write_xfer[2];
+
+ struct spi_message reg_read_msg;
+ struct spi_transfer reg_read_xfer[2];
+
+ struct spi_message fifo_msg;
+ struct spi_transfer fifo_xfer[2];
+
+ /*
+ * DMA (thus cache coherency maintenance) may require the
+ * transfer buffers live in their own cache lines.
+ */
+ u8 reg_write_tx_buf[1] __aligned(IIO_DMA_MINALIGN);
+ u8 reg_read_tx_buf[2];
+ u8 fifo_tx_buf[1];
+};
+
+static int adxl367_read_fifo(void *context, __be16 *fifo_buf,
+ unsigned int fifo_entries)
+{
+ struct adxl367_spi_state *st = context;
+
+ st->fifo_xfer[1].rx_buf = fifo_buf;
+ st->fifo_xfer[1].len = fifo_entries * sizeof(*fifo_buf);
+
+ return spi_sync(st->spi, &st->fifo_msg);
+}
+
+static int adxl367_read(void *context, const void *reg_buf, size_t reg_size,
+ void *val_buf, size_t val_size)
+{
+ struct adxl367_spi_state *st = context;
+ u8 reg = ((const u8 *)reg_buf)[0];
+
+ st->reg_read_tx_buf[1] = reg;
+ st->reg_read_xfer[1].rx_buf = val_buf;
+ st->reg_read_xfer[1].len = val_size;
+
+ return spi_sync(st->spi, &st->reg_read_msg);
+}
+
+static int adxl367_write(void *context, const void *val_buf, size_t val_size)
+{
+ struct adxl367_spi_state *st = context;
+
+ st->reg_write_xfer[1].tx_buf = val_buf;
+ st->reg_write_xfer[1].len = val_size;
+
+ return spi_sync(st->spi, &st->reg_write_msg);
+}
+
+static const struct regmap_bus adxl367_spi_regmap_bus = {
+ .read = adxl367_read,
+ .write = adxl367_write,
+};
+
+static const struct regmap_config adxl367_spi_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static const struct adxl367_ops adxl367_spi_ops = {
+ .read_fifo = adxl367_read_fifo,
+};
+
+static int adxl367_spi_probe(struct spi_device *spi)
+{
+ struct adxl367_spi_state *st;
+ struct regmap *regmap;
+
+ st = devm_kzalloc(&spi->dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ st->spi = spi;
+
+ /*
+ * Xfer: [XFR1] [ XFR2 ]
+ * Master: 0x0A ADDR DATA0 DATA1 ... DATAN
+ * Slave: .... ..........................
+ */
+ st->reg_write_tx_buf[0] = ADXL367_SPI_WRITE_COMMAND;
+ st->reg_write_xfer[0].tx_buf = st->reg_write_tx_buf;
+ st->reg_write_xfer[0].len = sizeof(st->reg_write_tx_buf);
+ spi_message_init_with_transfers(&st->reg_write_msg,
+ st->reg_write_xfer, 2);
+
+ /*
+ * Xfer: [ XFR1 ] [ XFR2 ]
+ * Master: 0x0B ADDR .....................
+ * Slave: ......... DATA0 DATA1 ... DATAN
+ */
+ st->reg_read_tx_buf[0] = ADXL367_SPI_READ_COMMAND;
+ st->reg_read_xfer[0].tx_buf = st->reg_read_tx_buf;
+ st->reg_read_xfer[0].len = sizeof(st->reg_read_tx_buf);
+ spi_message_init_with_transfers(&st->reg_read_msg,
+ st->reg_read_xfer, 2);
+
+ /*
+ * Xfer: [XFR1] [ XFR2 ]
+ * Master: 0x0D .....................
+ * Slave: .... DATA0 DATA1 ... DATAN
+ */
+ st->fifo_tx_buf[0] = ADXL367_SPI_FIFO_COMMAND;
+ st->fifo_xfer[0].tx_buf = st->fifo_tx_buf;
+ st->fifo_xfer[0].len = sizeof(st->fifo_tx_buf);
+ spi_message_init_with_transfers(&st->fifo_msg, st->fifo_xfer, 2);
+
+ regmap = devm_regmap_init(&spi->dev, &adxl367_spi_regmap_bus, st,
+ &adxl367_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return adxl367_probe(&spi->dev, &adxl367_spi_ops, st, regmap, spi->irq);
+}
+
+static const struct spi_device_id adxl367_spi_id[] = {
+ { .name = "adxl367" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adxl367_spi_id);
+
+static const struct of_device_id adxl367_of_match[] = {
+ { .compatible = "adi,adxl367" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adxl367_of_match);
+
+static struct spi_driver adxl367_spi_driver = {
+ .driver = {
+ .name = "adxl367_spi",
+ .of_match_table = adxl367_of_match,
+ },
+ .probe = adxl367_spi_probe,
+ .id_table = adxl367_spi_id,
+};
+
+module_spi_driver(adxl367_spi_driver);
+
+MODULE_IMPORT_NS("IIO_ADXL367");
+MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL367 3-axis accelerometer SPI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/adxl372.c b/drivers/iio/accel/adxl372.c
new file mode 100644
index 000000000..e375d068a
--- /dev/null
+++ b/drivers/iio/accel/adxl372.c
@@ -0,0 +1,1335 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ADXL371/ADXL372 3-Axis Digital Accelerometer core driver
+ *
+ * Copyright 2018 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include "adxl372.h"
+
+/* ADXL372 registers definition */
+#define ADXL372_DEVID 0x00
+#define ADXL372_DEVID_MST 0x01
+#define ADXL372_PARTID 0x02
+#define ADXL372_STATUS_1 0x04
+#define ADXL372_STATUS_2 0x05
+#define ADXL372_FIFO_ENTRIES_2 0x06
+#define ADXL372_FIFO_ENTRIES_1 0x07
+#define ADXL372_X_DATA_H 0x08
+#define ADXL372_X_DATA_L 0x09
+#define ADXL372_Y_DATA_H 0x0A
+#define ADXL372_Y_DATA_L 0x0B
+#define ADXL372_Z_DATA_H 0x0C
+#define ADXL372_Z_DATA_L 0x0D
+#define ADXL372_X_MAXPEAK_H 0x15
+#define ADXL372_X_MAXPEAK_L 0x16
+#define ADXL372_Y_MAXPEAK_H 0x17
+#define ADXL372_Y_MAXPEAK_L 0x18
+#define ADXL372_Z_MAXPEAK_H 0x19
+#define ADXL372_Z_MAXPEAK_L 0x1A
+#define ADXL372_OFFSET_X 0x20
+#define ADXL372_OFFSET_Y 0x21
+#define ADXL372_OFFSET_Z 0x22
+#define ADXL372_X_THRESH_ACT_H 0x23
+#define ADXL372_X_THRESH_ACT_L 0x24
+#define ADXL372_Y_THRESH_ACT_H 0x25
+#define ADXL372_Y_THRESH_ACT_L 0x26
+#define ADXL372_Z_THRESH_ACT_H 0x27
+#define ADXL372_Z_THRESH_ACT_L 0x28
+#define ADXL372_TIME_ACT 0x29
+#define ADXL372_X_THRESH_INACT_H 0x2A
+#define ADXL372_X_THRESH_INACT_L 0x2B
+#define ADXL372_Y_THRESH_INACT_H 0x2C
+#define ADXL372_Y_THRESH_INACT_L 0x2D
+#define ADXL372_Z_THRESH_INACT_H 0x2E
+#define ADXL372_Z_THRESH_INACT_L 0x2F
+#define ADXL372_TIME_INACT_H 0x30
+#define ADXL372_TIME_INACT_L 0x31
+#define ADXL372_X_THRESH_ACT2_H 0x32
+#define ADXL372_X_THRESH_ACT2_L 0x33
+#define ADXL372_Y_THRESH_ACT2_H 0x34
+#define ADXL372_Y_THRESH_ACT2_L 0x35
+#define ADXL372_Z_THRESH_ACT2_H 0x36
+#define ADXL372_Z_THRESH_ACT2_L 0x37
+#define ADXL372_HPF 0x38
+#define ADXL372_FIFO_SAMPLES 0x39
+#define ADXL372_FIFO_CTL 0x3A
+#define ADXL372_INT1_MAP 0x3B
+#define ADXL372_INT2_MAP 0x3C
+#define ADXL372_TIMING 0x3D
+#define ADXL372_MEASURE 0x3E
+#define ADXL372_POWER_CTL 0x3F
+#define ADXL372_SELF_TEST 0x40
+#define ADXL372_RESET 0x41
+#define ADXL372_FIFO_DATA 0x42
+
+#define ADXL372_DEVID_VAL 0xAD
+#define ADXL372_PARTID_VAL 0xFA
+#define ADXL372_RESET_CODE 0x52
+
+/* ADXL372_POWER_CTL */
+#define ADXL372_POWER_CTL_MODE_MSK GENMASK_ULL(1, 0)
+#define ADXL372_POWER_CTL_MODE(x) (((x) & 0x3) << 0)
+
+/* ADXL372_MEASURE */
+#define ADXL372_MEASURE_LINKLOOP_MSK GENMASK_ULL(5, 4)
+#define ADXL372_MEASURE_LINKLOOP_MODE(x) (((x) & 0x3) << 4)
+#define ADXL372_MEASURE_BANDWIDTH_MSK GENMASK_ULL(2, 0)
+#define ADXL372_MEASURE_BANDWIDTH_MODE(x) (((x) & 0x7) << 0)
+
+/* ADXL372_TIMING */
+#define ADXL372_TIMING_ODR_MSK GENMASK_ULL(7, 5)
+#define ADXL372_TIMING_ODR_MODE(x) (((x) & 0x7) << 5)
+
+/* ADXL372_FIFO_CTL */
+#define ADXL372_FIFO_CTL_FORMAT_MSK GENMASK(5, 3)
+#define ADXL372_FIFO_CTL_FORMAT_MODE(x) (((x) & 0x7) << 3)
+#define ADXL372_FIFO_CTL_MODE_MSK GENMASK(2, 1)
+#define ADXL372_FIFO_CTL_MODE_MODE(x) (((x) & 0x3) << 1)
+#define ADXL372_FIFO_CTL_SAMPLES_MSK BIT(1)
+#define ADXL372_FIFO_CTL_SAMPLES_MODE(x) (((x) > 0xFF) ? 1 : 0)
+
+/* ADXL372_STATUS_1 */
+#define ADXL372_STATUS_1_DATA_RDY(x) (((x) >> 0) & 0x1)
+#define ADXL372_STATUS_1_FIFO_RDY(x) (((x) >> 1) & 0x1)
+#define ADXL372_STATUS_1_FIFO_FULL(x) (((x) >> 2) & 0x1)
+#define ADXL372_STATUS_1_FIFO_OVR(x) (((x) >> 3) & 0x1)
+#define ADXL372_STATUS_1_USR_NVM_BUSY(x) (((x) >> 5) & 0x1)
+#define ADXL372_STATUS_1_AWAKE(x) (((x) >> 6) & 0x1)
+#define ADXL372_STATUS_1_ERR_USR_REGS(x) (((x) >> 7) & 0x1)
+
+/* ADXL372_STATUS_2 */
+#define ADXL372_STATUS_2_INACT(x) (((x) >> 4) & 0x1)
+#define ADXL372_STATUS_2_ACT(x) (((x) >> 5) & 0x1)
+#define ADXL372_STATUS_2_AC2(x) (((x) >> 6) & 0x1)
+
+/* ADXL372_INT1_MAP */
+#define ADXL372_INT1_MAP_DATA_RDY_MSK BIT(0)
+#define ADXL372_INT1_MAP_DATA_RDY_MODE(x) (((x) & 0x1) << 0)
+#define ADXL372_INT1_MAP_FIFO_RDY_MSK BIT(1)
+#define ADXL372_INT1_MAP_FIFO_RDY_MODE(x) (((x) & 0x1) << 1)
+#define ADXL372_INT1_MAP_FIFO_FULL_MSK BIT(2)
+#define ADXL372_INT1_MAP_FIFO_FULL_MODE(x) (((x) & 0x1) << 2)
+#define ADXL372_INT1_MAP_FIFO_OVR_MSK BIT(3)
+#define ADXL372_INT1_MAP_FIFO_OVR_MODE(x) (((x) & 0x1) << 3)
+#define ADXL372_INT1_MAP_INACT_MSK BIT(4)
+#define ADXL372_INT1_MAP_INACT_MODE(x) (((x) & 0x1) << 4)
+#define ADXL372_INT1_MAP_ACT_MSK BIT(5)
+#define ADXL372_INT1_MAP_ACT_MODE(x) (((x) & 0x1) << 5)
+#define ADXL372_INT1_MAP_AWAKE_MSK BIT(6)
+#define ADXL372_INT1_MAP_AWAKE_MODE(x) (((x) & 0x1) << 6)
+#define ADXL372_INT1_MAP_LOW_MSK BIT(7)
+#define ADXL372_INT1_MAP_LOW_MODE(x) (((x) & 0x1) << 7)
+
+/* ADX372_THRESH */
+#define ADXL372_THRESH_VAL_H_MSK GENMASK(10, 3)
+#define ADXL372_THRESH_VAL_H_SEL(x) FIELD_GET(ADXL372_THRESH_VAL_H_MSK, x)
+#define ADXL372_THRESH_VAL_L_MSK GENMASK(2, 0)
+#define ADXL372_THRESH_VAL_L_SEL(x) FIELD_GET(ADXL372_THRESH_VAL_L_MSK, x)
+
+/* The ADXL372 includes a deep, 512 sample FIFO buffer */
+#define ADXL372_FIFO_SIZE 512
+#define ADXL372_X_AXIS_EN(x) ((x) & BIT(0))
+#define ADXL372_Y_AXIS_EN(x) ((x) & BIT(1))
+#define ADXL372_Z_AXIS_EN(x) ((x) & BIT(2))
+
+/*
+ * At +/- 200g with 12-bit resolution, scale is computed as:
+ * (200 + 200) * 9.81 / (2^12 - 1) = 0.958241
+ */
+#define ADXL372_USCALE 958241
+
+enum adxl372_op_mode {
+ ADXL372_STANDBY,
+ ADXL372_WAKE_UP,
+ ADXL372_INSTANT_ON,
+ ADXL372_FULL_BW_MEASUREMENT,
+};
+
+enum adxl372_act_proc_mode {
+ ADXL372_DEFAULT,
+ ADXL372_LINKED,
+ ADXL372_LOOPED,
+};
+
+enum adxl372_th_activity {
+ ADXL372_ACTIVITY,
+ ADXL372_ACTIVITY2,
+ ADXL372_INACTIVITY,
+};
+
+enum adxl372_odr {
+ ADXL372_ODR_400HZ,
+ ADXL372_ODR_800HZ,
+ ADXL372_ODR_1600HZ,
+ ADXL372_ODR_3200HZ,
+ ADXL372_ODR_6400HZ,
+ ADXL372_ODR_NUM
+};
+
+enum adxl371_odr {
+ ADXL371_ODR_320HZ,
+ ADXL371_ODR_640HZ,
+ ADXL371_ODR_1280HZ,
+ ADXL371_ODR_2560HZ,
+ ADXL371_ODR_5120HZ,
+ ADXL371_ODR_NUM
+};
+
+enum adxl372_bandwidth {
+ ADXL372_BW_200HZ,
+ ADXL372_BW_400HZ,
+ ADXL372_BW_800HZ,
+ ADXL372_BW_1600HZ,
+ ADXL372_BW_3200HZ,
+};
+
+static const unsigned int adxl372_th_reg_high_addr[3] = {
+ [ADXL372_ACTIVITY] = ADXL372_X_THRESH_ACT_H,
+ [ADXL372_ACTIVITY2] = ADXL372_X_THRESH_ACT2_H,
+ [ADXL372_INACTIVITY] = ADXL372_X_THRESH_INACT_H,
+};
+
+enum adxl372_fifo_format {
+ ADXL372_XYZ_FIFO,
+ ADXL372_X_FIFO,
+ ADXL372_Y_FIFO,
+ ADXL372_XY_FIFO,
+ ADXL372_Z_FIFO,
+ ADXL372_XZ_FIFO,
+ ADXL372_YZ_FIFO,
+ ADXL372_XYZ_PEAK_FIFO,
+};
+
+enum adxl372_fifo_mode {
+ ADXL372_FIFO_BYPASSED,
+ ADXL372_FIFO_STREAMED,
+ ADXL372_FIFO_TRIGGERED,
+ ADXL372_FIFO_OLD_SAVED
+};
+
+static const int adxl372_samp_freq_tbl[ADXL372_ODR_NUM] = {
+ [ADXL372_ODR_400HZ] = 400,
+ [ADXL372_ODR_800HZ] = 800,
+ [ADXL372_ODR_1600HZ] = 1600,
+ [ADXL372_ODR_3200HZ] = 3200,
+ [ADXL372_ODR_6400HZ] = 6400,
+};
+
+static const int adxl372_bw_freq_tbl[ADXL372_ODR_NUM] = {
+ [ADXL372_BW_200HZ] = 200,
+ [ADXL372_BW_400HZ] = 400,
+ [ADXL372_BW_800HZ] = 800,
+ [ADXL372_BW_1600HZ] = 1600,
+ [ADXL372_BW_3200HZ] = 3200,
+};
+
+static const int adxl371_samp_freq_tbl[ADXL371_ODR_NUM] = {
+ [ADXL371_ODR_320HZ] = 320,
+ [ADXL371_ODR_640HZ] = 640,
+ [ADXL371_ODR_1280HZ] = 1280,
+ [ADXL371_ODR_2560HZ] = 2560,
+ [ADXL371_ODR_5120HZ] = 5120,
+};
+
+static const int adxl371_bw_freq_tbl[ADXL371_ODR_NUM] = {
+ [ADXL371_ODR_320HZ] = 160,
+ [ADXL371_ODR_640HZ] = 320,
+ [ADXL371_ODR_1280HZ] = 640,
+ [ADXL371_ODR_2560HZ] = 1280,
+ [ADXL371_ODR_5120HZ] = 2560,
+};
+
+const struct adxl372_chip_info adxl371_chip_info = {
+ .name = "adxl371",
+ .samp_freq_tbl = adxl371_samp_freq_tbl,
+ .bw_freq_tbl = adxl371_bw_freq_tbl,
+ .num_freqs = ARRAY_SIZE(adxl371_samp_freq_tbl),
+ .act_time_scale_us = 4125,
+ .act_time_scale_low_us = 8250,
+ .inact_time_scale_ms = 16,
+ .inact_time_scale_low_ms = 32,
+ .max_odr = ADXL371_ODR_5120HZ,
+ /* Silicon erratum (er001) causes FIFO data misalignment on ADXL371 */
+ .fifo_supported = false,
+};
+EXPORT_SYMBOL_NS_GPL(adxl371_chip_info, "IIO_ADXL372");
+
+const struct adxl372_chip_info adxl372_chip_info = {
+ .name = "adxl372",
+ .samp_freq_tbl = adxl372_samp_freq_tbl,
+ .bw_freq_tbl = adxl372_bw_freq_tbl,
+ .num_freqs = ARRAY_SIZE(adxl372_samp_freq_tbl),
+ .act_time_scale_us = 3300,
+ .act_time_scale_low_us = 6600,
+ .inact_time_scale_ms = 13,
+ .inact_time_scale_low_ms = 26,
+ .max_odr = ADXL372_ODR_6400HZ,
+ .fifo_supported = true,
+};
+EXPORT_SYMBOL_NS_GPL(adxl372_chip_info, "IIO_ADXL372");
+
+struct adxl372_axis_lookup {
+ unsigned int bits;
+ enum adxl372_fifo_format fifo_format;
+};
+
+static const struct adxl372_axis_lookup adxl372_axis_lookup_table[] = {
+ { BIT(0), ADXL372_X_FIFO },
+ { BIT(1), ADXL372_Y_FIFO },
+ { BIT(2), ADXL372_Z_FIFO },
+ { BIT(0) | BIT(1), ADXL372_XY_FIFO },
+ { BIT(0) | BIT(2), ADXL372_XZ_FIFO },
+ { BIT(1) | BIT(2), ADXL372_YZ_FIFO },
+ { BIT(0) | BIT(1) | BIT(2), ADXL372_XYZ_FIFO },
+};
+
+static const struct iio_event_spec adxl372_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD) | BIT(IIO_EV_INFO_ENABLE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD) | BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+#define ADXL372_ACCEL_CHANNEL(index, reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .shift = 4, \
+ .endianness = IIO_BE, \
+ }, \
+ .event_spec = adxl372_events, \
+ .num_event_specs = ARRAY_SIZE(adxl372_events) \
+}
+
+static const struct iio_chan_spec adxl372_channels[] = {
+ ADXL372_ACCEL_CHANNEL(0, ADXL372_X_DATA_H, X),
+ ADXL372_ACCEL_CHANNEL(1, ADXL372_Y_DATA_H, Y),
+ ADXL372_ACCEL_CHANNEL(2, ADXL372_Z_DATA_H, Z),
+};
+
+struct adxl372_state {
+ const struct adxl372_chip_info *chip_info;
+ int irq;
+ struct device *dev;
+ struct regmap *regmap;
+ struct iio_trigger *dready_trig;
+ struct iio_trigger *peak_datardy_trig;
+ enum adxl372_fifo_mode fifo_mode;
+ enum adxl372_fifo_format fifo_format;
+ unsigned int fifo_axis_mask;
+ enum adxl372_op_mode op_mode;
+ enum adxl372_act_proc_mode act_proc_mode;
+ enum adxl372_odr odr;
+ enum adxl372_bandwidth bw;
+ u32 act_time_ms;
+ u32 inact_time_ms;
+ u8 fifo_set_size;
+ unsigned long int1_bitmask;
+ u16 watermark;
+ __be16 fifo_buf[ADXL372_FIFO_SIZE];
+ bool peak_fifo_mode_en;
+ struct mutex threshold_m; /* lock for threshold */
+};
+
+static const unsigned long adxl372_channel_masks[] = {
+ BIT(0), BIT(1), BIT(2),
+ BIT(0) | BIT(1),
+ BIT(0) | BIT(2),
+ BIT(1) | BIT(2),
+ BIT(0) | BIT(1) | BIT(2),
+ 0
+};
+
+static ssize_t adxl372_read_threshold_value(struct iio_dev *indio_dev, unsigned int addr,
+ u16 *threshold)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+ __be16 raw_regval;
+ u16 regval;
+ int ret;
+
+ ret = regmap_bulk_read(st->regmap, addr, &raw_regval, sizeof(raw_regval));
+ if (ret < 0)
+ return ret;
+
+ regval = be16_to_cpu(raw_regval);
+ regval >>= 5;
+
+ *threshold = regval;
+
+ return 0;
+}
+
+static ssize_t adxl372_write_threshold_value(struct iio_dev *indio_dev, unsigned int addr,
+ u16 threshold)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&st->threshold_m);
+
+ ret = regmap_write(st->regmap, addr, ADXL372_THRESH_VAL_H_SEL(threshold));
+ if (ret < 0)
+ return ret;
+
+ return regmap_update_bits(st->regmap, addr + 1, GENMASK(7, 5),
+ ADXL372_THRESH_VAL_L_SEL(threshold) << 5);
+}
+
+static int adxl372_read_axis(struct adxl372_state *st, u8 addr)
+{
+ __be16 regval;
+ int ret;
+
+ ret = regmap_bulk_read(st->regmap, addr, &regval, sizeof(regval));
+ if (ret < 0)
+ return ret;
+
+ return be16_to_cpu(regval);
+}
+
+static int adxl372_set_op_mode(struct adxl372_state *st,
+ enum adxl372_op_mode op_mode)
+{
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL372_POWER_CTL,
+ ADXL372_POWER_CTL_MODE_MSK,
+ ADXL372_POWER_CTL_MODE(op_mode));
+ if (ret < 0)
+ return ret;
+
+ st->op_mode = op_mode;
+
+ return ret;
+}
+
+static int adxl372_set_odr(struct adxl372_state *st,
+ enum adxl372_odr odr)
+{
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL372_TIMING,
+ ADXL372_TIMING_ODR_MSK,
+ ADXL372_TIMING_ODR_MODE(odr));
+ if (ret < 0)
+ return ret;
+
+ st->odr = odr;
+
+ return ret;
+}
+
+static int adxl372_find_closest_match(const int *array,
+ unsigned int size, int val)
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ if (val <= array[i])
+ return i;
+ }
+
+ return size - 1;
+}
+
+static int adxl372_set_bandwidth(struct adxl372_state *st,
+ enum adxl372_bandwidth bw)
+{
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL372_MEASURE,
+ ADXL372_MEASURE_BANDWIDTH_MSK,
+ ADXL372_MEASURE_BANDWIDTH_MODE(bw));
+ if (ret < 0)
+ return ret;
+
+ st->bw = bw;
+
+ return ret;
+}
+
+static int adxl372_set_act_proc_mode(struct adxl372_state *st,
+ enum adxl372_act_proc_mode mode)
+{
+ int ret;
+
+ ret = regmap_update_bits(st->regmap,
+ ADXL372_MEASURE,
+ ADXL372_MEASURE_LINKLOOP_MSK,
+ ADXL372_MEASURE_LINKLOOP_MODE(mode));
+ if (ret < 0)
+ return ret;
+
+ st->act_proc_mode = mode;
+
+ return ret;
+}
+
+static int adxl372_set_activity_threshold(struct adxl372_state *st,
+ enum adxl372_th_activity act,
+ bool ref_en, bool enable,
+ unsigned int threshold)
+{
+ unsigned char buf[6];
+ unsigned char th_reg_high_val, th_reg_low_val, th_reg_high_addr;
+
+ /* scale factor is 100 mg/code */
+ th_reg_high_val = (threshold / 100) >> 3;
+ th_reg_low_val = ((threshold / 100) << 5) | (ref_en << 1) | enable;
+ th_reg_high_addr = adxl372_th_reg_high_addr[act];
+
+ buf[0] = th_reg_high_val;
+ buf[1] = th_reg_low_val;
+ buf[2] = th_reg_high_val;
+ buf[3] = th_reg_low_val;
+ buf[4] = th_reg_high_val;
+ buf[5] = th_reg_low_val;
+
+ return regmap_bulk_write(st->regmap, th_reg_high_addr,
+ buf, ARRAY_SIZE(buf));
+}
+
+static int adxl372_set_activity_time_ms(struct adxl372_state *st,
+ unsigned int act_time_ms)
+{
+ unsigned int reg_val, scale_factor;
+ int ret;
+
+ /*
+ * The scale factor of the TIME_ACT register depends on the ODR.
+ * A higher scale factor is used at the maximum ODR and a lower
+ * one at all other rates.
+ */
+ if (st->odr == st->chip_info->max_odr)
+ scale_factor = st->chip_info->act_time_scale_us;
+ else
+ scale_factor = st->chip_info->act_time_scale_low_us;
+
+ reg_val = DIV_ROUND_CLOSEST(act_time_ms * 1000, scale_factor);
+
+ /* TIME_ACT register is 8 bits wide */
+ if (reg_val > 0xFF)
+ reg_val = 0xFF;
+
+ ret = regmap_write(st->regmap, ADXL372_TIME_ACT, reg_val);
+ if (ret < 0)
+ return ret;
+
+ st->act_time_ms = act_time_ms;
+
+ return ret;
+}
+
+static int adxl372_set_inactivity_time_ms(struct adxl372_state *st,
+ unsigned int inact_time_ms)
+{
+ unsigned int reg_val_h, reg_val_l, res, scale_factor;
+ int ret;
+
+ /*
+ * The scale factor of the TIME_INACT register depends on the ODR.
+ * A higher scale factor is used at the maximum ODR and a lower
+ * one at all other rates.
+ */
+ if (st->odr == st->chip_info->max_odr)
+ scale_factor = st->chip_info->inact_time_scale_ms;
+ else
+ scale_factor = st->chip_info->inact_time_scale_low_ms;
+
+ res = DIV_ROUND_CLOSEST(inact_time_ms, scale_factor);
+ reg_val_h = (res >> 8) & 0xFF;
+ reg_val_l = res & 0xFF;
+
+ ret = regmap_write(st->regmap, ADXL372_TIME_INACT_H, reg_val_h);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL372_TIME_INACT_L, reg_val_l);
+ if (ret < 0)
+ return ret;
+
+ st->inact_time_ms = inact_time_ms;
+
+ return ret;
+}
+
+static int adxl372_set_interrupts(struct adxl372_state *st,
+ unsigned long int1_bitmask,
+ unsigned long int2_bitmask)
+{
+ int ret;
+
+ ret = regmap_write(st->regmap, ADXL372_INT1_MAP, int1_bitmask);
+ if (ret < 0)
+ return ret;
+
+ return regmap_write(st->regmap, ADXL372_INT2_MAP, int2_bitmask);
+}
+
+static int adxl372_configure_fifo(struct adxl372_state *st)
+{
+ unsigned int fifo_samples, fifo_ctl;
+ int ret;
+
+ /* FIFO must be configured while in standby mode */
+ ret = adxl372_set_op_mode(st, ADXL372_STANDBY);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * watermark stores the number of sets; we need to write the FIFO
+ * registers with the number of samples
+ */
+ fifo_samples = (st->watermark * st->fifo_set_size);
+ fifo_ctl = ADXL372_FIFO_CTL_FORMAT_MODE(st->fifo_format) |
+ ADXL372_FIFO_CTL_MODE_MODE(st->fifo_mode) |
+ ADXL372_FIFO_CTL_SAMPLES_MODE(fifo_samples);
+
+ ret = regmap_write(st->regmap,
+ ADXL372_FIFO_SAMPLES, fifo_samples & 0xFF);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL372_FIFO_CTL, fifo_ctl);
+ if (ret < 0)
+ return ret;
+
+ return adxl372_set_op_mode(st, ADXL372_FULL_BW_MEASUREMENT);
+}
+
+static int adxl372_get_status(struct adxl372_state *st,
+ u8 *status1, u8 *status2,
+ u16 *fifo_entries)
+{
+ __be32 buf;
+ u32 val;
+ int ret;
+
+ /* STATUS1, STATUS2, FIFO_ENTRIES2 and FIFO_ENTRIES are adjacent regs */
+ ret = regmap_bulk_read(st->regmap, ADXL372_STATUS_1,
+ &buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ val = be32_to_cpu(buf);
+
+ *status1 = (val >> 24) & 0x0F;
+ *status2 = (val >> 16) & 0x0F;
+ /*
+ * FIFO_ENTRIES contains the least significant byte, and FIFO_ENTRIES2
+ * contains the two most significant bits
+ */
+ *fifo_entries = val & 0x3FF;
+
+ return ret;
+}
+
+static void adxl372_arrange_axis_data(struct adxl372_state *st, __be16 *sample)
+{
+ __be16 axis_sample[3] = { };
+ int i = 0;
+
+ if (ADXL372_X_AXIS_EN(st->fifo_axis_mask))
+ axis_sample[i++] = sample[0];
+ if (ADXL372_Y_AXIS_EN(st->fifo_axis_mask))
+ axis_sample[i++] = sample[1];
+ if (ADXL372_Z_AXIS_EN(st->fifo_axis_mask))
+ axis_sample[i++] = sample[2];
+
+ memcpy(sample, axis_sample, 3 * sizeof(__be16));
+}
+
+static void adxl372_push_event(struct iio_dev *indio_dev, s64 timestamp, u8 status2)
+{
+ unsigned int ev_dir = IIO_EV_DIR_NONE;
+
+ if (ADXL372_STATUS_2_ACT(status2))
+ ev_dir = IIO_EV_DIR_RISING;
+
+ if (ADXL372_STATUS_2_INACT(status2))
+ ev_dir = IIO_EV_DIR_FALLING;
+
+ if (ev_dir != IIO_EV_DIR_NONE)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_THRESH, ev_dir),
+ timestamp);
+}
+
+static irqreturn_t adxl372_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct adxl372_state *st = iio_priv(indio_dev);
+ u8 status1, status2;
+ u16 fifo_entries;
+ int i, ret;
+
+ ret = adxl372_get_status(st, &status1, &status2, &fifo_entries);
+ if (ret < 0)
+ goto err;
+
+ adxl372_push_event(indio_dev, iio_get_time_ns(indio_dev), status2);
+
+ if (st->fifo_mode != ADXL372_FIFO_BYPASSED &&
+ ADXL372_STATUS_1_FIFO_FULL(status1)) {
+ /*
+ * When reading data from multiple axes from the FIFO,
+ * to ensure that data is not overwritten and stored out
+ * of order at least one sample set must be left in the
+ * FIFO after every read.
+ */
+ fifo_entries -= st->fifo_set_size;
+
+ /* Read data from the FIFO */
+ ret = regmap_noinc_read(st->regmap, ADXL372_FIFO_DATA,
+ st->fifo_buf,
+ fifo_entries * sizeof(u16));
+ if (ret < 0)
+ goto err;
+
+ /* Each sample is 2 bytes */
+ for (i = 0; i < fifo_entries; i += st->fifo_set_size) {
+ /* filter peak detection data */
+ if (st->peak_fifo_mode_en)
+ adxl372_arrange_axis_data(st, &st->fifo_buf[i]);
+ iio_push_to_buffers(indio_dev, &st->fifo_buf[i]);
+ }
+ }
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+static int adxl372_setup(struct adxl372_state *st)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADXL372_DEVID, &regval);
+ if (ret < 0)
+ return ret;
+
+ if (regval != ADXL372_DEVID_VAL) {
+ dev_err(st->dev, "Invalid chip id %x\n", regval);
+ return -ENODEV;
+ }
+
+ /*
+ * Perform a software reset to make sure the device is in a consistent
+ * state after start up.
+ */
+ ret = regmap_write(st->regmap, ADXL372_RESET, ADXL372_RESET_CODE);
+ if (ret < 0)
+ return ret;
+
+ ret = adxl372_set_op_mode(st, ADXL372_STANDBY);
+ if (ret < 0)
+ return ret;
+
+ /* Set threshold for activity detection to 1g */
+ ret = adxl372_set_activity_threshold(st, ADXL372_ACTIVITY,
+ true, true, 1000);
+ if (ret < 0)
+ return ret;
+
+ /* Set threshold for inactivity detection to 100mg */
+ ret = adxl372_set_activity_threshold(st, ADXL372_INACTIVITY,
+ true, true, 100);
+ if (ret < 0)
+ return ret;
+
+ /* Set activity processing in Looped mode */
+ ret = adxl372_set_act_proc_mode(st, ADXL372_LOOPED);
+ if (ret < 0)
+ return ret;
+
+ ret = adxl372_set_odr(st, st->chip_info->max_odr);
+ if (ret < 0)
+ return ret;
+
+ ret = adxl372_set_bandwidth(st, ADXL372_BW_3200HZ);
+ if (ret < 0)
+ return ret;
+
+ /* Set activity timer to 1ms */
+ ret = adxl372_set_activity_time_ms(st, 1);
+ if (ret < 0)
+ return ret;
+
+ /* Set inactivity timer to 10s */
+ ret = adxl372_set_inactivity_time_ms(st, 10000);
+ if (ret < 0)
+ return ret;
+
+ /* Set the mode of operation to full bandwidth measurement mode */
+ return adxl372_set_op_mode(st, ADXL372_FULL_BW_MEASUREMENT);
+}
+
+static int adxl372_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int writeval,
+ unsigned int *readval)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+ else
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static int adxl372_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = adxl372_read_axis(st, chan->address);
+ iio_device_release_direct(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ *val = sign_extend32(ret >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = ADXL372_USCALE;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = st->chip_info->samp_freq_tbl[st->odr];
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ *val = st->chip_info->bw_freq_tbl[st->bw];
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int adxl372_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+ int odr_index, bw_index, ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ odr_index = adxl372_find_closest_match(st->chip_info->samp_freq_tbl,
+ st->chip_info->num_freqs,
+ val);
+ ret = adxl372_set_odr(st, odr_index);
+ if (ret < 0)
+ return ret;
+ /* Recalculate activity time as the timer period depends on ODR */
+ ret = adxl372_set_activity_time_ms(st, st->act_time_ms);
+ if (ret < 0)
+ return ret;
+ /* Recalculate inactivity time as the timer period depends on ODR */
+ ret = adxl372_set_inactivity_time_ms(st, st->inact_time_ms);
+ if (ret < 0)
+ return ret;
+ /*
+ * The maximum bandwidth is constrained to at most half of
+ * the ODR to ensure that the Nyquist criteria is not violated
+ */
+ if (st->bw > odr_index)
+ ret = adxl372_set_bandwidth(st, odr_index);
+
+ return ret;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ bw_index = adxl372_find_closest_match(st->chip_info->bw_freq_tbl,
+ st->chip_info->num_freqs,
+ val);
+ return adxl372_set_bandwidth(st, bw_index);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl372_read_event_value(struct iio_dev *indio_dev, const struct iio_chan_spec *chan,
+ enum iio_event_type type, enum iio_event_direction dir,
+ enum iio_event_info info, int *val, int *val2)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+ unsigned int addr;
+ u16 raw_value;
+ int ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ addr = ADXL372_X_THRESH_ACT_H + 2 * chan->scan_index;
+ ret = adxl372_read_threshold_value(indio_dev, addr, &raw_value);
+ if (ret < 0)
+ return ret;
+ *val = raw_value * ADXL372_USCALE;
+ *val2 = 1000000;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ addr = ADXL372_X_THRESH_INACT_H + 2 * chan->scan_index;
+ ret = adxl372_read_threshold_value(indio_dev, addr, &raw_value);
+ if (ret < 0)
+ return ret;
+ *val = raw_value * ADXL372_USCALE;
+ *val2 = 1000000;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ *val = st->act_time_ms;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ *val = st->inact_time_ms;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl372_write_event_value(struct iio_dev *indio_dev, const struct iio_chan_spec *chan,
+ enum iio_event_type type, enum iio_event_direction dir,
+ enum iio_event_info info, int val, int val2)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+ unsigned int val_ms;
+ unsigned int addr;
+ u16 raw_val;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ raw_val = DIV_ROUND_UP(val * 1000000, ADXL372_USCALE);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ addr = ADXL372_X_THRESH_ACT_H + 2 * chan->scan_index;
+ return adxl372_write_threshold_value(indio_dev, addr, raw_val);
+ case IIO_EV_DIR_FALLING:
+ addr = ADXL372_X_THRESH_INACT_H + 2 * chan->scan_index;
+ return adxl372_write_threshold_value(indio_dev, addr, raw_val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ val_ms = val * 1000 + DIV_ROUND_UP(val2, 1000);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl372_set_activity_time_ms(st, val_ms);
+ case IIO_EV_DIR_FALLING:
+ return adxl372_set_inactivity_time_ms(st, val_ms);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl372_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 adxl372_state *st = iio_priv(indio_dev);
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return FIELD_GET(ADXL372_INT1_MAP_ACT_MSK, st->int1_bitmask);
+ case IIO_EV_DIR_FALLING:
+ return FIELD_GET(ADXL372_INT1_MAP_INACT_MSK, st->int1_bitmask);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl372_write_event_config(struct iio_dev *indio_dev, const struct iio_chan_spec *chan,
+ enum iio_event_type type, enum iio_event_direction dir,
+ bool state)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ set_mask_bits(&st->int1_bitmask, ADXL372_INT1_MAP_ACT_MSK,
+ ADXL372_INT1_MAP_ACT_MODE(state));
+ break;
+ case IIO_EV_DIR_FALLING:
+ set_mask_bits(&st->int1_bitmask, ADXL372_INT1_MAP_INACT_MSK,
+ ADXL372_INT1_MAP_INACT_MODE(state));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return adxl372_set_interrupts(st, st->int1_bitmask, 0);
+}
+
+static ssize_t adxl372_get_fifo_enabled(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl372_state *st = iio_priv(indio_dev);
+
+ return sprintf(buf, "%d\n", st->fifo_mode);
+}
+
+static ssize_t adxl372_get_fifo_watermark(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl372_state *st = iio_priv(indio_dev);
+
+ return sprintf(buf, "%d\n", st->watermark);
+}
+
+IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1");
+IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max,
+ __stringify(ADXL372_FIFO_SIZE));
+static IIO_DEVICE_ATTR(hwfifo_watermark, 0444,
+ adxl372_get_fifo_watermark, NULL, 0);
+static IIO_DEVICE_ATTR(hwfifo_enabled, 0444,
+ adxl372_get_fifo_enabled, NULL, 0);
+
+static const struct iio_dev_attr *adxl372_fifo_attributes[] = {
+ &iio_dev_attr_hwfifo_watermark_min,
+ &iio_dev_attr_hwfifo_watermark_max,
+ &iio_dev_attr_hwfifo_watermark,
+ &iio_dev_attr_hwfifo_enabled,
+ NULL,
+};
+
+static int adxl372_set_watermark(struct iio_dev *indio_dev, unsigned int val)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+
+ if (val > ADXL372_FIFO_SIZE)
+ val = ADXL372_FIFO_SIZE;
+
+ st->watermark = val;
+
+ return 0;
+}
+
+static int adxl372_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+ unsigned int mask;
+ int i, ret;
+
+ st->int1_bitmask |= ADXL372_INT1_MAP_FIFO_FULL_MSK;
+ ret = adxl372_set_interrupts(st, st->int1_bitmask, 0);
+ if (ret < 0)
+ return ret;
+
+ mask = *indio_dev->active_scan_mask;
+
+ for (i = 0; i < ARRAY_SIZE(adxl372_axis_lookup_table); i++) {
+ if (mask == adxl372_axis_lookup_table[i].bits)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(adxl372_axis_lookup_table))
+ return -EINVAL;
+
+ st->fifo_format = adxl372_axis_lookup_table[i].fifo_format;
+ st->fifo_axis_mask = adxl372_axis_lookup_table[i].bits;
+ st->fifo_set_size = bitmap_weight(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+
+ /* Configure the FIFO to store sets of impact event peak. */
+ if (st->peak_fifo_mode_en) {
+ st->fifo_set_size = 3;
+ st->fifo_format = ADXL372_XYZ_PEAK_FIFO;
+ }
+
+ /*
+ * The 512 FIFO samples can be allotted in several ways, such as:
+ * 170 sample sets of concurrent 3-axis data
+ * 256 sample sets of concurrent 2-axis data (user selectable)
+ * 512 sample sets of single-axis data
+ * 170 sets of impact event peak (x, y, z)
+ */
+ if ((st->watermark * st->fifo_set_size) > ADXL372_FIFO_SIZE)
+ st->watermark = (ADXL372_FIFO_SIZE / st->fifo_set_size);
+
+ st->fifo_mode = ADXL372_FIFO_STREAMED;
+
+ ret = adxl372_configure_fifo(st);
+ if (ret < 0) {
+ st->fifo_mode = ADXL372_FIFO_BYPASSED;
+ st->int1_bitmask &= ~ADXL372_INT1_MAP_FIFO_FULL_MSK;
+ adxl372_set_interrupts(st, st->int1_bitmask, 0);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int adxl372_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+
+ st->int1_bitmask &= ~ADXL372_INT1_MAP_FIFO_FULL_MSK;
+ adxl372_set_interrupts(st, st->int1_bitmask, 0);
+ st->fifo_mode = ADXL372_FIFO_BYPASSED;
+ adxl372_configure_fifo(st);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops adxl372_buffer_ops = {
+ .postenable = adxl372_buffer_postenable,
+ .predisable = adxl372_buffer_predisable,
+};
+
+static int adxl372_dready_trig_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct adxl372_state *st = iio_priv(indio_dev);
+
+ if (state)
+ st->int1_bitmask |= ADXL372_INT1_MAP_FIFO_FULL_MSK;
+
+ return adxl372_set_interrupts(st, st->int1_bitmask, 0);
+}
+
+static int adxl372_validate_trigger(struct iio_dev *indio_dev,
+ struct iio_trigger *trig)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+
+ if (st->dready_trig != trig && st->peak_datardy_trig != trig)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct iio_trigger_ops adxl372_trigger_ops = {
+ .validate_device = &iio_trigger_validate_own_device,
+ .set_trigger_state = adxl372_dready_trig_set_state,
+};
+
+static int adxl372_peak_dready_trig_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct adxl372_state *st = iio_priv(indio_dev);
+
+ if (state)
+ st->int1_bitmask |= ADXL372_INT1_MAP_FIFO_FULL_MSK;
+
+ st->peak_fifo_mode_en = state;
+
+ return adxl372_set_interrupts(st, st->int1_bitmask, 0);
+}
+
+static const struct iio_trigger_ops adxl372_peak_data_trigger_ops = {
+ .validate_device = &iio_trigger_validate_own_device,
+ .set_trigger_state = adxl372_peak_dready_trig_set_state,
+};
+
+static int adxl372_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = st->chip_info->samp_freq_tbl;
+ *type = IIO_VAL_INT;
+ *length = st->chip_info->num_freqs;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ *vals = st->chip_info->bw_freq_tbl;
+ *type = IIO_VAL_INT;
+ /*
+ * Bandwidth cannot exceed half the sampling frequency
+ * (Nyquist), so limit available values based on current ODR.
+ */
+ *length = st->odr + 1;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info adxl372_info = {
+ .validate_trigger = &adxl372_validate_trigger,
+ .read_raw = adxl372_read_raw,
+ .write_raw = adxl372_write_raw,
+ .read_avail = adxl372_read_avail,
+ .read_event_config = adxl372_read_event_config,
+ .write_event_config = adxl372_write_event_config,
+ .read_event_value = adxl372_read_event_value,
+ .write_event_value = adxl372_write_event_value,
+ .debugfs_reg_access = &adxl372_reg_access,
+ .hwfifo_set_watermark = adxl372_set_watermark,
+};
+
+bool adxl372_readable_noinc_reg(struct device *dev, unsigned int reg)
+{
+ return (reg == ADXL372_FIFO_DATA);
+}
+EXPORT_SYMBOL_NS_GPL(adxl372_readable_noinc_reg, "IIO_ADXL372");
+
+static int adxl372_buffer_setup(struct iio_dev *indio_dev)
+{
+ struct adxl372_state *st = iio_priv(indio_dev);
+ struct device *dev = st->dev;
+ int ret;
+
+ 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)
+ return ret;
+
+ if (!st->irq)
+ return 0;
+
+ st->dready_trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!st->dready_trig)
+ return -ENOMEM;
+
+ st->peak_datardy_trig = devm_iio_trigger_alloc(dev, "%s-dev%d-peak",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!st->peak_datardy_trig)
+ return -ENOMEM;
+
+ st->dready_trig->ops = &adxl372_trigger_ops;
+ st->peak_datardy_trig->ops = &adxl372_peak_data_trigger_ops;
+ iio_trigger_set_drvdata(st->dready_trig, indio_dev);
+ iio_trigger_set_drvdata(st->peak_datardy_trig, indio_dev);
+ ret = devm_iio_trigger_register(dev, st->dready_trig);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_trigger_register(dev, st->peak_datardy_trig);
+ if (ret)
+ return ret;
+
+ indio_dev->trig = iio_trigger_get(st->dready_trig);
+
+ return devm_request_irq(dev, st->irq,
+ iio_trigger_generic_data_rdy_poll,
+ IRQF_TRIGGER_RISING | IRQF_NO_THREAD,
+ indio_dev->name, st->dready_trig);
+}
+
+int adxl372_probe(struct device *dev, struct regmap *regmap,
+ int irq, const struct adxl372_chip_info *chip_info)
+{
+ struct iio_dev *indio_dev;
+ struct adxl372_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ dev_set_drvdata(dev, indio_dev);
+
+ st->dev = dev;
+ st->regmap = regmap;
+ st->irq = irq;
+ st->chip_info = chip_info;
+
+ ret = devm_mutex_init(dev, &st->threshold_m);
+ if (ret < 0)
+ return ret;
+
+ indio_dev->channels = adxl372_channels;
+ indio_dev->num_channels = ARRAY_SIZE(adxl372_channels);
+ indio_dev->name = chip_info->name;
+ indio_dev->info = &adxl372_info;
+
+ if (chip_info->fifo_supported) {
+ indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
+ indio_dev->available_scan_masks = adxl372_channel_masks;
+ } else {
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ }
+
+ ret = adxl372_setup(st);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "ADXL372 setup failed\n");
+
+ if (chip_info->fifo_supported) {
+ ret = adxl372_buffer_setup(indio_dev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(adxl372_probe, "IIO_ADXL372");
+
+MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL371/ADXL372 3-axis accelerometer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/adxl372.h b/drivers/iio/accel/adxl372.h
new file mode 100644
index 000000000..353a8b3a9
--- /dev/null
+++ b/drivers/iio/accel/adxl372.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * ADXL371/ADXL372 3-Axis Digital Accelerometer
+ *
+ * Copyright 2018 Analog Devices Inc.
+ */
+
+#ifndef _ADXL372_H_
+#define _ADXL372_H_
+
+#define ADXL372_REVID 0x03
+
+struct adxl372_chip_info {
+ const char *name;
+ const int *samp_freq_tbl;
+ const int *bw_freq_tbl;
+ unsigned int num_freqs;
+ unsigned int act_time_scale_us;
+ unsigned int act_time_scale_low_us;
+ unsigned int inact_time_scale_ms;
+ unsigned int inact_time_scale_low_ms;
+ unsigned int max_odr;
+ bool fifo_supported;
+};
+
+extern const struct adxl372_chip_info adxl371_chip_info;
+extern const struct adxl372_chip_info adxl372_chip_info;
+
+int adxl372_probe(struct device *dev, struct regmap *regmap,
+ int irq, const struct adxl372_chip_info *chip_info);
+bool adxl372_readable_noinc_reg(struct device *dev, unsigned int reg);
+
+#endif /* _ADXL372_H_ */
diff --git a/drivers/iio/accel/adxl372_i2c.c b/drivers/iio/accel/adxl372_i2c.c
new file mode 100644
index 000000000..e06ddb9c9
--- /dev/null
+++ b/drivers/iio/accel/adxl372_i2c.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ADXL371/ADXL372 3-Axis Digital Accelerometer I2C driver
+ *
+ * Copyright 2018 Analog Devices Inc.
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "adxl372.h"
+
+static const struct regmap_config adxl372_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .readable_noinc_reg = adxl372_readable_noinc_reg,
+};
+
+static int adxl372_i2c_probe(struct i2c_client *client)
+{
+ const struct adxl372_chip_info *chip_info;
+ struct regmap *regmap;
+ unsigned int regval;
+ int ret;
+
+ chip_info = i2c_get_match_data(client);
+
+ regmap = devm_regmap_init_i2c(client, &adxl372_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ ret = regmap_read(regmap, ADXL372_REVID, &regval);
+ if (ret < 0)
+ return ret;
+
+ /* Starting with the 3rd revision an I2C chip bug was fixed */
+ if (regval < 3)
+ dev_warn(&client->dev,
+ "I2C might not work properly with other devices on the bus");
+
+ return adxl372_probe(&client->dev, regmap, client->irq, chip_info);
+}
+
+static const struct i2c_device_id adxl372_i2c_id[] = {
+ { .name = "adxl371", .driver_data = (kernel_ulong_t)&adxl371_chip_info },
+ { .name = "adxl372", .driver_data = (kernel_ulong_t)&adxl372_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adxl372_i2c_id);
+
+static const struct of_device_id adxl372_of_match[] = {
+ { .compatible = "adi,adxl371", .data = &adxl371_chip_info },
+ { .compatible = "adi,adxl372", .data = &adxl372_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adxl372_of_match);
+
+static struct i2c_driver adxl372_i2c_driver = {
+ .driver = {
+ .name = "adxl372_i2c",
+ .of_match_table = adxl372_of_match,
+ },
+ .probe = adxl372_i2c_probe,
+ .id_table = adxl372_i2c_id,
+};
+
+module_i2c_driver(adxl372_i2c_driver);
+
+MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL371/ADXL372 3-axis accelerometer I2C driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_ADXL372");
diff --git a/drivers/iio/accel/adxl372_spi.c b/drivers/iio/accel/adxl372_spi.c
new file mode 100644
index 000000000..128e148c4
--- /dev/null
+++ b/drivers/iio/accel/adxl372_spi.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ADXL371/ADXL372 3-Axis Digital Accelerometer SPI driver
+ *
+ * Copyright 2018 Analog Devices Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "adxl372.h"
+
+static const struct regmap_config adxl372_spi_regmap_config = {
+ .reg_bits = 7,
+ .pad_bits = 1,
+ .val_bits = 8,
+ .read_flag_mask = BIT(0),
+ .readable_noinc_reg = adxl372_readable_noinc_reg,
+};
+
+static int adxl372_spi_probe(struct spi_device *spi)
+{
+ const struct adxl372_chip_info *chip_info;
+ struct regmap *regmap;
+
+ chip_info = spi_get_device_match_data(spi);
+
+ regmap = devm_regmap_init_spi(spi, &adxl372_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return adxl372_probe(&spi->dev, regmap, spi->irq, chip_info);
+}
+
+static const struct spi_device_id adxl372_spi_id[] = {
+ { .name = "adxl371", .driver_data = (kernel_ulong_t)&adxl371_chip_info },
+ { .name = "adxl372", .driver_data = (kernel_ulong_t)&adxl372_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adxl372_spi_id);
+
+static const struct of_device_id adxl372_of_match[] = {
+ { .compatible = "adi,adxl371", .data = &adxl371_chip_info },
+ { .compatible = "adi,adxl372", .data = &adxl372_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adxl372_of_match);
+
+static struct spi_driver adxl372_spi_driver = {
+ .driver = {
+ .name = "adxl372_spi",
+ .of_match_table = adxl372_of_match,
+ },
+ .probe = adxl372_spi_probe,
+ .id_table = adxl372_spi_id,
+};
+
+module_spi_driver(adxl372_spi_driver);
+
+MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL371/ADXL372 3-axis accelerometer SPI driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_ADXL372");
diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c
new file mode 100644
index 000000000..7dca55230
--- /dev/null
+++ b/drivers/iio/accel/adxl380.c
@@ -0,0 +1,2007 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ADXL380 3-Axis Digital Accelerometer core driver
+ *
+ * Copyright 2024 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <linux/unaligned.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>
+
+#include <linux/regulator/consumer.h>
+
+#include "adxl380.h"
+
+#define ADXL380_ID_VAL 380
+#define ADXL318_ID_VAL 380
+#define ADXL382_ID_VAL 382
+#define ADXL319_ID_VAL 382
+
+#define ADXL380_DEVID_AD_REG 0x00
+#define ADXL380_PART_ID_REG 0x02
+
+#define ADXL380_X_DATA_H_REG 0x15
+#define ADXL380_Y_DATA_H_REG 0x17
+#define ADXL380_Z_DATA_H_REG 0x19
+#define ADXL380_T_DATA_H_REG 0x1B
+
+#define ADXL380_MISC_0_REG 0x20
+#define ADXL380_XL382_MSK BIT(7)
+
+#define ADXL380_MISC_1_REG 0x21
+
+#define ADXL380_X_DSM_OFFSET_REG 0x4D
+
+#define ADXL380_ACT_INACT_CTL_REG 0x37
+#define ADXL380_INACT_EN_MSK BIT(2)
+#define ADXL380_ACT_EN_MSK BIT(0)
+
+#define ADXL380_SNSR_AXIS_EN_REG 0x38
+#define ADXL380_ACT_INACT_AXIS_EN_MSK GENMASK(2, 0)
+
+#define ADXL380_THRESH_ACT_H_REG 0x39
+#define ADXL380_TIME_ACT_H_REG 0x3B
+#define ADXL380_THRESH_INACT_H_REG 0x3E
+#define ADXL380_TIME_INACT_H_REG 0x40
+#define ADXL380_THRESH_MAX GENMASK(12, 0)
+#define ADXL380_TIME_MAX GENMASK(24, 0)
+
+#define ADXL380_FIFO_CONFIG_0_REG 0x30
+#define ADXL380_FIFO_SAMPLES_8_MSK BIT(0)
+#define ADXL380_FIFO_MODE_MSK GENMASK(5, 4)
+
+#define ADXL380_FIFO_DISABLED 0
+#define ADXL380_FIFO_NORMAL 1
+#define ADXL380_FIFO_STREAMED 2
+#define ADXL380_FIFO_TRIGGERED 3
+
+#define ADXL380_FIFO_CONFIG_1_REG 0x31
+#define ADXL380_FIFO_STATUS_0_REG 0x1E
+
+#define ADXL380_TAP_THRESH_REG 0x43
+#define ADXL380_TAP_DUR_REG 0x44
+#define ADXL380_TAP_LATENT_REG 0x45
+#define ADXL380_TAP_WINDOW_REG 0x46
+#define ADXL380_TAP_TIME_MAX GENMASK(7, 0)
+
+#define ADXL380_TAP_CFG_REG 0x47
+#define ADXL380_TAP_AXIS_MSK GENMASK(1, 0)
+
+#define ADXL380_TRIG_CFG_REG 0x49
+#define ADXL380_TRIG_CFG_DEC_2X_MSK BIT(7)
+#define ADXL380_TRIG_CFG_SINC_RATE_MSK BIT(6)
+
+#define ADXL380_FILTER_REG 0x50
+#define ADXL380_FILTER_EQ_FILT_MSK BIT(6)
+#define ADXL380_FILTER_LPF_MODE_MSK GENMASK(5, 4)
+#define ADXL380_FILTER_HPF_PATH_MSK BIT(3)
+#define ADXL380_FILTER_HPF_CORNER_MSK GENMASK(2, 0)
+
+#define ADXL380_OP_MODE_REG 0x26
+#define ADXL380_OP_MODE_RANGE_MSK GENMASK(7, 6)
+#define ADXL380_OP_MODE_MSK GENMASK(3, 0)
+#define ADXL380_OP_MODE_STANDBY 0
+#define ADXL380_OP_MODE_HEART_SOUND 1
+#define ADXL380_OP_MODE_ULP 2
+#define ADXL380_OP_MODE_VLP 3
+#define ADXL380_OP_MODE_LP 4
+#define ADXL380_OP_MODE_LP_ULP 6
+#define ADXL380_OP_MODE_LP_VLP 7
+#define ADXL380_OP_MODE_RBW 8
+#define ADXL380_OP_MODE_RBW_ULP 10
+#define ADXL380_OP_MODE_RBW_VLP 11
+#define ADXL380_OP_MODE_HP 12
+#define ADXL380_OP_MODE_HP_ULP 14
+#define ADXL380_OP_MODE_HP_VLP 15
+
+#define ADXL380_OP_MODE_4G_RANGE 0
+#define ADXL382_OP_MODE_15G_RANGE 0
+#define ADXL380_OP_MODE_8G_RANGE 1
+#define ADXL382_OP_MODE_30G_RANGE 1
+#define ADXL380_OP_MODE_16G_RANGE 2
+#define ADXL382_OP_MODE_60G_RANGE 2
+
+#define ADXL380_DIG_EN_REG 0x27
+#define ADXL380_CHAN_EN_MSK(chan) BIT(4 + (chan))
+#define ADXL380_FIFO_EN_MSK BIT(3)
+
+#define ADXL380_INT0_MAP0_REG 0x2B
+#define ADXL380_INT1_MAP0_REG 0x2D
+#define ADXL380_INT_MAP0_INACT_INT0_MSK BIT(6)
+#define ADXL380_INT_MAP0_ACT_INT0_MSK BIT(5)
+#define ADXL380_INT_MAP0_FIFO_WM_INT0_MSK BIT(3)
+
+#define ADXL380_INT0_MAP1_REG 0x2C
+#define ADXL380_INT1_MAP1_REG 0x2E
+#define ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK BIT(1)
+#define ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK BIT(0)
+
+#define ADXL380_INT0_REG 0x5D
+#define ADXL380_INT0_POL_MSK BIT(7)
+
+#define ADXL380_RESET_REG 0x2A
+#define ADXL380_FIFO_DATA 0x1D
+
+#define ADXL380_DEVID_AD_VAL 0xAD
+#define ADXL380_RESET_CODE 0x52
+
+#define ADXL380_STATUS_0_REG 0x11
+#define ADXL380_STATUS_0_FIFO_FULL_MSK BIT(1)
+#define ADXL380_STATUS_0_FIFO_WM_MSK BIT(3)
+
+#define ADXL380_STATUS_1_INACT_MSK BIT(6)
+#define ADXL380_STATUS_1_ACT_MSK BIT(5)
+#define ADXL380_STATUS_1_DOUBLE_TAP_MSK BIT(1)
+#define ADXL380_STATUS_1_SINGLE_TAP_MSK BIT(0)
+
+#define ADXL380_FIFO_SAMPLES 315UL
+
+enum adxl380_channels {
+ ADXL380_ACCEL_X,
+ ADXL380_ACCEL_Y,
+ ADXL380_ACCEL_Z,
+ ADXL380_TEMP,
+ ADXL380_CH_NUM
+};
+
+enum adxl380_axis {
+ ADXL380_X_AXIS,
+ ADXL380_Y_AXIS,
+ ADXL380_Z_AXIS,
+};
+
+enum adxl380_activity_type {
+ ADXL380_ACTIVITY,
+ ADXL380_INACTIVITY,
+};
+
+enum adxl380_tap_type {
+ ADXL380_SINGLE_TAP,
+ ADXL380_DOUBLE_TAP,
+};
+
+enum adxl380_tap_time_type {
+ ADXL380_TAP_TIME_LATENT,
+ ADXL380_TAP_TIME_WINDOW,
+};
+
+static const int adxl380_range_scale_factor_tbl[] = { 1, 2, 4 };
+
+static const unsigned int adxl380_th_reg_high_addr[2] = {
+ [ADXL380_ACTIVITY] = ADXL380_THRESH_ACT_H_REG,
+ [ADXL380_INACTIVITY] = ADXL380_THRESH_INACT_H_REG,
+};
+
+static const unsigned int adxl380_time_reg_high_addr[2] = {
+ [ADXL380_ACTIVITY] = ADXL380_TIME_ACT_H_REG,
+ [ADXL380_INACTIVITY] = ADXL380_TIME_INACT_H_REG,
+};
+
+static const unsigned int adxl380_tap_time_reg[2] = {
+ [ADXL380_TAP_TIME_LATENT] = ADXL380_TAP_LATENT_REG,
+ [ADXL380_TAP_TIME_WINDOW] = ADXL380_TAP_WINDOW_REG,
+};
+
+struct adxl380_state {
+ struct regmap *regmap;
+ struct device *dev;
+ const struct adxl380_chip_info *chip_info;
+ /*
+ * Synchronize access to members of driver state, and ensure atomicity
+ * of consecutive regmap operations.
+ */
+ struct mutex lock;
+ enum adxl380_axis tap_axis_en;
+ u8 range;
+ u8 odr;
+ u8 fifo_set_size;
+ u8 transf_buf[3];
+ u16 watermark;
+ u32 act_time_ms;
+ u32 act_threshold;
+ u32 inact_time_ms;
+ u32 inact_threshold;
+ u32 tap_latent_us;
+ u32 tap_window_us;
+ u32 tap_duration_us;
+ u32 tap_threshold;
+ int irq;
+ int int_map[2];
+ int lpf_tbl[4];
+ int hpf_tbl[7][2];
+
+ __be16 fifo_buf[ADXL380_FIFO_SAMPLES] __aligned(IIO_DMA_MINALIGN);
+};
+
+bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg)
+{
+ return reg == ADXL380_FIFO_DATA;
+}
+EXPORT_SYMBOL_NS_GPL(adxl380_readable_noinc_reg, "IIO_ADXL380");
+
+static int adxl380_act_inact_enabled(struct adxl380_state *st, bool *enabled)
+{
+ unsigned int act_inact_ctl;
+ int ret;
+
+ if (!st->chip_info->has_low_power) {
+ *enabled = false;
+ return 0;
+ }
+
+ ret = regmap_read(st->regmap, ADXL380_ACT_INACT_CTL_REG, &act_inact_ctl);
+ if (ret)
+ return ret;
+
+ *enabled = FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) ||
+ FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl);
+
+ return 0;
+}
+
+static int adxl380_set_measure_en(struct adxl380_state *st, bool en)
+{
+ int ret;
+ u8 op_mode = ADXL380_OP_MODE_STANDBY;
+
+ if (en) {
+ bool act_inact_enabled;
+
+ ret = adxl380_act_inact_enabled(st, &act_inact_enabled);
+ if (ret)
+ return ret;
+
+ /*
+ * Activity/Inactivity detection available only in VLP/ULP
+ * mode and for devices that support low power modes.
+ */
+ if (act_inact_enabled)
+ st->odr = ADXL380_ODR_VLP;
+
+ if (st->odr == ADXL380_ODR_VLP)
+ op_mode = ADXL380_OP_MODE_VLP;
+ else
+ op_mode = ADXL380_OP_MODE_HP;
+ }
+
+ return regmap_update_bits(st->regmap, ADXL380_OP_MODE_REG,
+ ADXL380_OP_MODE_MSK,
+ FIELD_PREP(ADXL380_OP_MODE_MSK, op_mode));
+}
+
+static void adxl380_scale_act_inact_thresholds(struct adxl380_state *st,
+ u8 old_range,
+ u8 new_range)
+{
+ st->act_threshold = mult_frac(st->act_threshold,
+ adxl380_range_scale_factor_tbl[old_range],
+ adxl380_range_scale_factor_tbl[new_range]);
+ st->inact_threshold = mult_frac(st->inact_threshold,
+ adxl380_range_scale_factor_tbl[old_range],
+ adxl380_range_scale_factor_tbl[new_range]);
+}
+
+static int adxl380_write_act_inact_threshold(struct adxl380_state *st,
+ enum adxl380_activity_type act,
+ unsigned int th)
+{
+ int ret;
+ u8 reg = adxl380_th_reg_high_addr[act];
+
+ if (th > ADXL380_THRESH_MAX)
+ return -EINVAL;
+
+ ret = regmap_write(st->regmap, reg + 1, th & GENMASK(7, 0));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, reg, GENMASK(2, 0), th >> 8);
+ if (ret)
+ return ret;
+
+ if (act == ADXL380_ACTIVITY)
+ st->act_threshold = th;
+ else
+ st->inact_threshold = th;
+
+ return 0;
+}
+
+static int adxl380_set_act_inact_threshold(struct iio_dev *indio_dev,
+ enum adxl380_activity_type act,
+ u16 th)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_act_inact_threshold(st, act, th);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_set_tap_threshold_value(struct iio_dev *indio_dev, u8 th)
+{
+ int ret;
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL380_TAP_THRESH_REG, th);
+ if (ret)
+ return ret;
+
+ st->tap_threshold = th;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int _adxl380_write_tap_time_us(struct adxl380_state *st,
+ enum adxl380_tap_time_type tap_time_type,
+ u32 us)
+{
+ u8 reg = adxl380_tap_time_reg[tap_time_type];
+ unsigned int reg_val;
+ int ret;
+
+ /* scale factor for tap window is 1250us / LSB */
+ reg_val = DIV_ROUND_CLOSEST(us, 1250);
+ if (reg_val > ADXL380_TAP_TIME_MAX)
+ reg_val = ADXL380_TAP_TIME_MAX;
+
+ ret = regmap_write(st->regmap, reg, reg_val);
+ if (ret)
+ return ret;
+
+ if (tap_time_type == ADXL380_TAP_TIME_WINDOW)
+ st->tap_window_us = us;
+ else
+ st->tap_latent_us = us;
+
+ return 0;
+}
+
+static int adxl380_write_tap_time_us(struct adxl380_state *st,
+ enum adxl380_tap_time_type tap_time_type, u32 us)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = _adxl380_write_tap_time_us(st, tap_time_type, us);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_write_tap_dur_us(struct iio_dev *indio_dev, u32 us)
+{
+ int ret;
+ unsigned int reg_val;
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ /* 625us per code is the scale factor of TAP_DUR register */
+ reg_val = DIV_ROUND_CLOSEST(us, 625);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL380_TAP_DUR_REG, reg_val);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_read_chn(struct adxl380_state *st, u8 addr)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_bulk_read(st->regmap, addr, &st->transf_buf, 2);
+ if (ret)
+ return ret;
+
+ return get_unaligned_be16(st->transf_buf);
+}
+
+static int adxl380_get_odr(struct adxl380_state *st, int *odr)
+{
+ *odr = st->chip_info->samp_freq_tbl[st->odr];
+
+ return 0;
+}
+
+static const int adxl380_lpf_div[] = {
+ 1, 4, 8, 16,
+};
+
+static int adxl380_fill_lpf_tbl(struct adxl380_state *st)
+{
+ int ret, i;
+ int odr;
+
+ ret = adxl380_get_odr(st, &odr);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(st->lpf_tbl); i++)
+ st->lpf_tbl[i] = DIV_ROUND_CLOSEST(odr, adxl380_lpf_div[i]);
+
+ return 0;
+}
+
+static const int adxl380_hpf_mul[] = {
+ 0, 247000, 62084, 15545, 3862, 954, 238,
+};
+
+static int adxl380_fill_hpf_tbl(struct adxl380_state *st)
+{
+ int i, ret, odr_hz;
+ u32 multiplier;
+ u64 div, rem, odr;
+
+ ret = adxl380_get_odr(st, &odr_hz);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(adxl380_hpf_mul); i++) {
+ odr = mul_u64_u32_shr(odr_hz, MEGA, 0);
+ multiplier = adxl380_hpf_mul[i];
+ div = div64_u64_rem(mul_u64_u32_shr(odr, multiplier, 0),
+ TERA * 100, &rem);
+
+ st->hpf_tbl[i][0] = div;
+ st->hpf_tbl[i][1] = div_u64(rem, MEGA * 100);
+ }
+
+ return 0;
+}
+
+static int adxl380_set_odr(struct adxl380_state *st, u8 odr)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ if (odr >= ADXL380_ODR_DSM) {
+ u8 mul = odr - ADXL380_ODR_DSM;
+ u8 field;
+
+ field = FIELD_PREP(ADXL380_TRIG_CFG_DEC_2X_MSK, mul & 1);
+ ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG,
+ ADXL380_TRIG_CFG_DEC_2X_MSK, field);
+ if (ret)
+ return ret;
+
+ field = FIELD_PREP(ADXL380_TRIG_CFG_SINC_RATE_MSK, mul >> 1);
+ ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG,
+ ADXL380_TRIG_CFG_SINC_RATE_MSK, field);
+ if (ret)
+ return ret;
+ }
+
+ st->odr = odr;
+ ret = adxl380_set_measure_en(st, true);
+ if (ret)
+ return ret;
+
+ ret = adxl380_fill_lpf_tbl(st);
+ if (ret)
+ return ret;
+
+ return adxl380_fill_hpf_tbl(st);
+}
+
+static int adxl380_find_match_1d_tbl(const int *array, unsigned int size,
+ int val)
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ if (val == array[i])
+ return i;
+ }
+
+ return size - 1;
+}
+
+static int adxl380_find_match_2d_tbl(const int (*freq_tbl)[2], int n, int val, 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 adxl380_get_lpf(struct adxl380_state *st, int *lpf)
+{
+ int ret;
+ unsigned int trig_cfg, lpf_idx;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_read(st->regmap, ADXL380_FILTER_REG, &trig_cfg);
+ if (ret)
+ return ret;
+
+ lpf_idx = FIELD_GET(ADXL380_FILTER_LPF_MODE_MSK, trig_cfg);
+
+ *lpf = st->lpf_tbl[lpf_idx];
+
+ return 0;
+}
+
+static int adxl380_set_lpf(struct adxl380_state *st, u8 lpf)
+{
+ int ret;
+ u8 eq_bypass = 0;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ if (lpf)
+ eq_bypass = 1;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
+ ADXL380_FILTER_EQ_FILT_MSK,
+ FIELD_PREP(ADXL380_FILTER_EQ_FILT_MSK, eq_bypass));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
+ ADXL380_FILTER_LPF_MODE_MSK,
+ FIELD_PREP(ADXL380_FILTER_LPF_MODE_MSK, lpf));
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_get_hpf(struct adxl380_state *st, int *hpf_int, int *hpf_frac)
+{
+ int ret;
+ unsigned int trig_cfg, hpf_idx;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_read(st->regmap, ADXL380_FILTER_REG, &trig_cfg);
+ if (ret)
+ return ret;
+
+ hpf_idx = FIELD_GET(ADXL380_FILTER_HPF_CORNER_MSK, trig_cfg);
+
+ *hpf_int = st->hpf_tbl[hpf_idx][0];
+ *hpf_frac = st->hpf_tbl[hpf_idx][1];
+
+ return 0;
+}
+
+static int adxl380_set_hpf(struct adxl380_state *st, u8 hpf)
+{
+ int ret;
+ u8 hpf_path = 0;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ if (hpf)
+ hpf_path = 1;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
+ ADXL380_FILTER_HPF_PATH_MSK,
+ FIELD_PREP(ADXL380_FILTER_HPF_PATH_MSK, hpf_path));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG,
+ ADXL380_FILTER_HPF_CORNER_MSK,
+ FIELD_PREP(ADXL380_FILTER_HPF_CORNER_MSK, hpf));
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int _adxl380_set_act_inact_time_ms(struct adxl380_state *st,
+ enum adxl380_activity_type act,
+ u32 ms)
+{
+ u8 reg = adxl380_time_reg_high_addr[act];
+ unsigned int reg_val;
+ int ret;
+
+ /* 500us per code is the scale factor of TIME_ACT / TIME_INACT registers */
+ reg_val = min(DIV_ROUND_CLOSEST(ms * 1000, 500), ADXL380_TIME_MAX);
+
+ put_unaligned_be24(reg_val, &st->transf_buf[0]);
+
+ ret = regmap_bulk_write(st->regmap, reg, st->transf_buf, sizeof(st->transf_buf));
+ if (ret)
+ return ret;
+
+ if (act == ADXL380_ACTIVITY)
+ st->act_time_ms = ms;
+ else
+ st->inact_time_ms = ms;
+
+ return 0;
+}
+
+static int adxl380_set_act_inact_time_ms(struct adxl380_state *st,
+ enum adxl380_activity_type act,
+ u32 ms)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = _adxl380_set_act_inact_time_ms(st, act, ms);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_set_range(struct adxl380_state *st, u8 range)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_OP_MODE_REG,
+ ADXL380_OP_MODE_RANGE_MSK,
+ FIELD_PREP(ADXL380_OP_MODE_RANGE_MSK, range));
+
+ if (ret)
+ return ret;
+
+ adxl380_scale_act_inact_thresholds(st, st->range, range);
+
+ /* Activity thresholds depend on range */
+ ret = adxl380_write_act_inact_threshold(st, ADXL380_ACTIVITY,
+ st->act_threshold);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_act_inact_threshold(st, ADXL380_INACTIVITY,
+ st->inact_threshold);
+ if (ret)
+ return ret;
+
+ st->range = range;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_write_act_inact_en(struct adxl380_state *st,
+ enum adxl380_activity_type type,
+ bool en)
+{
+ if (type == ADXL380_ACTIVITY)
+ return regmap_update_bits(st->regmap, ADXL380_ACT_INACT_CTL_REG,
+ ADXL380_ACT_EN_MSK,
+ FIELD_PREP(ADXL380_ACT_EN_MSK, en));
+
+ return regmap_update_bits(st->regmap, ADXL380_ACT_INACT_CTL_REG,
+ ADXL380_INACT_EN_MSK,
+ FIELD_PREP(ADXL380_INACT_EN_MSK, en));
+}
+
+static int adxl380_read_act_inact_int(struct adxl380_state *st,
+ enum adxl380_activity_type type,
+ bool *en)
+{
+ int ret;
+ unsigned int reg_val;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_read(st->regmap, st->int_map[0], &reg_val);
+ if (ret)
+ return ret;
+
+ if (type == ADXL380_ACTIVITY)
+ *en = FIELD_GET(ADXL380_INT_MAP0_ACT_INT0_MSK, reg_val);
+ else
+ *en = FIELD_GET(ADXL380_INT_MAP0_INACT_INT0_MSK, reg_val);
+
+ return 0;
+}
+
+static int adxl380_write_act_inact_int(struct adxl380_state *st,
+ enum adxl380_activity_type act,
+ bool en)
+{
+ if (act == ADXL380_ACTIVITY)
+ return regmap_update_bits(st->regmap, st->int_map[0],
+ ADXL380_INT_MAP0_ACT_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP0_ACT_INT0_MSK, en));
+
+ return regmap_update_bits(st->regmap, st->int_map[0],
+ ADXL380_INT_MAP0_INACT_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP0_INACT_INT0_MSK, en));
+}
+
+static int adxl380_act_inact_config(struct adxl380_state *st,
+ enum adxl380_activity_type type,
+ bool en)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_act_inact_en(st, type, en);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_act_inact_int(st, type, en);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_write_tap_axis(struct adxl380_state *st,
+ enum adxl380_axis axis)
+{
+ int ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_TAP_CFG_REG,
+ ADXL380_TAP_AXIS_MSK,
+ FIELD_PREP(ADXL380_TAP_AXIS_MSK, axis));
+
+ if (ret)
+ return ret;
+
+ st->tap_axis_en = axis;
+
+ return 0;
+}
+
+static int adxl380_read_tap_int(struct adxl380_state *st, enum adxl380_tap_type type, bool *en)
+{
+ int ret;
+ unsigned int reg_val;
+
+ ret = regmap_read(st->regmap, st->int_map[1], &reg_val);
+ if (ret)
+ return ret;
+
+ if (type == ADXL380_SINGLE_TAP)
+ *en = FIELD_GET(ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK, reg_val);
+ else
+ *en = FIELD_GET(ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK, reg_val);
+
+ return 0;
+}
+
+static int adxl380_write_tap_int(struct adxl380_state *st, enum adxl380_tap_type type, bool en)
+{
+ if (type == ADXL380_SINGLE_TAP)
+ return regmap_update_bits(st->regmap, st->int_map[1],
+ ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK, en));
+
+ return regmap_update_bits(st->regmap, st->int_map[1],
+ ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK, en));
+}
+
+static int adxl380_tap_config(struct adxl380_state *st,
+ enum adxl380_axis axis,
+ enum adxl380_tap_type type,
+ bool en)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_tap_axis(st, axis);
+ if (ret)
+ return ret;
+
+ ret = adxl380_write_tap_int(st, type, en);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_set_fifo_samples(struct adxl380_state *st)
+{
+ int ret;
+ u16 fifo_samples = st->watermark * st->fifo_set_size;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FIFO_CONFIG_0_REG,
+ ADXL380_FIFO_SAMPLES_8_MSK,
+ FIELD_PREP(ADXL380_FIFO_SAMPLES_8_MSK,
+ !!(fifo_samples & BIT(8))));
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, ADXL380_FIFO_CONFIG_1_REG,
+ fifo_samples & 0xFF);
+}
+
+static int adxl380_get_status(struct adxl380_state *st, u8 *status0, u8 *status1)
+{
+ int ret;
+
+ /* STATUS0, STATUS1 are adjacent regs */
+ ret = regmap_bulk_read(st->regmap, ADXL380_STATUS_0_REG,
+ &st->transf_buf, 2);
+ if (ret)
+ return ret;
+
+ *status0 = st->transf_buf[0];
+ *status1 = st->transf_buf[1];
+
+ return 0;
+}
+
+static int adxl380_get_fifo_entries(struct adxl380_state *st, u16 *fifo_entries)
+{
+ int ret;
+
+ ret = regmap_bulk_read(st->regmap, ADXL380_FIFO_STATUS_0_REG,
+ &st->transf_buf, 2);
+ if (ret)
+ return ret;
+
+ *fifo_entries = st->transf_buf[0] | ((BIT(0) & st->transf_buf[1]) << 8);
+
+ return 0;
+}
+
+static void adxl380_push_event(struct iio_dev *indio_dev, s64 timestamp,
+ u8 status1)
+{
+ if (FIELD_GET(ADXL380_STATUS_1_ACT_MSK, status1))
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING),
+ timestamp);
+
+ if (FIELD_GET(ADXL380_STATUS_1_INACT_MSK, status1))
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING),
+ timestamp);
+ if (FIELD_GET(ADXL380_STATUS_1_SINGLE_TAP_MSK, status1))
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_GESTURE, IIO_EV_DIR_SINGLETAP),
+ timestamp);
+
+ if (FIELD_GET(ADXL380_STATUS_1_DOUBLE_TAP_MSK, status1))
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_GESTURE, IIO_EV_DIR_DOUBLETAP),
+ timestamp);
+}
+
+static irqreturn_t adxl380_irq_handler(int irq, void *p)
+{
+ struct iio_dev *indio_dev = p;
+ struct adxl380_state *st = iio_priv(indio_dev);
+ u8 status0, status1;
+ u16 fifo_entries;
+ int i;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_get_status(st, &status0, &status1);
+ if (ret)
+ return IRQ_HANDLED;
+
+ adxl380_push_event(indio_dev, iio_get_time_ns(indio_dev), status1);
+
+ if (!FIELD_GET(ADXL380_STATUS_0_FIFO_WM_MSK, status0))
+ return IRQ_HANDLED;
+
+ ret = adxl380_get_fifo_entries(st, &fifo_entries);
+ if (ret)
+ return IRQ_HANDLED;
+
+ fifo_entries = rounddown(fifo_entries, st->fifo_set_size);
+ ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA, &st->fifo_buf,
+ sizeof(*st->fifo_buf) * fifo_entries);
+ if (ret)
+ return IRQ_HANDLED;
+ for (i = 0; i < fifo_entries; i += st->fifo_set_size)
+ iio_push_to_buffers(indio_dev, &st->fifo_buf[i]);
+
+ return IRQ_HANDLED;
+}
+
+static int adxl380_write_calibbias_value(struct adxl380_state *st,
+ unsigned long chan_addr,
+ s8 calibbias)
+{
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, ADXL380_X_DSM_OFFSET_REG + chan_addr, calibbias);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_read_calibbias_value(struct adxl380_state *st,
+ unsigned long chan_addr,
+ int *calibbias)
+{
+ int ret;
+ unsigned int reg_val;
+
+ guard(mutex)(&st->lock);
+
+ ret = regmap_read(st->regmap, ADXL380_X_DSM_OFFSET_REG + chan_addr, &reg_val);
+ if (ret)
+ return ret;
+
+ *calibbias = sign_extend32(reg_val, 7);
+
+ return 0;
+}
+
+static ssize_t hwfifo_watermark_min_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "1\n");
+}
+
+static ssize_t hwfifo_watermark_max_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%lu\n", ADXL380_FIFO_SAMPLES);
+}
+
+static ssize_t adxl380_get_fifo_watermark(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ return sysfs_emit(buf, "%d\n", st->watermark);
+}
+
+static ssize_t adxl380_get_fifo_enabled(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int ret;
+ unsigned int reg_val;
+
+ ret = regmap_read(st->regmap, ADXL380_DIG_EN_REG, &reg_val);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%lu\n",
+ FIELD_GET(ADXL380_FIFO_EN_MSK, reg_val));
+}
+
+static IIO_DEVICE_ATTR_RO(hwfifo_watermark_min, 0);
+static IIO_DEVICE_ATTR_RO(hwfifo_watermark_max, 0);
+static IIO_DEVICE_ATTR(hwfifo_watermark, 0444,
+ adxl380_get_fifo_watermark, NULL, 0);
+static IIO_DEVICE_ATTR(hwfifo_enabled, 0444,
+ adxl380_get_fifo_enabled, NULL, 0);
+
+static const struct iio_dev_attr *adxl380_fifo_attributes[] = {
+ &iio_dev_attr_hwfifo_watermark_min,
+ &iio_dev_attr_hwfifo_watermark_max,
+ &iio_dev_attr_hwfifo_watermark,
+ &iio_dev_attr_hwfifo_enabled,
+ NULL
+};
+
+static int adxl380_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int i;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap,
+ st->int_map[0],
+ ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, 1));
+ if (ret)
+ return ret;
+
+ for_each_clear_bit(i, indio_dev->active_scan_mask, ADXL380_CH_NUM) {
+ ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
+ ADXL380_CHAN_EN_MSK(i),
+ 0 << (4 + i));
+ if (ret)
+ return ret;
+ }
+
+ st->fifo_set_size = bitmap_weight(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+
+ if ((st->watermark * st->fifo_set_size) > ADXL380_FIFO_SAMPLES)
+ st->watermark = (ADXL380_FIFO_SAMPLES / st->fifo_set_size);
+
+ ret = adxl380_set_fifo_samples(st);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, ADXL380_FIFO_EN_MSK,
+ FIELD_PREP(ADXL380_FIFO_EN_MSK, 1));
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static int adxl380_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int ret, i;
+
+ guard(mutex)(&st->lock);
+
+ ret = adxl380_set_measure_en(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap,
+ st->int_map[0],
+ ADXL380_INT_MAP0_FIFO_WM_INT0_MSK,
+ FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, 0));
+ if (ret)
+ return ret;
+
+ for (i = 0; i < indio_dev->num_channels; i++) {
+ ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
+ ADXL380_CHAN_EN_MSK(i),
+ 1 << (4 + i));
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, ADXL380_FIFO_EN_MSK,
+ FIELD_PREP(ADXL380_FIFO_EN_MSK, 0));
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+static const struct iio_buffer_setup_ops adxl380_buffer_ops = {
+ .postenable = adxl380_buffer_postenable,
+ .predisable = adxl380_buffer_predisable,
+};
+
+static int adxl380_samp_freq_avail(struct adxl380_state *st, const int **vals,
+ int *length)
+{
+ bool act_inact_enabled;
+ int ret;
+
+ if (!st->chip_info->has_low_power) {
+ *vals = st->chip_info->samp_freq_tbl + ADXL380_ODR_DSM;
+ *length = ADXL380_ODR_MAX - ADXL380_ODR_DSM;
+ return 0;
+ }
+
+ ret = adxl380_act_inact_enabled(st, &act_inact_enabled);
+ if (ret)
+ return 0;
+
+ /*
+ * Motion detection is only functional in low-power mode, and this
+ * affects the available sampling frequencies.
+ */
+ *vals = st->chip_info->samp_freq_tbl;
+ *length = act_inact_enabled ? ADXL380_ODR_DSM : ADXL380_ODR_MAX;
+
+ return 0;
+}
+
+static int adxl380_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = adxl380_read_chn(st, chan->address);
+ iio_device_release_direct(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ *val = sign_extend32(ret >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ scoped_guard(mutex, &st->lock) {
+ *val = st->chip_info->scale_tbl[st->range][0];
+ *val2 = st->chip_info->scale_tbl[st->range][1];
+ }
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_TEMP:
+ /* 10.2 LSB / Degree Celsius */
+ *val = 10000;
+ *val2 = 102;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_TEMP:
+ *val = st->chip_info->temp_offset;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_CALIBBIAS:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ ret = adxl380_read_calibbias_value(st, chan->scan_index, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = adxl380_get_odr(st, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ ret = adxl380_get_lpf(st, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+ ret = adxl380_get_hpf(st, val, val2);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ return -EINVAL;
+}
+
+static int adxl380_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int ret;
+
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *vals = (const int *)st->chip_info->scale_tbl;
+ *type = IIO_VAL_INT_PLUS_NANO;
+ *length = ARRAY_SIZE(st->chip_info->scale_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = adxl380_samp_freq_avail(st, vals, length);
+ if (ret)
+ return ret;
+
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ *vals = (const int *)st->lpf_tbl;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(st->lpf_tbl);
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+ *vals = (const int *)st->hpf_tbl;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ /* Values are stored in a 2D matrix */
+ *length = ARRAY_SIZE(st->hpf_tbl) * 2;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl380_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ const int *freq_vals;
+ int odr_index, lpf_index, hpf_index, range_index, freq_count, ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = adxl380_samp_freq_avail(st, &freq_vals, &freq_count);
+ if (ret)
+ return ret;
+
+ odr_index = adxl380_find_match_1d_tbl(freq_vals, freq_count,
+ val);
+ return adxl380_set_odr(st, odr_index);
+ case IIO_CHAN_INFO_CALIBBIAS:
+ return adxl380_write_calibbias_value(st, chan->scan_index, val);
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ lpf_index = adxl380_find_match_1d_tbl(st->lpf_tbl,
+ ARRAY_SIZE(st->lpf_tbl),
+ val);
+ return adxl380_set_lpf(st, lpf_index);
+ case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+ hpf_index = adxl380_find_match_2d_tbl(st->hpf_tbl,
+ ARRAY_SIZE(st->hpf_tbl),
+ val, val2);
+ if (hpf_index < 0)
+ return hpf_index;
+ return adxl380_set_hpf(st, hpf_index);
+ case IIO_CHAN_INFO_SCALE:
+ range_index = adxl380_find_match_2d_tbl(st->chip_info->scale_tbl,
+ ARRAY_SIZE(st->chip_info->scale_tbl),
+ val, val2);
+ if (range_index < 0)
+ return range_index;
+ return adxl380_set_range(st, range_index);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl380_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long info)
+{
+ switch (info) {
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
+static int adxl380_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 adxl380_state *st = iio_priv(indio_dev);
+ int ret;
+ bool int_en;
+ bool tap_axis_en = false;
+
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ tap_axis_en = st->tap_axis_en == ADXL380_X_AXIS;
+ break;
+ case IIO_MOD_Y:
+ tap_axis_en = st->tap_axis_en == ADXL380_Y_AXIS;
+ break;
+ case IIO_MOD_Z:
+ tap_axis_en = st->tap_axis_en == ADXL380_Z_AXIS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ ret = adxl380_read_act_inact_int(st, ADXL380_ACTIVITY, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ case IIO_EV_DIR_FALLING:
+ ret = adxl380_read_act_inact_int(st, ADXL380_INACTIVITY, &int_en);
+ if (ret)
+ return ret;
+ return int_en;
+ case IIO_EV_DIR_SINGLETAP:
+ ret = adxl380_read_tap_int(st, ADXL380_SINGLE_TAP, &int_en);
+ if (ret)
+ return ret;
+ return int_en && tap_axis_en;
+ case IIO_EV_DIR_DOUBLETAP:
+ ret = adxl380_read_tap_int(st, ADXL380_DOUBLE_TAP, &int_en);
+ if (ret)
+ return ret;
+ return int_en && tap_axis_en;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl380_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ enum adxl380_axis axis;
+
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ axis = ADXL380_X_AXIS;
+ break;
+ case IIO_MOD_Y:
+ axis = ADXL380_Y_AXIS;
+ break;
+ case IIO_MOD_Z:
+ axis = ADXL380_Z_AXIS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl380_act_inact_config(st, ADXL380_ACTIVITY, state);
+ case IIO_EV_DIR_FALLING:
+ return adxl380_act_inact_config(st, ADXL380_INACTIVITY, state);
+ case IIO_EV_DIR_SINGLETAP:
+ return adxl380_tap_config(st, axis, ADXL380_SINGLE_TAP, state);
+ case IIO_EV_DIR_DOUBLETAP:
+ return adxl380_tap_config(st, axis, ADXL380_DOUBLE_TAP, state);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl380_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (info) {
+ case IIO_EV_INFO_VALUE: {
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ *val = st->act_threshold;
+ return IIO_VAL_INT;
+ case IIO_EV_DIR_FALLING:
+ *val = st->inact_threshold;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ }
+ case IIO_EV_INFO_PERIOD:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ *val = st->act_time_ms;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_DIR_FALLING:
+ *val = st->inact_time_ms;
+ *val2 = 1000;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ *val = st->tap_threshold;
+ return IIO_VAL_INT;
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ *val = st->tap_window_us;
+ *val2 = 1000000;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ *val = st->tap_latent_us;
+ *val2 = 1000000;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adxl380_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type, enum iio_event_direction dir,
+ enum iio_event_info info, int val, int val2)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ u32 val_ms, val_us;
+
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl380_set_act_inact_threshold(indio_dev,
+ ADXL380_ACTIVITY, val);
+ case IIO_EV_DIR_FALLING:
+ return adxl380_set_act_inact_threshold(indio_dev,
+ ADXL380_INACTIVITY, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ val_ms = val * 1000 + DIV_ROUND_UP(val2, 1000);
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return adxl380_set_act_inact_time_ms(st,
+ ADXL380_ACTIVITY, val_ms);
+ case IIO_EV_DIR_FALLING:
+ return adxl380_set_act_inact_time_ms(st,
+ ADXL380_INACTIVITY, val_ms);
+ default:
+ return -EINVAL;
+ }
+
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ return adxl380_set_tap_threshold_value(indio_dev, val);
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ val_us = val * 1000000 + val2;
+ return adxl380_write_tap_time_us(st,
+ ADXL380_TAP_TIME_WINDOW,
+ val_us);
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ val_us = val * 1000000 + val2;
+ return adxl380_write_tap_time_us(st,
+ ADXL380_TAP_TIME_LATENT,
+ val_us);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t in_accel_gesture_tap_maxtomin_time_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int vals[2];
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ vals[0] = st->tap_duration_us;
+ vals[1] = MICRO;
+
+ return iio_format_value(buf, IIO_VAL_FRACTIONAL, 2, vals);
+}
+
+static ssize_t in_accel_gesture_tap_maxtomin_time_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adxl380_state *st = iio_priv(indio_dev);
+ int ret, val_int, val_fract_us;
+
+ guard(mutex)(&st->lock);
+
+ ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract_us);
+ if (ret)
+ return ret;
+
+ /* maximum value is 255 * 625 us = 0.159375 seconds */
+ if (val_int || val_fract_us > 159375 || val_fract_us < 0)
+ return -EINVAL;
+
+ ret = adxl380_write_tap_dur_us(indio_dev, val_fract_us);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_maxtomin_time, 0);
+
+static struct attribute *adxl380_event_attributes[] = {
+ &iio_dev_attr_in_accel_gesture_tap_maxtomin_time.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group adxl380_event_attribute_group = {
+ .attrs = adxl380_event_attributes,
+};
+
+static int adxl380_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int writeval,
+ unsigned int *readval)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static int adxl380_set_watermark(struct iio_dev *indio_dev, unsigned int val)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ st->watermark = min(val, ADXL380_FIFO_SAMPLES);
+
+ return 0;
+}
+
+static const struct iio_info adxl318_info = {
+ .read_raw = adxl380_read_raw,
+ .read_avail = &adxl380_read_avail,
+ .write_raw = adxl380_write_raw,
+ .write_raw_get_fmt = adxl380_write_raw_get_fmt,
+ .debugfs_reg_access = &adxl380_reg_access,
+ .hwfifo_set_watermark = adxl380_set_watermark,
+};
+
+static const struct iio_info adxl380_info = {
+ .read_raw = adxl380_read_raw,
+ .read_avail = &adxl380_read_avail,
+ .write_raw = adxl380_write_raw,
+ .write_raw_get_fmt = adxl380_write_raw_get_fmt,
+ .read_event_config = adxl380_read_event_config,
+ .write_event_config = adxl380_write_event_config,
+ .read_event_value = adxl380_read_event_value,
+ .write_event_value = adxl380_write_event_value,
+ .event_attrs = &adxl380_event_attribute_group,
+ .debugfs_reg_access = &adxl380_reg_access,
+ .hwfifo_set_watermark = adxl380_set_watermark,
+};
+
+const struct adxl380_chip_info adxl318_chip_info = {
+ .name = "adxl318",
+ .chip_id = ADXL318_ID_VAL,
+ .scale_tbl = {
+ [ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 },
+ [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 },
+ [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 },
+ },
+ .samp_freq_tbl = { 0, 8000, 16000, 32000 },
+ /*
+ * The datasheet defines an intercept of 550 LSB at 25 degC
+ * and a sensitivity of 10.2 LSB/C.
+ */
+ .temp_offset = 25 * 102 / 10 - 550,
+ .info = &adxl318_info,
+};
+EXPORT_SYMBOL_NS_GPL(adxl318_chip_info, "IIO_ADXL380");
+
+const struct adxl380_chip_info adxl319_chip_info = {
+ .name = "adxl319",
+ .chip_id = ADXL319_ID_VAL,
+ .scale_tbl = {
+ [ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 },
+ [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 },
+ [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 },
+ },
+ .samp_freq_tbl = { 0, 16000, 32000, 64000 },
+ /*
+ * The datasheet defines an intercept of 550 LSB at 25 degC
+ * and a sensitivity of 10.2 LSB/C.
+ */
+ .temp_offset = 25 * 102 / 10 - 550,
+ .info = &adxl318_info,
+};
+EXPORT_SYMBOL_NS_GPL(adxl319_chip_info, "IIO_ADXL380");
+
+const struct adxl380_chip_info adxl380_chip_info = {
+ .name = "adxl380",
+ .chip_id = ADXL380_ID_VAL,
+ .scale_tbl = {
+ [ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 },
+ [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 },
+ [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 },
+ },
+ .samp_freq_tbl = { 1000, 8000, 16000, 32000 },
+ /*
+ * The datasheet defines an intercept of 470 LSB at 25 degC
+ * and a sensitivity of 10.2 LSB/C.
+ */
+ .temp_offset = 25 * 102 / 10 - 470,
+ .has_low_power = true,
+ .info = &adxl380_info,
+
+};
+EXPORT_SYMBOL_NS_GPL(adxl380_chip_info, "IIO_ADXL380");
+
+const struct adxl380_chip_info adxl382_chip_info = {
+ .name = "adxl382",
+ .chip_id = ADXL382_ID_VAL,
+ .scale_tbl = {
+ [ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 },
+ [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 },
+ [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 },
+ },
+ .samp_freq_tbl = { 1000, 16000, 32000, 64000 },
+ /*
+ * The datasheet defines an intercept of 570 LSB at 25 degC
+ * and a sensitivity of 10.2 LSB/C.
+ */
+ .temp_offset = 25 * 102 / 10 - 570,
+ .has_low_power = true,
+ .info = &adxl380_info,
+};
+EXPORT_SYMBOL_NS_GPL(adxl382_chip_info, "IIO_ADXL380");
+
+static const struct iio_event_spec adxl380_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+ {
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_SINGLETAP,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_RESET_TIMEOUT),
+ },
+ {
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_DOUBLETAP,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_RESET_TIMEOUT) |
+ BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
+ },
+};
+
+#define ADXL380_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_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_all_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_type = \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \
+ BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \
+ BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+ .event_spec = adxl380_events, \
+ .num_event_specs = ARRAY_SIZE(adxl380_events) \
+}
+
+static const struct iio_chan_spec adxl380_channels[] = {
+ ADXL380_ACCEL_CHANNEL(0, ADXL380_X_DATA_H_REG, X),
+ ADXL380_ACCEL_CHANNEL(1, ADXL380_Y_DATA_H_REG, Y),
+ ADXL380_ACCEL_CHANNEL(2, ADXL380_Z_DATA_H_REG, Z),
+ {
+ .type = IIO_TEMP,
+ .address = ADXL380_T_DATA_H_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,
+ .shift = 4,
+ .endianness = IIO_BE,
+ },
+ },
+};
+
+static int adxl380_config_irq(struct iio_dev *indio_dev)
+{
+ struct adxl380_state *st = iio_priv(indio_dev);
+ unsigned long irq_flag;
+ u32 irq_type;
+ u8 polarity;
+ int ret;
+
+ st->irq = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT0");
+ if (st->irq > 0) {
+ st->int_map[0] = ADXL380_INT0_MAP0_REG;
+ st->int_map[1] = ADXL380_INT0_MAP1_REG;
+ } else {
+ st->irq = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT1");
+ if (st->irq < 0)
+ return dev_err_probe(st->dev, st->irq,
+ "no interrupt name specified\n");
+ st->int_map[0] = ADXL380_INT1_MAP0_REG;
+ st->int_map[1] = ADXL380_INT1_MAP1_REG;
+ }
+
+ irq_type = irq_get_trigger_type(st->irq);
+ if (irq_type == IRQ_TYPE_LEVEL_HIGH) {
+ polarity = 0;
+ irq_flag = IRQF_TRIGGER_HIGH | IRQF_ONESHOT;
+ } else if (irq_type == IRQ_TYPE_LEVEL_LOW) {
+ polarity = 1;
+ irq_flag = IRQF_TRIGGER_LOW | IRQF_ONESHOT;
+ } else {
+ return dev_err_probe(st->dev, -EINVAL,
+ "Invalid interrupt 0x%x. Only level interrupts supported\n",
+ irq_type);
+ }
+
+ ret = regmap_update_bits(st->regmap, ADXL380_INT0_REG,
+ ADXL380_INT0_POL_MSK,
+ FIELD_PREP(ADXL380_INT0_POL_MSK, polarity));
+ if (ret)
+ return ret;
+
+ return devm_request_threaded_irq(st->dev, st->irq, NULL,
+ adxl380_irq_handler, irq_flag,
+ indio_dev->name, indio_dev);
+}
+
+static int adxl380_setup(struct iio_dev *indio_dev)
+{
+ unsigned int reg_val;
+ u16 part_id, chip_id;
+ int ret, i;
+ struct adxl380_state *st = iio_priv(indio_dev);
+
+ ret = regmap_read(st->regmap, ADXL380_DEVID_AD_REG, &reg_val);
+ if (ret)
+ return ret;
+
+ if (reg_val != ADXL380_DEVID_AD_VAL)
+ dev_warn(st->dev, "Unknown chip id %x\n", reg_val);
+
+ ret = regmap_bulk_read(st->regmap, ADXL380_PART_ID_REG,
+ &st->transf_buf, 2);
+ if (ret)
+ return ret;
+
+ part_id = get_unaligned_be16(st->transf_buf);
+ part_id >>= 4;
+
+ if (part_id != ADXL380_ID_VAL)
+ dev_warn(st->dev, "Unknown part id %x\n", part_id);
+
+ ret = regmap_read(st->regmap, ADXL380_MISC_0_REG, &reg_val);
+ if (ret)
+ return ret;
+
+ /* Bit to differentiate between ADXL380/382. */
+ if (reg_val & ADXL380_XL382_MSK)
+ chip_id = ADXL382_ID_VAL;
+ else
+ chip_id = ADXL380_ID_VAL;
+
+ if (chip_id != st->chip_info->chip_id)
+ dev_warn(st->dev, "Unknown chip id %x\n", chip_id);
+
+ ret = regmap_write(st->regmap, ADXL380_RESET_REG, ADXL380_RESET_CODE);
+ if (ret)
+ return ret;
+
+ /*
+ * A latency of approximately 0.5 ms is required after soft reset.
+ * Stated in the register REG_RESET description.
+ */
+ fsleep(500);
+
+ for (i = 0; i < indio_dev->num_channels; i++) {
+ ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG,
+ ADXL380_CHAN_EN_MSK(i),
+ 1 << (4 + i));
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits(st->regmap, ADXL380_FIFO_CONFIG_0_REG,
+ ADXL380_FIFO_MODE_MSK,
+ FIELD_PREP(ADXL380_FIFO_MODE_MSK, ADXL380_FIFO_STREAMED));
+ if (ret)
+ return ret;
+
+ /* Select all 3 axis for act/inact detection. */
+ ret = regmap_update_bits(st->regmap, ADXL380_SNSR_AXIS_EN_REG,
+ ADXL380_ACT_INACT_AXIS_EN_MSK,
+ FIELD_PREP(ADXL380_ACT_INACT_AXIS_EN_MSK,
+ ADXL380_ACT_INACT_AXIS_EN_MSK));
+ if (ret)
+ return ret;
+
+ ret = adxl380_config_irq(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = adxl380_fill_lpf_tbl(st);
+ if (ret)
+ return ret;
+
+ ret = adxl380_fill_hpf_tbl(st);
+ if (ret)
+ return ret;
+
+ return adxl380_set_measure_en(st, true);
+}
+
+int adxl380_probe(struct device *dev, struct regmap *regmap,
+ const struct adxl380_chip_info *chip_info)
+{
+ struct iio_dev *indio_dev;
+ struct adxl380_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ st->dev = dev;
+ st->regmap = regmap;
+ st->chip_info = chip_info;
+ st->odr = ADXL380_ODR_DSM;
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ indio_dev->channels = adxl380_channels;
+ indio_dev->num_channels = ARRAY_SIZE(adxl380_channels);
+ indio_dev->name = chip_info->name;
+ indio_dev->info = chip_info->info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = devm_regulator_get_enable(dev, "vddio");
+ if (ret)
+ return dev_err_probe(st->dev, ret,
+ "Failed to get vddio regulator\n");
+
+ ret = devm_regulator_get_enable(st->dev, "vsupply");
+ if (ret)
+ return dev_err_probe(st->dev, ret,
+ "Failed to get vsupply regulator\n");
+
+ ret = adxl380_setup(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_kfifo_buffer_setup_ext(st->dev, indio_dev,
+ &adxl380_buffer_ops,
+ adxl380_fifo_attributes);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(adxl380_probe, "IIO_ADXL380");
+
+MODULE_AUTHOR("Ramona Gradinariu <ramona.gradinariu@analog.com>");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/adxl380.h b/drivers/iio/accel/adxl380.h
new file mode 100644
index 000000000..d2c260c8b
--- /dev/null
+++ b/drivers/iio/accel/adxl380.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * ADXL380 3-Axis Digital Accelerometer
+ *
+ * Copyright 2024 Analog Devices Inc.
+ */
+
+#ifndef _ADXL380_H_
+#define _ADXL380_H_
+
+enum adxl380_odr {
+ ADXL380_ODR_VLP,
+ ADXL380_ODR_DSM,
+ ADXL380_ODR_DSM_2X,
+ ADXL380_ODR_DSM_4X,
+ ADXL380_ODR_MAX
+};
+
+struct adxl380_chip_info {
+ const char *name;
+ const int scale_tbl[3][2];
+ const int samp_freq_tbl[ADXL380_ODR_MAX];
+ const struct iio_info *info;
+ const int temp_offset;
+ const u16 chip_id;
+ const bool has_low_power;
+};
+
+extern const struct adxl380_chip_info adxl318_chip_info;
+extern const struct adxl380_chip_info adxl319_chip_info;
+extern const struct adxl380_chip_info adxl380_chip_info;
+extern const struct adxl380_chip_info adxl382_chip_info;
+
+int adxl380_probe(struct device *dev, struct regmap *regmap,
+ const struct adxl380_chip_info *chip_info);
+bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg);
+
+#endif /* _ADXL380_H_ */
diff --git a/drivers/iio/accel/adxl380_i2c.c b/drivers/iio/accel/adxl380_i2c.c
new file mode 100644
index 000000000..2673ddbbd
--- /dev/null
+++ b/drivers/iio/accel/adxl380_i2c.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ADXL380 3-Axis Digital Accelerometer I2C driver
+ *
+ * Copyright 2024 Analog Devices Inc.
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "adxl380.h"
+
+static const struct regmap_config adxl380_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .readable_noinc_reg = adxl380_readable_noinc_reg,
+};
+
+static int adxl380_i2c_probe(struct i2c_client *client)
+{
+ struct regmap *regmap;
+ const struct adxl380_chip_info *chip_data;
+
+ chip_data = i2c_get_match_data(client);
+
+ regmap = devm_regmap_init_i2c(client, &adxl380_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return adxl380_probe(&client->dev, regmap, chip_data);
+}
+
+static const struct i2c_device_id adxl380_i2c_id[] = {
+ { .name = "adxl318", .driver_data = (kernel_ulong_t)&adxl318_chip_info },
+ { .name = "adxl319", .driver_data = (kernel_ulong_t)&adxl319_chip_info },
+ { .name = "adxl380", .driver_data = (kernel_ulong_t)&adxl380_chip_info },
+ { .name = "adxl382", .driver_data = (kernel_ulong_t)&adxl382_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adxl380_i2c_id);
+
+static const struct of_device_id adxl380_of_match[] = {
+ { .compatible = "adi,adxl318", .data = &adxl318_chip_info },
+ { .compatible = "adi,adxl319", .data = &adxl319_chip_info },
+ { .compatible = "adi,adxl380", .data = &adxl380_chip_info },
+ { .compatible = "adi,adxl382", .data = &adxl382_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adxl380_of_match);
+
+static struct i2c_driver adxl380_i2c_driver = {
+ .driver = {
+ .name = "adxl380_i2c",
+ .of_match_table = adxl380_of_match,
+ },
+ .probe = adxl380_i2c_probe,
+ .id_table = adxl380_i2c_id,
+};
+
+module_i2c_driver(adxl380_i2c_driver);
+
+MODULE_AUTHOR("Ramona Gradinariu <ramona.gradinariu@analog.com>");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer I2C driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_ADXL380");
diff --git a/drivers/iio/accel/adxl380_spi.c b/drivers/iio/accel/adxl380_spi.c
new file mode 100644
index 000000000..ae8467f4e
--- /dev/null
+++ b/drivers/iio/accel/adxl380_spi.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ADXL380 3-Axis Digital Accelerometer SPI driver
+ *
+ * Copyright 2024 Analog Devices Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "adxl380.h"
+
+static const struct regmap_config adxl380_spi_regmap_config = {
+ .reg_bits = 7,
+ .pad_bits = 1,
+ .val_bits = 8,
+ .read_flag_mask = BIT(0),
+ .readable_noinc_reg = adxl380_readable_noinc_reg,
+};
+
+static int adxl380_spi_probe(struct spi_device *spi)
+{
+ const struct adxl380_chip_info *chip_data;
+ struct regmap *regmap;
+
+ chip_data = spi_get_device_match_data(spi);
+
+ regmap = devm_regmap_init_spi(spi, &adxl380_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return adxl380_probe(&spi->dev, regmap, chip_data);
+}
+
+static const struct spi_device_id adxl380_spi_id[] = {
+ { .name = "adxl318", .driver_data = (kernel_ulong_t)&adxl318_chip_info },
+ { .name = "adxl319", .driver_data = (kernel_ulong_t)&adxl319_chip_info },
+ { .name = "adxl380", .driver_data = (kernel_ulong_t)&adxl380_chip_info },
+ { .name = "adxl382", .driver_data = (kernel_ulong_t)&adxl382_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adxl380_spi_id);
+
+static const struct of_device_id adxl380_of_match[] = {
+ { .compatible = "adi,adxl318", .data = &adxl318_chip_info },
+ { .compatible = "adi,adxl319", .data = &adxl319_chip_info },
+ { .compatible = "adi,adxl380", .data = &adxl380_chip_info },
+ { .compatible = "adi,adxl382", .data = &adxl382_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adxl380_of_match);
+
+static struct spi_driver adxl380_spi_driver = {
+ .driver = {
+ .name = "adxl380_spi",
+ .of_match_table = adxl380_of_match,
+ },
+ .probe = adxl380_spi_probe,
+ .id_table = adxl380_spi_id,
+};
+
+module_spi_driver(adxl380_spi_driver);
+
+MODULE_AUTHOR("Ramona Gradinariu <ramona.gradinariu@analog.com>");
+MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer SPI driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_ADXL380");
diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c
new file mode 100644
index 000000000..e7dbbd476
--- /dev/null
+++ b/drivers/iio/accel/bma180.c
@@ -0,0 +1,1134 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bma180.c - IIO driver for Bosch BMA180 triaxial acceleration sensor
+ *
+ * Copyright 2013 Oleksandr Kravchenko <x0199363@ti.com>
+ *
+ * Support for BMA250 (c) Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ * SPI is not supported by driver
+ * BMA023/BMA150/SMB380: 7-bit I2C slave address 0x38
+ * BMA180: 7-bit I2C slave address 0x40 or 0x41
+ * BMA250: 7-bit I2C slave address 0x18 or 0x19
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.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>
+
+enum chip_ids {
+ BMA023,
+ BMA150,
+ BMA180,
+ BMA250,
+};
+
+struct bma180_data;
+
+struct bma180_part_info {
+ u8 chip_id;
+ const struct iio_chan_spec *channels;
+ unsigned int num_channels;
+ const int *scale_table;
+ unsigned int num_scales;
+ const int *bw_table;
+ unsigned int num_bw;
+ int temp_offset;
+
+ u8 int_reset_reg, int_reset_mask;
+ u8 sleep_reg, sleep_mask;
+ u8 bw_reg, bw_mask, bw_offset;
+ u8 scale_reg, scale_mask;
+ u8 power_reg, power_mask, lowpower_val;
+ u8 int_enable_reg, int_enable_mask;
+ u8 softreset_reg, softreset_val;
+
+ int (*chip_config)(struct bma180_data *data);
+ void (*chip_disable)(struct bma180_data *data);
+};
+
+/* Register set */
+#define BMA023_CTRL_REG0 0x0a
+#define BMA023_CTRL_REG1 0x0b
+#define BMA023_CTRL_REG2 0x14
+#define BMA023_CTRL_REG3 0x15
+
+#define BMA023_RANGE_MASK GENMASK(4, 3) /* Range of accel values */
+#define BMA023_BW_MASK GENMASK(2, 0) /* Accel bandwidth */
+#define BMA023_SLEEP BIT(0)
+#define BMA023_INT_RESET_MASK BIT(6)
+#define BMA023_NEW_DATA_INT BIT(5) /* Intr every new accel data is ready */
+#define BMA023_RESET_VAL BIT(1)
+
+#define BMA180_CHIP_ID 0x00 /* Need to distinguish BMA180 from other */
+#define BMA180_ACC_X_LSB 0x02 /* First of 6 registers of accel data */
+#define BMA180_TEMP 0x08
+#define BMA180_CTRL_REG0 0x0d
+#define BMA180_RESET 0x10
+#define BMA180_BW_TCS 0x20
+#define BMA180_CTRL_REG3 0x21
+#define BMA180_TCO_Z 0x30
+#define BMA180_OFFSET_LSB1 0x35
+
+/* BMA180_CTRL_REG0 bits */
+#define BMA180_DIS_WAKE_UP BIT(0) /* Disable wake up mode */
+#define BMA180_SLEEP BIT(1) /* 1 - chip will sleep */
+#define BMA180_EE_W BIT(4) /* Unlock writing to addr from 0x20 */
+#define BMA180_RESET_INT BIT(6) /* Reset pending interrupts */
+
+/* BMA180_CTRL_REG3 bits */
+#define BMA180_NEW_DATA_INT BIT(1) /* Intr every new accel data is ready */
+
+/* BMA180_OFFSET_LSB1 skipping mode bit */
+#define BMA180_SMP_SKIP BIT(0)
+
+/* Bit masks for registers bit fields */
+#define BMA180_RANGE 0x0e /* Range of measured accel values */
+#define BMA180_BW 0xf0 /* Accel bandwidth */
+#define BMA180_MODE_CONFIG 0x03 /* Config operation modes */
+
+/* We have to write this value in reset register to do soft reset */
+#define BMA180_RESET_VAL 0xb6
+
+#define BMA023_ID_REG_VAL 0x02
+#define BMA180_ID_REG_VAL 0x03
+#define BMA250_ID_REG_VAL 0x03
+
+/* Chip power modes */
+#define BMA180_LOW_POWER 0x03
+
+#define BMA250_RANGE_REG 0x0f
+#define BMA250_BW_REG 0x10
+#define BMA250_POWER_REG 0x11
+#define BMA250_RESET_REG 0x14
+#define BMA250_INT_ENABLE_REG 0x17
+#define BMA250_INT_MAP_REG 0x1a
+#define BMA250_INT_RESET_REG 0x21
+
+#define BMA250_RANGE_MASK GENMASK(3, 0) /* Range of accel values */
+#define BMA250_BW_MASK GENMASK(4, 0) /* Accel bandwidth */
+#define BMA250_BW_OFFSET 8
+#define BMA250_SUSPEND_MASK BIT(7) /* chip will sleep */
+#define BMA250_LOWPOWER_MASK BIT(6)
+#define BMA250_DATA_INTEN_MASK BIT(4)
+#define BMA250_INT1_DATA_MASK BIT(0)
+#define BMA250_INT_RESET_MASK BIT(7) /* Reset pending interrupts */
+
+struct bma180_data {
+ struct regulator *vdd_supply;
+ struct regulator *vddio_supply;
+ struct i2c_client *client;
+ struct iio_trigger *trig;
+ const struct bma180_part_info *part_info;
+ struct iio_mount_matrix orientation;
+ struct mutex mutex;
+ bool sleep_state;
+ int scale;
+ int bw;
+ bool pmode;
+};
+
+enum bma180_chan {
+ AXIS_X,
+ AXIS_Y,
+ AXIS_Z,
+ TEMP
+};
+
+static int bma023_bw_table[] = { 25, 50, 100, 190, 375, 750, 1500 }; /* Hz */
+static int bma023_scale_table[] = { 2452, 4903, 9709, };
+
+static int bma180_bw_table[] = { 10, 20, 40, 75, 150, 300 }; /* Hz */
+static int bma180_scale_table[] = { 1275, 1863, 2452, 3727, 4903, 9709, 19417 };
+
+static int bma250_bw_table[] = { 8, 16, 31, 63, 125, 250, 500, 1000 }; /* Hz */
+static int bma250_scale_table[] = { 0, 0, 0, 38344, 0, 76590, 0, 0, 153180, 0,
+ 0, 0, 306458 };
+
+static int bma180_get_data_reg(struct bma180_data *data, enum bma180_chan chan)
+{
+ int ret;
+
+ if (data->sleep_state)
+ return -EBUSY;
+
+ switch (chan) {
+ case TEMP:
+ ret = i2c_smbus_read_byte_data(data->client, BMA180_TEMP);
+ if (ret < 0)
+ dev_err(&data->client->dev, "failed to read temp register\n");
+ break;
+ default:
+ ret = i2c_smbus_read_word_data(data->client,
+ BMA180_ACC_X_LSB + chan * 2);
+ if (ret < 0)
+ dev_err(&data->client->dev,
+ "failed to read accel_%c register\n",
+ 'x' + chan);
+ }
+
+ return ret;
+}
+
+static int bma180_set_bits(struct bma180_data *data, u8 reg, u8 mask, u8 val)
+{
+ int ret = i2c_smbus_read_byte_data(data->client, reg);
+ u8 reg_val = (ret & ~mask) | (val << (ffs(mask) - 1));
+
+ if (ret < 0)
+ return ret;
+
+ return i2c_smbus_write_byte_data(data->client, reg, reg_val);
+}
+
+static int bma180_reset_intr(struct bma180_data *data)
+{
+ int ret = bma180_set_bits(data, data->part_info->int_reset_reg,
+ data->part_info->int_reset_mask, 1);
+
+ if (ret)
+ dev_err(&data->client->dev, "failed to reset interrupt\n");
+
+ return ret;
+}
+
+static int bma180_set_new_data_intr_state(struct bma180_data *data, bool state)
+{
+ int ret = bma180_set_bits(data, data->part_info->int_enable_reg,
+ data->part_info->int_enable_mask, state);
+ if (ret)
+ goto err;
+ ret = bma180_reset_intr(data);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ dev_err(&data->client->dev,
+ "failed to set new data interrupt state %d\n", state);
+ return ret;
+}
+
+static int bma180_set_sleep_state(struct bma180_data *data, bool state)
+{
+ int ret = bma180_set_bits(data, data->part_info->sleep_reg,
+ data->part_info->sleep_mask, state);
+
+ if (ret) {
+ dev_err(&data->client->dev,
+ "failed to set sleep state %d\n", state);
+ return ret;
+ }
+ data->sleep_state = state;
+
+ return 0;
+}
+
+static int bma180_set_ee_writing_state(struct bma180_data *data, bool state)
+{
+ int ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_EE_W, state);
+
+ if (ret)
+ dev_err(&data->client->dev,
+ "failed to set ee writing state %d\n", state);
+
+ return ret;
+}
+
+static int bma180_set_bw(struct bma180_data *data, int val)
+{
+ int ret, i;
+
+ if (data->sleep_state)
+ return -EBUSY;
+
+ for (i = 0; i < data->part_info->num_bw; ++i) {
+ if (data->part_info->bw_table[i] == val) {
+ ret = bma180_set_bits(data, data->part_info->bw_reg,
+ data->part_info->bw_mask,
+ i + data->part_info->bw_offset);
+ if (ret) {
+ dev_err(&data->client->dev,
+ "failed to set bandwidth\n");
+ return ret;
+ }
+ data->bw = val;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int bma180_set_scale(struct bma180_data *data, int val)
+{
+ int ret, i;
+
+ if (data->sleep_state)
+ return -EBUSY;
+
+ for (i = 0; i < data->part_info->num_scales; ++i)
+ if (data->part_info->scale_table[i] == val) {
+ ret = bma180_set_bits(data, data->part_info->scale_reg,
+ data->part_info->scale_mask, i);
+ if (ret) {
+ dev_err(&data->client->dev,
+ "failed to set scale\n");
+ return ret;
+ }
+ data->scale = val;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int bma180_set_pmode(struct bma180_data *data, bool mode)
+{
+ u8 reg_val = mode ? data->part_info->lowpower_val : 0;
+ int ret = bma180_set_bits(data, data->part_info->power_reg,
+ data->part_info->power_mask, reg_val);
+
+ if (ret) {
+ dev_err(&data->client->dev, "failed to set power mode\n");
+ return ret;
+ }
+ data->pmode = mode;
+
+ return 0;
+}
+
+static int bma180_soft_reset(struct bma180_data *data)
+{
+ int ret = i2c_smbus_write_byte_data(data->client,
+ data->part_info->softreset_reg,
+ data->part_info->softreset_val);
+
+ if (ret)
+ dev_err(&data->client->dev, "failed to reset the chip\n");
+
+ return ret;
+}
+
+static int bma180_chip_init(struct bma180_data *data)
+{
+ /* Try to read chip_id register. It must return 0x03. */
+ int ret = i2c_smbus_read_byte_data(data->client, BMA180_CHIP_ID);
+
+ if (ret < 0)
+ return ret;
+ if (ret != data->part_info->chip_id) {
+ dev_err(&data->client->dev, "wrong chip ID %d expected %d\n",
+ ret, data->part_info->chip_id);
+ return -ENODEV;
+ }
+
+ ret = bma180_soft_reset(data);
+ if (ret)
+ return ret;
+ /*
+ * No serial transaction should occur within minimum 10 us
+ * after soft_reset command
+ */
+ msleep(20);
+
+ return bma180_set_new_data_intr_state(data, false);
+}
+
+static int bma023_chip_config(struct bma180_data *data)
+{
+ int ret = bma180_chip_init(data);
+
+ if (ret)
+ goto err;
+
+ ret = bma180_set_bw(data, 50); /* 50 Hz */
+ if (ret)
+ goto err;
+ ret = bma180_set_scale(data, 2452); /* 2 G */
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ dev_err(&data->client->dev, "failed to config the chip\n");
+ return ret;
+}
+
+static int bma180_chip_config(struct bma180_data *data)
+{
+ int ret = bma180_chip_init(data);
+
+ if (ret)
+ goto err;
+ ret = bma180_set_pmode(data, false);
+ if (ret)
+ goto err;
+ ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_DIS_WAKE_UP, 1);
+ if (ret)
+ goto err;
+ ret = bma180_set_ee_writing_state(data, true);
+ if (ret)
+ goto err;
+ ret = bma180_set_bits(data, BMA180_OFFSET_LSB1, BMA180_SMP_SKIP, 1);
+ if (ret)
+ goto err;
+ ret = bma180_set_bw(data, 20); /* 20 Hz */
+ if (ret)
+ goto err;
+ ret = bma180_set_scale(data, 2452); /* 2 G */
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ dev_err(&data->client->dev, "failed to config the chip\n");
+ return ret;
+}
+
+static int bma250_chip_config(struct bma180_data *data)
+{
+ int ret = bma180_chip_init(data);
+
+ if (ret)
+ goto err;
+ ret = bma180_set_pmode(data, false);
+ if (ret)
+ goto err;
+ ret = bma180_set_bw(data, 16); /* 16 Hz */
+ if (ret)
+ goto err;
+ ret = bma180_set_scale(data, 38344); /* 2 G */
+ if (ret)
+ goto err;
+ /*
+ * This enables dataready interrupt on the INT1 pin
+ * FIXME: support using the INT2 pin
+ */
+ ret = bma180_set_bits(data, BMA250_INT_MAP_REG, BMA250_INT1_DATA_MASK, 1);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ dev_err(&data->client->dev, "failed to config the chip\n");
+ return ret;
+}
+
+static void bma023_chip_disable(struct bma180_data *data)
+{
+ if (bma180_set_sleep_state(data, true))
+ goto err;
+
+ return;
+
+err:
+ dev_err(&data->client->dev, "failed to disable the chip\n");
+}
+
+static void bma180_chip_disable(struct bma180_data *data)
+{
+ if (bma180_set_new_data_intr_state(data, false))
+ goto err;
+ if (bma180_set_ee_writing_state(data, false))
+ goto err;
+ if (bma180_set_sleep_state(data, true))
+ goto err;
+
+ return;
+
+err:
+ dev_err(&data->client->dev, "failed to disable the chip\n");
+}
+
+static void bma250_chip_disable(struct bma180_data *data)
+{
+ if (bma180_set_new_data_intr_state(data, false))
+ goto err;
+ if (bma180_set_sleep_state(data, true))
+ goto err;
+
+ return;
+
+err:
+ dev_err(&data->client->dev, "failed to disable the chip\n");
+}
+
+static ssize_t bma180_show_avail(char *buf, const int *vals, unsigned int n,
+ bool micros)
+{
+ size_t len = 0;
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (!vals[i])
+ continue;
+ len += scnprintf(buf + len, PAGE_SIZE - len,
+ micros ? "0.%06d " : "%d ", vals[i]);
+ }
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static ssize_t bma180_show_filter_freq_avail(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bma180_data *data = iio_priv(dev_to_iio_dev(dev));
+
+ return bma180_show_avail(buf, data->part_info->bw_table,
+ data->part_info->num_bw, false);
+}
+
+static ssize_t bma180_show_scale_avail(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bma180_data *data = iio_priv(dev_to_iio_dev(dev));
+
+ return bma180_show_avail(buf, data->part_info->scale_table,
+ data->part_info->num_scales, true);
+}
+
+static IIO_DEVICE_ATTR(in_accel_filter_low_pass_3db_frequency_available,
+ S_IRUGO, bma180_show_filter_freq_avail, NULL, 0);
+
+static IIO_DEVICE_ATTR(in_accel_scale_available,
+ S_IRUGO, bma180_show_scale_avail, NULL, 0);
+
+static struct attribute *bma180_attributes[] = {
+ &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.
+ dev_attr.attr,
+ &iio_dev_attr_in_accel_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group bma180_attrs_group = {
+ .attrs = bma180_attributes,
+};
+
+static int bma180_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val, int *val2,
+ long mask)
+{
+ struct bma180_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ mutex_lock(&data->mutex);
+ ret = bma180_get_data_reg(data, chan->scan_index);
+ mutex_unlock(&data->mutex);
+ iio_device_release_direct(indio_dev);
+ if (ret < 0)
+ return ret;
+ if (chan->scan_type.sign == 's') {
+ *val = sign_extend32(ret >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+ } else {
+ *val = ret;
+ }
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ *val = data->bw;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ *val = 0;
+ *val2 = data->scale;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ *val = 500;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ *val = data->part_info->temp_offset;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bma180_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val, int val2, long mask)
+{
+ struct bma180_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ if (val)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = bma180_set_scale(data, val2);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ if (val2)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = bma180_set_bw(data, val);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info bma180_info = {
+ .attrs = &bma180_attrs_group,
+ .read_raw = bma180_read_raw,
+ .write_raw = bma180_write_raw,
+};
+
+static const char * const bma180_power_modes[] = { "low_noise", "low_power" };
+
+static int bma180_get_power_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct bma180_data *data = iio_priv(indio_dev);
+
+ return data->pmode;
+}
+
+static int bma180_set_power_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, unsigned int mode)
+{
+ struct bma180_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = bma180_set_pmode(data, mode);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static const struct iio_mount_matrix *
+bma180_accel_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct bma180_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_enum bma180_power_mode_enum = {
+ .items = bma180_power_modes,
+ .num_items = ARRAY_SIZE(bma180_power_modes),
+ .get = bma180_get_power_mode,
+ .set = bma180_set_power_mode,
+};
+
+static const struct iio_chan_spec_ext_info bma023_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bma180_accel_get_mount_matrix),
+ { }
+};
+
+static const struct iio_chan_spec_ext_info bma180_ext_info[] = {
+ IIO_ENUM("power_mode", IIO_SHARED_BY_TYPE, &bma180_power_mode_enum),
+ IIO_ENUM_AVAILABLE("power_mode", IIO_SHARED_BY_TYPE, &bma180_power_mode_enum),
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bma180_accel_get_mount_matrix),
+ { }
+};
+
+#define BMA023_ACC_CHANNEL(_axis, _bits) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
+ .scan_index = AXIS_##_axis, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = _bits, \
+ .storagebits = 16, \
+ .shift = 16 - _bits, \
+ }, \
+ .ext_info = bma023_ext_info, \
+}
+
+#define BMA150_TEMP_CHANNEL { \
+ .type = IIO_TEMP, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), \
+ .scan_index = TEMP, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 8, \
+ .storagebits = 16, \
+ }, \
+}
+
+#define BMA180_ACC_CHANNEL(_axis, _bits) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
+ .scan_index = AXIS_##_axis, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = _bits, \
+ .storagebits = 16, \
+ .shift = 16 - _bits, \
+ }, \
+ .ext_info = bma180_ext_info, \
+}
+
+#define BMA180_TEMP_CHANNEL { \
+ .type = IIO_TEMP, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), \
+ .scan_index = TEMP, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 8, \
+ .storagebits = 16, \
+ }, \
+}
+
+static const struct iio_chan_spec bma023_channels[] = {
+ BMA023_ACC_CHANNEL(X, 10),
+ BMA023_ACC_CHANNEL(Y, 10),
+ BMA023_ACC_CHANNEL(Z, 10),
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static const struct iio_chan_spec bma150_channels[] = {
+ BMA023_ACC_CHANNEL(X, 10),
+ BMA023_ACC_CHANNEL(Y, 10),
+ BMA023_ACC_CHANNEL(Z, 10),
+ BMA150_TEMP_CHANNEL,
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static const struct iio_chan_spec bma180_channels[] = {
+ BMA180_ACC_CHANNEL(X, 14),
+ BMA180_ACC_CHANNEL(Y, 14),
+ BMA180_ACC_CHANNEL(Z, 14),
+ BMA180_TEMP_CHANNEL,
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static const struct iio_chan_spec bma250_channels[] = {
+ BMA180_ACC_CHANNEL(X, 10),
+ BMA180_ACC_CHANNEL(Y, 10),
+ BMA180_ACC_CHANNEL(Z, 10),
+ BMA180_TEMP_CHANNEL,
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static const struct bma180_part_info bma180_part_info[] = {
+ [BMA023] = {
+ .chip_id = BMA023_ID_REG_VAL,
+ .channels = bma023_channels,
+ .num_channels = ARRAY_SIZE(bma023_channels),
+ .scale_table = bma023_scale_table,
+ .num_scales = ARRAY_SIZE(bma023_scale_table),
+ .bw_table = bma023_bw_table,
+ .num_bw = ARRAY_SIZE(bma023_bw_table),
+ /* No temperature channel */
+ .temp_offset = 0,
+ .int_reset_reg = BMA023_CTRL_REG0,
+ .int_reset_mask = BMA023_INT_RESET_MASK,
+ .sleep_reg = BMA023_CTRL_REG0,
+ .sleep_mask = BMA023_SLEEP,
+ .bw_reg = BMA023_CTRL_REG2,
+ .bw_mask = BMA023_BW_MASK,
+ .scale_reg = BMA023_CTRL_REG2,
+ .scale_mask = BMA023_RANGE_MASK,
+ /* No power mode on bma023 */
+ .power_reg = 0,
+ .power_mask = 0,
+ .lowpower_val = 0,
+ .int_enable_reg = BMA023_CTRL_REG3,
+ .int_enable_mask = BMA023_NEW_DATA_INT,
+ .softreset_reg = BMA023_CTRL_REG0,
+ .softreset_val = BMA023_RESET_VAL,
+ .chip_config = bma023_chip_config,
+ .chip_disable = bma023_chip_disable,
+ },
+ [BMA150] = {
+ .chip_id = BMA023_ID_REG_VAL,
+ .channels = bma150_channels,
+ .num_channels = ARRAY_SIZE(bma150_channels),
+ .scale_table = bma023_scale_table,
+ .num_scales = ARRAY_SIZE(bma023_scale_table),
+ .bw_table = bma023_bw_table,
+ .num_bw = ARRAY_SIZE(bma023_bw_table),
+ .temp_offset = -60, /* 0 LSB @ -30 degree C */
+ .int_reset_reg = BMA023_CTRL_REG0,
+ .int_reset_mask = BMA023_INT_RESET_MASK,
+ .sleep_reg = BMA023_CTRL_REG0,
+ .sleep_mask = BMA023_SLEEP,
+ .bw_reg = BMA023_CTRL_REG2,
+ .bw_mask = BMA023_BW_MASK,
+ .scale_reg = BMA023_CTRL_REG2,
+ .scale_mask = BMA023_RANGE_MASK,
+ /* No power mode on bma150 */
+ .power_reg = 0,
+ .power_mask = 0,
+ .lowpower_val = 0,
+ .int_enable_reg = BMA023_CTRL_REG3,
+ .int_enable_mask = BMA023_NEW_DATA_INT,
+ .softreset_reg = BMA023_CTRL_REG0,
+ .softreset_val = BMA023_RESET_VAL,
+ .chip_config = bma023_chip_config,
+ .chip_disable = bma023_chip_disable,
+ },
+ [BMA180] = {
+ .chip_id = BMA180_ID_REG_VAL,
+ .channels = bma180_channels,
+ .num_channels = ARRAY_SIZE(bma180_channels),
+ .scale_table = bma180_scale_table,
+ .num_scales = ARRAY_SIZE(bma180_scale_table),
+ .bw_table = bma180_bw_table,
+ .num_bw = ARRAY_SIZE(bma180_bw_table),
+ .temp_offset = 48, /* 0 LSB @ 24 degree C */
+ .int_reset_reg = BMA180_CTRL_REG0,
+ .int_reset_mask = BMA180_RESET_INT,
+ .sleep_reg = BMA180_CTRL_REG0,
+ .sleep_mask = BMA180_SLEEP,
+ .bw_reg = BMA180_BW_TCS,
+ .bw_mask = BMA180_BW,
+ .scale_reg = BMA180_OFFSET_LSB1,
+ .scale_mask = BMA180_RANGE,
+ .power_reg = BMA180_TCO_Z,
+ .power_mask = BMA180_MODE_CONFIG,
+ .lowpower_val = BMA180_LOW_POWER,
+ .int_enable_reg = BMA180_CTRL_REG3,
+ .int_enable_mask = BMA180_NEW_DATA_INT,
+ .softreset_reg = BMA180_RESET,
+ .softreset_val = BMA180_RESET_VAL,
+ .chip_config = bma180_chip_config,
+ .chip_disable = bma180_chip_disable,
+ },
+ [BMA250] = {
+ .chip_id = BMA250_ID_REG_VAL,
+ .channels = bma250_channels,
+ .num_channels = ARRAY_SIZE(bma250_channels),
+ .scale_table = bma250_scale_table,
+ .num_scales = ARRAY_SIZE(bma250_scale_table),
+ .bw_table = bma250_bw_table,
+ .num_bw = ARRAY_SIZE(bma250_bw_table),
+ .temp_offset = 48, /* 0 LSB @ 24 degree C */
+ .int_reset_reg = BMA250_INT_RESET_REG,
+ .int_reset_mask = BMA250_INT_RESET_MASK,
+ .sleep_reg = BMA250_POWER_REG,
+ .sleep_mask = BMA250_SUSPEND_MASK,
+ .bw_reg = BMA250_BW_REG,
+ .bw_mask = BMA250_BW_MASK,
+ .bw_offset = BMA250_BW_OFFSET,
+ .scale_reg = BMA250_RANGE_REG,
+ .scale_mask = BMA250_RANGE_MASK,
+ .power_reg = BMA250_POWER_REG,
+ .power_mask = BMA250_LOWPOWER_MASK,
+ .lowpower_val = 1,
+ .int_enable_reg = BMA250_INT_ENABLE_REG,
+ .int_enable_mask = BMA250_DATA_INTEN_MASK,
+ .softreset_reg = BMA250_RESET_REG,
+ .softreset_val = BMA180_RESET_VAL,
+ .chip_config = bma250_chip_config,
+ .chip_disable = bma250_chip_disable,
+ },
+};
+
+static irqreturn_t bma180_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct bma180_data *data = iio_priv(indio_dev);
+ s64 time_ns = iio_get_time_ns(indio_dev);
+ int bit, ret, i = 0;
+ struct {
+ s16 chan[4];
+ aligned_s64 timestamp;
+ } scan = { };
+
+ mutex_lock(&data->mutex);
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = bma180_get_data_reg(data, bit);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ goto err;
+ }
+ scan.chan[i++] = ret;
+ }
+
+ mutex_unlock(&data->mutex);
+
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns);
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int bma180_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct bma180_data *data = iio_priv(indio_dev);
+
+ return bma180_set_new_data_intr_state(data, state);
+}
+
+static void bma180_trig_reen(struct iio_trigger *trig)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct bma180_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = bma180_reset_intr(data);
+ if (ret)
+ dev_err(&data->client->dev, "failed to reset interrupt\n");
+}
+
+static const struct iio_trigger_ops bma180_trigger_ops = {
+ .set_trigger_state = bma180_data_rdy_trigger_set_state,
+ .reenable = bma180_trig_reen,
+};
+
+static int bma180_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct device *dev = &client->dev;
+ struct bma180_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);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ data->part_info = i2c_get_match_data(client);
+
+ ret = iio_read_mount_matrix(dev, &data->orientation);
+ if (ret)
+ return ret;
+
+ data->vdd_supply = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(data->vdd_supply))
+ return dev_err_probe(dev, PTR_ERR(data->vdd_supply),
+ "Failed to get vdd regulator\n");
+
+ data->vddio_supply = devm_regulator_get(dev, "vddio");
+ if (IS_ERR(data->vddio_supply))
+ return dev_err_probe(dev, PTR_ERR(data->vddio_supply),
+ "Failed to get vddio regulator\n");
+
+ /* Typical voltage 2.4V these are min and max */
+ ret = regulator_set_voltage(data->vdd_supply, 1620000, 3600000);
+ if (ret)
+ return ret;
+ ret = regulator_set_voltage(data->vddio_supply, 1200000, 3600000);
+ if (ret)
+ return ret;
+ ret = regulator_enable(data->vdd_supply);
+ if (ret) {
+ dev_err(dev, "Failed to enable vdd regulator: %d\n", ret);
+ return ret;
+ }
+ ret = regulator_enable(data->vddio_supply);
+ if (ret) {
+ dev_err(dev, "Failed to enable vddio regulator: %d\n", ret);
+ goto err_disable_vdd;
+ }
+ /* Wait to make sure we started up properly (3 ms at least) */
+ usleep_range(3000, 5000);
+
+ ret = data->part_info->chip_config(data);
+ if (ret < 0)
+ goto err_chip_disable;
+
+ mutex_init(&data->mutex);
+ indio_dev->channels = data->part_info->channels;
+ indio_dev->num_channels = data->part_info->num_channels;
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &bma180_info;
+
+ if (client->irq > 0) {
+ data->trig = iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->trig) {
+ ret = -ENOMEM;
+ goto err_chip_disable;
+ }
+
+ ret = devm_request_irq(dev, client->irq,
+ iio_trigger_generic_data_rdy_poll,
+ IRQF_TRIGGER_RISING | IRQF_NO_THREAD,
+ "bma180_event", data->trig);
+ if (ret)
+ goto err_trigger_free;
+
+ data->trig->ops = &bma180_trigger_ops;
+ iio_trigger_set_drvdata(data->trig, indio_dev);
+
+ ret = iio_trigger_register(data->trig);
+ if (ret)
+ goto err_trigger_free;
+
+ indio_dev->trig = iio_trigger_get(data->trig);
+ }
+
+ ret = iio_triggered_buffer_setup(indio_dev, NULL,
+ bma180_trigger_handler, NULL);
+ if (ret < 0) {
+ dev_err(dev, "unable to setup iio triggered buffer\n");
+ goto err_trigger_unregister;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(dev, "unable to register iio device\n");
+ goto err_buffer_cleanup;
+ }
+
+ return 0;
+
+err_buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+err_trigger_unregister:
+ if (data->trig)
+ iio_trigger_unregister(data->trig);
+err_trigger_free:
+ iio_trigger_free(data->trig);
+err_chip_disable:
+ data->part_info->chip_disable(data);
+ regulator_disable(data->vddio_supply);
+err_disable_vdd:
+ regulator_disable(data->vdd_supply);
+
+ return ret;
+}
+
+static void bma180_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct bma180_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+ if (data->trig) {
+ iio_trigger_unregister(data->trig);
+ iio_trigger_free(data->trig);
+ }
+
+ mutex_lock(&data->mutex);
+ data->part_info->chip_disable(data);
+ mutex_unlock(&data->mutex);
+ regulator_disable(data->vddio_supply);
+ regulator_disable(data->vdd_supply);
+}
+
+static int bma180_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct bma180_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = bma180_set_sleep_state(data, true);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int bma180_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct bma180_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = bma180_set_sleep_state(data, false);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(bma180_pm_ops, bma180_suspend, bma180_resume);
+
+static const struct i2c_device_id bma180_ids[] = {
+ { .name = "bma023", .driver_data = (kernel_ulong_t)&bma180_part_info[BMA023] },
+ { .name = "bma150", .driver_data = (kernel_ulong_t)&bma180_part_info[BMA150] },
+ { .name = "bma180", .driver_data = (kernel_ulong_t)&bma180_part_info[BMA180] },
+ { .name = "bma250", .driver_data = (kernel_ulong_t)&bma180_part_info[BMA250] },
+ { .name = "smb380", .driver_data = (kernel_ulong_t)&bma180_part_info[BMA150] },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, bma180_ids);
+
+static const struct of_device_id bma180_of_match[] = {
+ {
+ .compatible = "bosch,bma023",
+ .data = &bma180_part_info[BMA023]
+ },
+ {
+ .compatible = "bosch,bma150",
+ .data = &bma180_part_info[BMA150]
+ },
+ {
+ .compatible = "bosch,bma180",
+ .data = &bma180_part_info[BMA180]
+ },
+ {
+ .compatible = "bosch,bma250",
+ .data = &bma180_part_info[BMA250]
+ },
+ {
+ .compatible = "bosch,smb380",
+ .data = &bma180_part_info[BMA150]
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bma180_of_match);
+
+static struct i2c_driver bma180_driver = {
+ .driver = {
+ .name = "bma180",
+ .pm = pm_sleep_ptr(&bma180_pm_ops),
+ .of_match_table = bma180_of_match,
+ },
+ .probe = bma180_probe,
+ .remove = bma180_remove,
+ .id_table = bma180_ids,
+};
+
+module_i2c_driver(bma180_driver);
+
+MODULE_AUTHOR("Kravchenko Oleksandr <x0199363@ti.com>");
+MODULE_AUTHOR("Texas Instruments, Inc.");
+MODULE_DESCRIPTION("Bosch BMA023/BMA1x0/BMA250 triaxial acceleration sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/bma220.h b/drivers/iio/accel/bma220.h
new file mode 100644
index 000000000..00dfe2752
--- /dev/null
+++ b/drivers/iio/accel/bma220.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Forward declarations needed by the bma220 sources.
+ *
+ * Copyright 2025 Petre Rodan <petre.rodan@subdimension.ro>
+ */
+
+#ifndef _BMA220_H
+#define _BMA220_H
+
+#include <linux/pm.h>
+#include <linux/regmap.h>
+
+#define BMA220_REG_WDT 0x17
+#define BMA220_WDT_MASK GENMASK(2, 1)
+#define BMA220_WDT_OFF 0x0
+#define BMA220_WDT_1MS 0x2
+#define BMA220_WDT_10MS 0x3
+
+struct device;
+
+extern const struct regmap_config bma220_i2c_regmap_config;
+extern const struct regmap_config bma220_spi_regmap_config;
+extern const struct dev_pm_ops bma220_pm_ops;
+
+int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq);
+
+#endif
diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c
new file mode 100644
index 000000000..b83abfdd8
--- /dev/null
+++ b/drivers/iio/accel/bma220_core.c
@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * BMA220 Digital triaxial acceleration sensor driver
+ *
+ * Copyright (c) 2016,2020 Intel Corporation.
+ * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro>
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include "bma220.h"
+
+#define BMA220_REG_ID 0x00
+#define BMA220_REG_REVISION_ID 0x01
+#define BMA220_REG_ACCEL_X 0x02
+#define BMA220_REG_ACCEL_Y 0x03
+#define BMA220_REG_ACCEL_Z 0x04
+#define BMA220_REG_CONF0 0x05
+#define BMA220_HIGH_DUR_MSK GENMASK(5, 0)
+#define BMA220_HIGH_HY_MSK GENMASK(7, 6)
+#define BMA220_REG_CONF1 0x06
+#define BMA220_HIGH_TH_MSK GENMASK(3, 0)
+#define BMA220_LOW_TH_MSK GENMASK(7, 4)
+#define BMA220_REG_CONF2 0x07
+#define BMA220_LOW_DUR_MSK GENMASK(5, 0)
+#define BMA220_LOW_HY_MSK GENMASK(7, 6)
+#define BMA220_REG_CONF3 0x08
+#define BMA220_TT_DUR_MSK GENMASK(2, 0)
+#define BMA220_TT_TH_MSK GENMASK(6, 3)
+#define BMA220_REG_CONF4 0x09
+#define BMA220_SLOPE_DUR_MSK GENMASK(1, 0)
+#define BMA220_SLOPE_TH_MSK GENMASK(5, 2)
+#define BMA220_REG_CONF5 0x0a
+#define BMA220_TIP_EN_MSK BIT(4)
+#define BMA220_REG_IF0 0x0b
+#define BMA220_REG_IF1 0x0c
+#define BMA220_IF_SLOPE BIT(0)
+#define BMA220_IF_DRDY BIT(1)
+#define BMA220_IF_HIGH BIT(2)
+#define BMA220_IF_LOW BIT(3)
+#define BMA220_IF_TT BIT(4)
+#define BMA220_REG_IE0 0x0d
+#define BMA220_INT_EN_TAP_Z_MSK BIT(0)
+#define BMA220_INT_EN_TAP_Y_MSK BIT(1)
+#define BMA220_INT_EN_TAP_X_MSK BIT(2)
+#define BMA220_INT_EN_SLOPE_Z_MSK BIT(3)
+#define BMA220_INT_EN_SLOPE_Y_MSK BIT(4)
+#define BMA220_INT_EN_SLOPE_X_MSK BIT(5)
+#define BMA220_INT_EN_DRDY_MSK BIT(7)
+#define BMA220_REG_IE1 0x0e
+#define BMA220_INT_EN_HIGH_Z_MSK BIT(0)
+#define BMA220_INT_EN_HIGH_Y_MSK BIT(1)
+#define BMA220_INT_EN_HIGH_X_MSK BIT(2)
+#define BMA220_INT_EN_LOW_MSK BIT(3)
+#define BMA220_INT_LATCH_MSK GENMASK(6, 4)
+#define BMA220_INT_RST_MSK BIT(7)
+#define BMA220_REG_IE2 0x0f
+#define BMA220_REG_FILTER 0x10
+#define BMA220_FILTER_MASK GENMASK(3, 0)
+#define BMA220_REG_RANGE 0x11
+#define BMA220_RANGE_MASK GENMASK(1, 0)
+#define BMA220_REG_SUSPEND 0x18
+#define BMA220_REG_SOFTRESET 0x19
+
+#define BMA220_CHIP_ID 0xDD
+#define BMA220_SUSPEND_SLEEP 0xFF
+#define BMA220_SUSPEND_WAKE 0x00
+#define BMA220_RESET_MODE 0xFF
+#define BMA220_NONRESET_MODE 0x00
+
+#define BMA220_DEVICE_NAME "bma220"
+
+#define BMA220_COF_1000Hz 0x0
+#define BMA220_COF_500Hz 0x1
+#define BMA220_COF_250Hz 0x2
+#define BMA220_COF_125Hz 0x3
+#define BMA220_COF_64Hz 0x4
+#define BMA220_COF_32Hz 0x5
+
+#define BMA220_ACCEL_CHANNEL(index, reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) |\
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 6, \
+ .storagebits = 8, \
+ .shift = 2, \
+ .endianness = IIO_CPU, \
+ }, \
+}
+
+enum bma220_axis {
+ AXIS_X,
+ AXIS_Y,
+ AXIS_Z,
+};
+
+static const int bma220_scale_table[][2] = {
+ { 0, 623000 }, { 1, 248000 }, { 2, 491000 }, { 4, 983000 },
+};
+
+struct bma220_data {
+ struct regmap *regmap;
+ struct mutex lock;
+ u8 lpf_3dB_freq_idx;
+ u8 range_idx;
+ struct iio_trigger *trig;
+ struct {
+ s8 chans[3];
+ /* Ensure timestamp is naturally aligned. */
+ aligned_s64 timestamp;
+ } scan __aligned(IIO_DMA_MINALIGN);
+};
+
+static const struct iio_chan_spec bma220_channels[] = {
+ BMA220_ACCEL_CHANNEL(0, BMA220_REG_ACCEL_X, X),
+ BMA220_ACCEL_CHANNEL(1, BMA220_REG_ACCEL_Y, Y),
+ BMA220_ACCEL_CHANNEL(2, BMA220_REG_ACCEL_Z, Z),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+/* Available cut-off frequencies of the low pass filter in Hz. */
+static const int bma220_lpf_3dB_freq_Hz_table[] = {
+ [BMA220_COF_1000Hz] = 1000,
+ [BMA220_COF_500Hz] = 500,
+ [BMA220_COF_250Hz] = 250,
+ [BMA220_COF_125Hz] = 125,
+ [BMA220_COF_64Hz] = 64,
+ [BMA220_COF_32Hz] = 32,
+};
+
+static const unsigned long bma220_accel_scan_masks[] = {
+ BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z),
+ 0
+};
+
+static bool bma220_is_writable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BMA220_REG_CONF0:
+ case BMA220_REG_CONF1:
+ case BMA220_REG_CONF2:
+ case BMA220_REG_CONF3:
+ case BMA220_REG_CONF4:
+ case BMA220_REG_CONF5:
+ case BMA220_REG_IE0:
+ case BMA220_REG_IE1:
+ case BMA220_REG_IE2:
+ case BMA220_REG_FILTER:
+ case BMA220_REG_RANGE:
+ case BMA220_REG_WDT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+const struct regmap_config bma220_spi_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .read_flag_mask = BIT(7),
+ .max_register = BMA220_REG_SOFTRESET,
+ .cache_type = REGCACHE_NONE,
+ .writeable_reg = bma220_is_writable_reg,
+};
+EXPORT_SYMBOL_NS_GPL(bma220_spi_regmap_config, "IIO_BOSCH_BMA220");
+
+/*
+ * Based on the datasheet the memory map differs between the SPI and the I2C
+ * implementations. I2C register addresses are simply shifted to the left
+ * by 1 bit yet the register size remains unchanged.
+ * This driver employs the SPI memory map to correlate register names to
+ * addresses regardless of the bus type.
+ */
+
+const struct regmap_config bma220_i2c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .reg_shift = -1,
+ .max_register = BMA220_REG_SOFTRESET,
+ .cache_type = REGCACHE_NONE,
+ .writeable_reg = bma220_is_writable_reg,
+};
+EXPORT_SYMBOL_NS_GPL(bma220_i2c_regmap_config, "IIO_BOSCH_BMA220");
+
+static int bma220_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ return regmap_update_bits(data->regmap, BMA220_REG_IE0,
+ BMA220_INT_EN_DRDY_MSK,
+ FIELD_PREP(BMA220_INT_EN_DRDY_MSK, state));
+}
+
+static const struct iio_trigger_ops bma220_trigger_ops = {
+ .set_trigger_state = &bma220_data_rdy_trigger_set_state,
+ .validate_device = &iio_trigger_validate_own_device,
+};
+
+static irqreturn_t bma220_trigger_handler(int irq, void *p)
+{
+ int ret;
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ ret = regmap_bulk_read(data->regmap, BMA220_REG_ACCEL_X,
+ &data->scan.chans,
+ sizeof(data->scan.chans));
+ if (ret < 0)
+ return IRQ_NONE;
+
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ iio_get_time_ns(indio_dev));
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int bma220_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int ret;
+ u8 index;
+ unsigned int reg;
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_read(data->regmap, chan->address, &reg);
+ if (ret < 0)
+ return -EINVAL;
+ *val = sign_extend32(reg >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ index = data->range_idx;
+ *val = bma220_scale_table[index][0];
+ *val2 = bma220_scale_table[index][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ index = data->lpf_3dB_freq_idx;
+ *val = bma220_lpf_3dB_freq_Hz_table[index];
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int bma220_find_match_2dt(const int (*tbl)[2], const int n,
+ const int val, const int val2)
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (tbl[i][0] == val && tbl[i][1] == val2)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int bma220_find_match(const int *arr, const int n, const int val)
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (arr[i] == val)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int bma220_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int ret;
+ int index = -1;
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ index = bma220_find_match_2dt(bma220_scale_table,
+ ARRAY_SIZE(bma220_scale_table),
+ val, val2);
+ if (index < 0)
+ return -EINVAL;
+
+ ret = regmap_update_bits(data->regmap, BMA220_REG_RANGE,
+ BMA220_RANGE_MASK,
+ FIELD_PREP(BMA220_RANGE_MASK, index));
+ if (ret < 0)
+ return ret;
+ data->range_idx = index;
+
+ return 0;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ index = bma220_find_match(bma220_lpf_3dB_freq_Hz_table,
+ ARRAY_SIZE(bma220_lpf_3dB_freq_Hz_table),
+ val);
+ if (index < 0)
+ return -EINVAL;
+
+ ret = regmap_update_bits(data->regmap, BMA220_REG_FILTER,
+ BMA220_FILTER_MASK,
+ FIELD_PREP(BMA220_FILTER_MASK, index));
+ if (ret < 0)
+ return ret;
+ data->lpf_3dB_freq_idx = index;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int bma220_read_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_SCALE:
+ *vals = (int *)bma220_scale_table;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = ARRAY_SIZE(bma220_scale_table) * 2;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ *vals = (const int *)bma220_lpf_3dB_freq_Hz_table;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(bma220_lpf_3dB_freq_Hz_table);
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bma220_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ if (readval)
+ return regmap_read(data->regmap, reg, readval);
+ return regmap_write(data->regmap, reg, writeval);
+}
+
+static const struct iio_info bma220_info = {
+ .read_raw = bma220_read_raw,
+ .write_raw = bma220_write_raw,
+ .read_avail = bma220_read_avail,
+ .debugfs_reg_access = &bma220_reg_access,
+};
+
+static int bma220_reset(struct bma220_data *data, bool up)
+{
+ int ret;
+ unsigned int i, val;
+
+ /*
+ * The chip can be reset by a simple register read.
+ * We need up to 2 register reads of the softreset register
+ * to make sure that the device is in the desired state.
+ */
+ for (i = 0; i < 2; i++) {
+ ret = regmap_read(data->regmap, BMA220_REG_SOFTRESET, &val);
+ if (ret < 0)
+ return ret;
+
+ if (up && val == BMA220_RESET_MODE)
+ return 0;
+
+ if (!up && val == BMA220_NONRESET_MODE)
+ return 0;
+ }
+
+ return -EBUSY;
+}
+
+static int bma220_power(struct bma220_data *data, bool up)
+{
+ int ret;
+ unsigned int i, val;
+
+ /*
+ * The chip can be suspended/woken up by a simple register read.
+ * So, we need up to 2 register reads of the suspend register
+ * to make sure that the device is in the desired state.
+ */
+ for (i = 0; i < 2; i++) {
+ ret = regmap_read(data->regmap, BMA220_REG_SUSPEND, &val);
+ if (ret < 0)
+ return ret;
+
+ if (up && val == BMA220_SUSPEND_SLEEP)
+ return 0;
+
+ if (!up && val == BMA220_SUSPEND_WAKE)
+ return 0;
+ }
+
+ return -EBUSY;
+}
+
+static int bma220_init(struct device *dev, struct bma220_data *data)
+{
+ int ret;
+ unsigned int val;
+ static const char * const regulator_names[] = { "vddd", "vddio", "vdda" };
+
+ ret = devm_regulator_bulk_get_enable(dev,
+ ARRAY_SIZE(regulator_names),
+ regulator_names);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get regulators\n");
+
+ ret = regmap_read(data->regmap, BMA220_REG_ID, &val);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read chip id register\n");
+
+ if (val != BMA220_CHIP_ID)
+ dev_info(dev, "Unknown chip found: 0x%02x\n", val);
+
+ ret = bma220_power(data, true);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to power-on chip\n");
+
+ ret = bma220_reset(data, true);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to soft reset chip\n");
+
+ return 0;
+}
+
+static void bma220_deinit(void *data_ptr)
+{
+ struct bma220_data *data = data_ptr;
+ int ret;
+ struct device *dev = regmap_get_device(data->regmap);
+
+ ret = bma220_power(data, false);
+ if (ret)
+ dev_warn(dev,
+ "Failed to put device into suspend mode (%pe)\n",
+ ERR_PTR(ret));
+}
+
+static irqreturn_t bma220_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct bma220_data *data = iio_priv(indio_dev);
+ int ret;
+ unsigned int bma220_reg_if1;
+
+ ret = regmap_read(data->regmap, BMA220_REG_IF1, &bma220_reg_if1);
+ if (ret)
+ return IRQ_NONE;
+
+ if (FIELD_GET(BMA220_IF_DRDY, bma220_reg_if1))
+ iio_trigger_poll_nested(data->trig);
+
+ return IRQ_HANDLED;
+}
+
+int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct bma220_data *data;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->regmap = regmap;
+
+ ret = bma220_init(dev, data);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret)
+ return ret;
+
+ indio_dev->info = &bma220_info;
+ indio_dev->name = BMA220_DEVICE_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = bma220_channels;
+ indio_dev->num_channels = ARRAY_SIZE(bma220_channels);
+ indio_dev->available_scan_masks = bma220_accel_scan_masks;
+
+ if (irq > 0) {
+ data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->trig)
+ return -ENOMEM;
+
+ data->trig->ops = &bma220_trigger_ops;
+ iio_trigger_set_drvdata(data->trig, indio_dev);
+
+ ret = devm_iio_trigger_register(dev, data->trig);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "iio trigger register fail\n");
+ indio_dev->trig = iio_trigger_get(data->trig);
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ &bma220_irq_handler, IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, bma220_deinit, data);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
+ bma220_trigger_handler, NULL);
+ if (ret < 0)
+ dev_err_probe(dev, ret, "iio triggered buffer setup failed\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(bma220_common_probe, "IIO_BOSCH_BMA220");
+
+static int bma220_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ return bma220_power(data, false);
+}
+
+static int bma220_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ return bma220_power(data, true);
+}
+EXPORT_NS_SIMPLE_DEV_PM_OPS(bma220_pm_ops, bma220_suspend, bma220_resume,
+ IIO_BOSCH_BMA220);
+
+MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>");
+MODULE_DESCRIPTION("BMA220 acceleration sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/bma220_i2c.c b/drivers/iio/accel/bma220_i2c.c
new file mode 100644
index 000000000..7e850e95c
--- /dev/null
+++ b/drivers/iio/accel/bma220_i2c.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Bosch triaxial acceleration sensor
+ *
+ * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro>
+ *
+ * Datasheet: https://media.digikey.com/pdf/Data%20Sheets/Bosch/BMA220.pdf
+ * I2C address is either 0x0b or 0x0a depending on CSB (pin 10)
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include "bma220.h"
+
+static int bma220_set_wdt(struct regmap *regmap, const u8 val)
+{
+ return regmap_update_bits(regmap, BMA220_REG_WDT, BMA220_WDT_MASK,
+ FIELD_PREP(BMA220_WDT_MASK, val));
+}
+
+static int bma220_i2c_probe(struct i2c_client *client)
+{
+ struct regmap *regmap;
+ int ret;
+
+ regmap = devm_regmap_init_i2c(client, &bma220_i2c_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(regmap),
+ "failed to create regmap\n");
+
+ ret = bma220_common_probe(&client->dev, regmap, client->irq);
+ if (ret)
+ return ret;
+
+ return bma220_set_wdt(regmap, BMA220_WDT_1MS);
+}
+
+static const struct of_device_id bma220_i2c_match[] = {
+ { .compatible = "bosch,bma220" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bma220_i2c_match);
+
+static const struct i2c_device_id bma220_i2c_id[] = {
+ { .name = "bma220" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, bma220_i2c_id);
+
+static struct i2c_driver bma220_i2c_driver = {
+ .driver = {
+ .name = "bma220_i2c",
+ .pm = pm_sleep_ptr(&bma220_pm_ops),
+ .of_match_table = bma220_i2c_match,
+ },
+ .probe = bma220_i2c_probe,
+ .id_table = bma220_i2c_id,
+};
+module_i2c_driver(bma220_i2c_driver);
+
+MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>");
+MODULE_DESCRIPTION("Bosch triaxial acceleration sensor i2c driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_BOSCH_BMA220");
diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c
new file mode 100644
index 000000000..d6c1087cc
--- /dev/null
+++ b/drivers/iio/accel/bma220_spi.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * BMA220 Digital triaxial acceleration sensor driver
+ *
+ * Copyright (c) 2016,2020 Intel Corporation.
+ */
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/spi/spi.h>
+
+#include "bma220.h"
+
+static int bma220_spi_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_spi(spi, &bma220_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&spi->dev, PTR_ERR(regmap),
+ "failed to create regmap\n");
+
+ return bma220_common_probe(&spi->dev, regmap, spi->irq);
+}
+
+static const struct spi_device_id bma220_spi_id[] = {
+ { .name = "bma220" },
+ { }
+};
+
+static const struct acpi_device_id bma220_acpi_id[] = {
+ { "BMA0220", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, bma220_spi_id);
+
+static const struct of_device_id bma220_of_spi_match[] = {
+ { .compatible = "bosch,bma220" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bma220_of_spi_match);
+
+static struct spi_driver bma220_spi_driver = {
+ .driver = {
+ .name = "bma220_spi",
+ .pm = pm_sleep_ptr(&bma220_pm_ops),
+ .of_match_table = bma220_of_spi_match,
+ .acpi_match_table = bma220_acpi_id,
+ },
+ .probe = bma220_spi_probe,
+ .id_table = bma220_spi_id,
+};
+module_spi_driver(bma220_spi_driver);
+
+MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>");
+MODULE_DESCRIPTION("BMA220 triaxial acceleration sensor spi driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_BOSCH_BMA220");
diff --git a/drivers/iio/accel/bma400.h b/drivers/iio/accel/bma400.h
new file mode 100644
index 000000000..b5f3cac51
--- /dev/null
+++ b/drivers/iio/accel/bma400.h
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Register constants and other forward declarations needed by the bma400
+ * sources.
+ *
+ * Copyright 2019 Dan Robertson <dan@dlrobertson.com>
+ */
+
+#ifndef _BMA400_H_
+#define _BMA400_H_
+
+#include <linux/bits.h>
+#include <linux/regmap.h>
+
+/*
+ * Read-Only Registers
+ */
+
+/* Chip ID of BMA 400 devices found in the chip ID register. */
+#define BMA400_ID_REG_VAL 0x90
+
+/* Status and ID registers */
+#define BMA400_CHIP_ID_REG 0x00
+#define BMA400_ERR_REG 0x02
+#define BMA400_STATUS_REG 0x03
+
+/* Acceleration registers */
+#define BMA400_ACC_X_LSB_REG 0x04
+#define BMA400_ACC_X_MSB_REG 0x05
+#define BMA400_ACC_Y_LSB_REG 0x06
+#define BMA400_ACC_Y_MSB_REG 0x07
+#define BMA400_ACC_Z_LSB_REG 0x08
+#define BMA400_ACC_Z_MSB_REG 0x09
+
+/* Sensor time registers */
+#define BMA400_SENSOR_TIME0_REG 0x0a
+#define BMA400_SENSOR_TIME1_REG 0x0b
+#define BMA400_SENSOR_TIME2_REG 0x0c
+
+/* Event and interrupt registers */
+#define BMA400_EVENT_REG 0x0d
+
+#define BMA400_INT_STAT0_REG 0x0e
+#define BMA400_INT_STAT0_GEN1_MASK BIT(2)
+#define BMA400_INT_STAT0_GEN2_MASK BIT(3)
+#define BMA400_INT_STAT0_DRDY_MASK BIT(7)
+
+#define BMA400_INT_STAT1_REG 0x0f
+#define BMA400_INT_STAT1_STEP_INT_MASK GENMASK(9, 8)
+#define BMA400_INT_STAT1_S_TAP_MASK BIT(10)
+#define BMA400_INT_STAT1_D_TAP_MASK BIT(11)
+
+#define BMA400_INT_STAT2_REG 0x10
+
+/* Bit present in all INT_STAT registers */
+#define BMA400_INT_STAT_ENG_OVRRUN_MASK BIT(4)
+
+/* Temperature register */
+#define BMA400_TEMP_DATA_REG 0x11
+
+/* FIFO length and data registers */
+#define BMA400_FIFO_LENGTH0_REG 0x12
+#define BMA400_FIFO_LENGTH1_REG 0x13
+#define BMA400_FIFO_DATA_REG 0x14
+
+/* Step count registers */
+#define BMA400_STEP_CNT0_REG 0x15
+#define BMA400_STEP_CNT1_REG 0x16
+#define BMA400_STEP_CNT3_REG 0x17
+#define BMA400_STEP_STAT_REG 0x18
+#define BMA400_STEP_RAW_LEN 0x03
+
+/*
+ * Read-write configuration registers
+ */
+#define BMA400_ACC_CONFIG0_REG 0x19
+#define BMA400_ACC_CONFIG0_LP_OSR_MASK GENMASK(6, 5)
+
+#define BMA400_ACC_CONFIG1_REG 0x1a
+#define BMA400_ACC_CONFIG1_ODR_MASK GENMASK(3, 0)
+#define BMA400_ACC_CONFIG1_ODR_MIN_RAW 0x05
+#define BMA400_ACC_CONFIG1_ODR_LP_RAW 0x06
+#define BMA400_ACC_CONFIG1_ODR_MAX_RAW 0x0b
+#define BMA400_ACC_CONFIG1_ODR_MAX_HZ 800
+#define BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ 25
+#define BMA400_ACC_CONFIG1_ODR_MIN_HZ 12
+#define BMA400_ACC_CONFIG1_NP_OSR_MASK GENMASK(5, 4)
+#define BMA400_ACC_CONFIG1_ACC_RANGE_MASK GENMASK(7, 6)
+
+#define BMA400_ACC_CONFIG2_REG 0x1b
+
+/* Interrupt registers */
+#define BMA400_INT_CONFIG0_REG 0x1f
+#define BMA400_INT_CONFIG0_GEN1_MASK BIT(2)
+#define BMA400_INT_CONFIG0_GEN2_MASK BIT(3)
+#define BMA400_INT_CONFIG0_DRDY_MASK BIT(7)
+
+enum bma400_generic_intr {
+ BMA400_GEN1_INTR = 0x1,
+ BMA400_GEN2_INTR = 0x2,
+};
+
+#define BMA400_INT_CONFIG1_REG 0x20
+#define BMA400_INT_CONFIG1_STEP_INT_MASK BIT(0)
+#define BMA400_INT_CONFIG1_S_TAP_MASK BIT(2)
+#define BMA400_INT_CONFIG1_D_TAP_MASK BIT(3)
+
+#define BMA400_INT1_MAP_REG 0x21
+#define BMA400_INT12_MAP_REG 0x23
+#define BMA400_INT_IO_CTRL_REG 0x24
+
+#define BMA400_TWO_BITS_MASK GENMASK(1, 0)
+
+/* Generic interrupts register */
+#define BMA400_GENINT_CONFIG_REG_BASE 0x3f
+#define BMA400_NUM_GENINT_CONFIG_REGS 11
+#define BMA400_GENINT_CONFIG_REG(gen_intr, config_idx) \
+ (BMA400_GENINT_CONFIG_REG_BASE + \
+ (gen_intr - 1) * BMA400_NUM_GENINT_CONFIG_REGS + \
+ (config_idx))
+#define BMA400_GENINT_CONFIG0_HYST_MASK GENMASK(1, 0)
+#define BMA400_GENINT_CONFIG0_REF_UPD_MODE_MASK GENMASK(3, 2)
+#define BMA400_GENINT_CONFIG0_DATA_SRC_MASK BIT(4)
+#define BMA400_GENINT_CONFIG0_X_EN_MASK BIT(5)
+#define BMA400_GENINT_CONFIG0_Y_EN_MASK BIT(6)
+#define BMA400_GENINT_CONFIG0_Z_EN_MASK BIT(7)
+
+enum bma400_accel_data_src {
+ ACCEL_FILT1 = 0x0,
+ ACCEL_FILT2 = 0x1,
+};
+
+enum bma400_ref_updt_mode {
+ BMA400_REF_MANUAL_UPDT_MODE = 0x0,
+ BMA400_REF_ONETIME_UPDT_MODE = 0x1,
+ BMA400_REF_EVERYTIME_UPDT_MODE = 0x2,
+ BMA400_REF_EVERYTIME_LP_UPDT_MODE = 0x3,
+};
+
+#define BMA400_GEN_CONFIG1_OFF 0x01
+#define BMA400_GENINT_CONFIG1_AXES_COMB_MASK BIT(0)
+#define BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK BIT(1)
+
+enum bma400_genintr_acceleval_axescomb {
+ BMA400_EVAL_X_OR_Y_OR_Z = 0x0,
+ BMA400_EVAL_X_AND_Y_AND_Z = 0x1,
+};
+
+enum bma400_detect_criterion {
+ BMA400_DETECT_INACTIVITY = 0x0,
+ BMA400_DETECT_ACTIVITY = 0x1,
+};
+
+/* TAP config registers */
+#define BMA400_TAP_CONFIG_REG 0x57
+#define BMA400_TAP_CONFIG_SEN_MASK GENMASK(2, 0)
+
+#define BMA400_TAP_CONFIG1_REG 0x58
+#define BMA400_TAP_CONFIG1_TICSTH_MASK GENMASK(1, 0)
+#define BMA400_TAP_CONFIG1_QUIET_MASK GENMASK(3, 2)
+#define BMA400_TAP_CONFIG1_QUIETDT_MASK GENMASK(5, 4)
+#define BMA400_TAP_TIM_LIST_LEN 4
+
+#define BMA400_CMD_REG 0x7e
+/*
+ * BMA400_SCALE_MIN macro value represents m/s^2 for 1 LSB before
+ * converting to micro values for +-2g range.
+ *
+ * For +-2g - 1 LSB = 0.976562 milli g = 0.009576 m/s^2
+ * For +-4g - 1 LSB = 1.953125 milli g = 0.019153 m/s^2
+ * For +-16g - 1 LSB = 7.8125 milli g = 0.076614 m/s^2
+ *
+ * The raw value which is used to select the different ranges is determined
+ * by the first bit set position from the scale value, so BMA400_SCALE_MIN
+ * should be odd.
+ *
+ * Scale values for +-2g, +-4g, +-8g and +-16g are populated into bma400_scales
+ * array by left shifting BMA400_SCALE_MIN.
+ * e.g.:
+ * To select +-2g = 9577 << 0 = raw value to write is 0.
+ * To select +-8g = 9577 << 2 = raw value to write is 2.
+ * To select +-16g = 9577 << 3 = raw value to write is 3.
+ */
+#define BMA400_ACC_SCALE_MIN 9577
+#define BMA400_ACC_SCALE_MAX 76617
+
+extern const struct regmap_config bma400_regmap_config;
+
+int bma400_probe(struct device *dev, struct regmap *regmap, int irq,
+ const char *name);
+
+#endif
diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c
new file mode 100644
index 000000000..7e3cff4cc
--- /dev/null
+++ b/drivers/iio/accel/bma400_core.c
@@ -0,0 +1,1808 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Core IIO driver for Bosch BMA400 triaxial acceleration sensor.
+ *
+ * Copyright 2019 Dan Robertson <dan@dlrobertson.com>
+ *
+ * TODO:
+ * - Support for power management
+ * - Support events and interrupts
+ * - Create channel for step count
+ * - Create channel for sensor time
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include "bma400.h"
+
+/*
+ * The G-range selection may be one of 2g, 4g, 8, or 16g. The scale may
+ * be selected with the acc_range bits of the ACC_CONFIG1 register.
+ * NB: This buffer is populated in the device init.
+ */
+static int bma400_scales[8];
+
+/*
+ * See the ACC_CONFIG1 section of the datasheet.
+ * NB: This buffer is populated in the device init.
+ */
+static int bma400_sample_freqs[14];
+
+static const int bma400_osr_range[] = { 0, 1, 3 };
+
+static int tap_reset_timeout[BMA400_TAP_TIM_LIST_LEN] = {
+ 300000,
+ 400000,
+ 500000,
+ 600000
+};
+
+static int tap_max2min_time[BMA400_TAP_TIM_LIST_LEN] = {
+ 30000,
+ 45000,
+ 60000,
+ 90000
+};
+
+static int double_tap2_min_delay[BMA400_TAP_TIM_LIST_LEN] = {
+ 20000,
+ 40000,
+ 60000,
+ 80000
+};
+
+/* See the ACC_CONFIG0 section of the datasheet */
+enum bma400_power_mode {
+ POWER_MODE_SLEEP = 0x00,
+ POWER_MODE_LOW = 0x01,
+ POWER_MODE_NORMAL = 0x02,
+ POWER_MODE_INVALID = 0x03,
+};
+
+enum bma400_scan {
+ BMA400_ACCL_X,
+ BMA400_ACCL_Y,
+ BMA400_ACCL_Z,
+ BMA400_TEMP,
+};
+
+struct bma400_sample_freq {
+ int hz;
+ int uhz;
+};
+
+enum bma400_activity {
+ BMA400_STILL,
+ BMA400_WALKING,
+ BMA400_RUNNING,
+};
+
+struct bma400_data {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex mutex; /* data register lock */
+ struct iio_mount_matrix orientation;
+ enum bma400_power_mode power_mode;
+ struct bma400_sample_freq sample_freq;
+ int oversampling_ratio;
+ int scale;
+ struct iio_trigger *trig;
+ int steps_enabled;
+ bool step_event_en;
+ bool activity_event_en;
+ unsigned int generic_event_en;
+ unsigned int tap_event_en_bitmask;
+ /* Correct time stamp alignment */
+ struct {
+ __le16 buff[3];
+ u8 temperature;
+ aligned_s64 ts;
+ } buffer __aligned(IIO_DMA_MINALIGN);
+ __le16 status;
+ __be16 duration;
+};
+
+struct bma400_genintr_info {
+ enum bma400_generic_intr genintr;
+ unsigned int intrmask;
+ enum iio_event_direction dir;
+ enum bma400_detect_criterion detect_mode;
+};
+
+/* Lookup struct for determining GEN1/GEN2 based on dir */
+static const struct bma400_genintr_info bma400_genintrs[] = {
+ [IIO_EV_DIR_RISING] = {
+ .genintr = BMA400_GEN1_INTR,
+ .intrmask = BMA400_INT_CONFIG0_GEN1_MASK,
+ .dir = IIO_EV_DIR_RISING,
+ .detect_mode = BMA400_DETECT_ACTIVITY,
+ },
+ [IIO_EV_DIR_FALLING] = {
+ .genintr = BMA400_GEN2_INTR,
+ .intrmask = BMA400_INT_CONFIG0_GEN2_MASK,
+ .dir = IIO_EV_DIR_FALLING,
+ .detect_mode = BMA400_DETECT_INACTIVITY,
+ }
+};
+
+static inline const struct bma400_genintr_info *
+get_bma400_genintr_info(enum iio_event_direction dir)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ case IIO_EV_DIR_FALLING:
+ return &bma400_genintrs[dir];
+ default:
+ return NULL;
+ };
+}
+
+static bool bma400_is_writable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BMA400_CHIP_ID_REG:
+ case BMA400_ERR_REG:
+ case BMA400_STATUS_REG:
+ case BMA400_ACC_X_LSB_REG:
+ case BMA400_ACC_X_MSB_REG:
+ case BMA400_ACC_Y_LSB_REG:
+ case BMA400_ACC_Y_MSB_REG:
+ case BMA400_ACC_Z_LSB_REG:
+ case BMA400_ACC_Z_MSB_REG:
+ case BMA400_SENSOR_TIME0_REG:
+ case BMA400_SENSOR_TIME1_REG:
+ case BMA400_SENSOR_TIME2_REG:
+ case BMA400_EVENT_REG:
+ case BMA400_INT_STAT0_REG:
+ case BMA400_INT_STAT1_REG:
+ case BMA400_INT_STAT2_REG:
+ case BMA400_TEMP_DATA_REG:
+ case BMA400_FIFO_LENGTH0_REG:
+ case BMA400_FIFO_LENGTH1_REG:
+ case BMA400_FIFO_DATA_REG:
+ case BMA400_STEP_CNT0_REG:
+ case BMA400_STEP_CNT1_REG:
+ case BMA400_STEP_CNT3_REG:
+ case BMA400_STEP_STAT_REG:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static bool bma400_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BMA400_ERR_REG:
+ case BMA400_STATUS_REG:
+ case BMA400_ACC_X_LSB_REG:
+ case BMA400_ACC_X_MSB_REG:
+ case BMA400_ACC_Y_LSB_REG:
+ case BMA400_ACC_Y_MSB_REG:
+ case BMA400_ACC_Z_LSB_REG:
+ case BMA400_ACC_Z_MSB_REG:
+ case BMA400_SENSOR_TIME0_REG:
+ case BMA400_SENSOR_TIME1_REG:
+ case BMA400_SENSOR_TIME2_REG:
+ case BMA400_EVENT_REG:
+ case BMA400_INT_STAT0_REG:
+ case BMA400_INT_STAT1_REG:
+ case BMA400_INT_STAT2_REG:
+ case BMA400_TEMP_DATA_REG:
+ case BMA400_FIFO_LENGTH0_REG:
+ case BMA400_FIFO_LENGTH1_REG:
+ case BMA400_FIFO_DATA_REG:
+ case BMA400_STEP_CNT0_REG:
+ case BMA400_STEP_CNT1_REG:
+ case BMA400_STEP_CNT3_REG:
+ case BMA400_STEP_STAT_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+const struct regmap_config bma400_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = BMA400_CMD_REG,
+ .cache_type = REGCACHE_MAPLE,
+ .writeable_reg = bma400_is_writable_reg,
+ .volatile_reg = bma400_is_volatile_reg,
+};
+EXPORT_SYMBOL_NS(bma400_regmap_config, "IIO_BMA400");
+
+static const struct iio_mount_matrix *
+bma400_accel_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct bma400_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info bma400_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bma400_accel_get_mount_matrix),
+ { }
+};
+
+static const struct iio_event_spec bma400_step_detect_event = {
+ .type = IIO_EV_TYPE_CHANGE,
+ .dir = IIO_EV_DIR_NONE,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+};
+
+static const struct iio_event_spec bma400_activity_event = {
+ .type = IIO_EV_TYPE_CHANGE,
+ .dir = IIO_EV_DIR_NONE,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE),
+};
+
+static const struct iio_event_spec bma400_accel_event[] = {
+ {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD) |
+ BIT(IIO_EV_INFO_HYSTERESIS) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD) |
+ BIT(IIO_EV_INFO_HYSTERESIS) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_SINGLETAP,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_RESET_TIMEOUT),
+ },
+ {
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_DOUBLETAP,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_RESET_TIMEOUT) |
+ BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
+ },
+};
+
+static int usec_to_tapreg_raw(int usec, const int *time_list)
+{
+ int index;
+
+ for (index = 0; index < BMA400_TAP_TIM_LIST_LEN; index++) {
+ if (usec == time_list[index])
+ return index;
+ }
+ return -EINVAL;
+}
+
+static ssize_t in_accel_gesture_tap_maxtomin_time_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bma400_data *data = iio_priv(indio_dev);
+ int ret, reg_val, raw, vals[2];
+
+ ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG, &reg_val);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(BMA400_TAP_CONFIG1_TICSTH_MASK, reg_val);
+ vals[0] = 0;
+ vals[1] = tap_max2min_time[raw];
+
+ return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, 2, vals);
+}
+
+static ssize_t in_accel_gesture_tap_maxtomin_time_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bma400_data *data = iio_priv(indio_dev);
+ int ret, val_int, val_fract, raw;
+
+ ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract);
+ if (ret)
+ return ret;
+
+ raw = usec_to_tapreg_raw(val_fract, tap_max2min_time);
+ if (raw < 0)
+ return -EINVAL;
+
+ ret = regmap_update_bits(data->regmap, BMA400_TAP_CONFIG1_REG,
+ BMA400_TAP_CONFIG1_TICSTH_MASK,
+ FIELD_PREP(BMA400_TAP_CONFIG1_TICSTH_MASK, raw));
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_maxtomin_time, 0);
+
+/*
+ * Tap interrupts works with 200 Hz input data rate and the time based tap
+ * controls are in the terms of data samples so the below calculation is
+ * used to convert the configuration values into seconds.
+ * e.g.:
+ * 60 data samples * 0.005 ms = 0.3 seconds.
+ * 80 data samples * 0.005 ms = 0.4 seconds.
+ */
+
+/* quiet configuration values in seconds */
+static IIO_CONST_ATTR(in_accel_gesture_tap_reset_timeout_available,
+ "0.3 0.4 0.5 0.6");
+
+/* tics_th configuration values in seconds */
+static IIO_CONST_ATTR(in_accel_gesture_tap_maxtomin_time_available,
+ "0.03 0.045 0.06 0.09");
+
+/* quiet_dt configuration values in seconds */
+static IIO_CONST_ATTR(in_accel_gesture_doubletap_tap2_min_delay_available,
+ "0.02 0.04 0.06 0.08");
+
+/* List of sensitivity values available to configure tap interrupts */
+static IIO_CONST_ATTR(in_accel_gesture_tap_value_available, "0 1 2 3 4 5 6 7");
+
+static struct attribute *bma400_event_attributes[] = {
+ &iio_const_attr_in_accel_gesture_tap_value_available.dev_attr.attr,
+ &iio_const_attr_in_accel_gesture_tap_reset_timeout_available.dev_attr.attr,
+ &iio_const_attr_in_accel_gesture_tap_maxtomin_time_available.dev_attr.attr,
+ &iio_const_attr_in_accel_gesture_doubletap_tap2_min_delay_available.dev_attr.attr,
+ &iio_dev_attr_in_accel_gesture_tap_maxtomin_time.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group bma400_event_attribute_group = {
+ .attrs = bma400_event_attributes,
+};
+
+#define BMA400_ACC_CHANNEL(_index, _axis) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .ext_info = bma400_ext_info, \
+ .scan_index = _index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .endianness = IIO_LE, \
+ }, \
+ .event_spec = bma400_accel_event, \
+ .num_event_specs = ARRAY_SIZE(bma400_accel_event) \
+}
+
+#define BMA400_ACTIVITY_CHANNEL(_chan2) { \
+ .type = IIO_ACTIVITY, \
+ .modified = 1, \
+ .channel2 = _chan2, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
+ .scan_index = -1, /* No buffer support */ \
+ .event_spec = &bma400_activity_event, \
+ .num_event_specs = 1, \
+}
+
+static const struct iio_chan_spec bma400_channels[] = {
+ BMA400_ACC_CHANNEL(0, X),
+ BMA400_ACC_CHANNEL(1, Y),
+ BMA400_ACC_CHANNEL(2, Z),
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .scan_index = 3,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 8,
+ .storagebits = 8,
+ .endianness = IIO_LE,
+ },
+ },
+ {
+ .type = IIO_STEPS,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_ENABLE),
+ .scan_index = -1, /* No buffer support */
+ .event_spec = &bma400_step_detect_event,
+ .num_event_specs = 1,
+ },
+ BMA400_ACTIVITY_CHANNEL(IIO_MOD_STILL),
+ BMA400_ACTIVITY_CHANNEL(IIO_MOD_WALKING),
+ BMA400_ACTIVITY_CHANNEL(IIO_MOD_RUNNING),
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static int bma400_get_temp_reg(struct bma400_data *data, int *val, int *val2)
+{
+ unsigned int raw_temp;
+ int host_temp;
+ int ret;
+
+ if (data->power_mode == POWER_MODE_SLEEP)
+ return -EBUSY;
+
+ ret = regmap_read(data->regmap, BMA400_TEMP_DATA_REG, &raw_temp);
+ if (ret)
+ return ret;
+
+ host_temp = sign_extend32(raw_temp, 7);
+ /*
+ * The formula for the TEMP_DATA register in the datasheet
+ * is: x * 0.5 + 23
+ */
+ *val = (host_temp >> 1) + 23;
+ *val2 = (host_temp & 0x1) * 500000;
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int bma400_get_accel_reg(struct bma400_data *data,
+ const struct iio_chan_spec *chan,
+ int *val)
+{
+ __le16 raw_accel;
+ int lsb_reg;
+ int ret;
+
+ if (data->power_mode == POWER_MODE_SLEEP)
+ return -EBUSY;
+
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ lsb_reg = BMA400_ACC_X_LSB_REG;
+ break;
+ case IIO_MOD_Y:
+ lsb_reg = BMA400_ACC_Y_LSB_REG;
+ break;
+ case IIO_MOD_Z:
+ lsb_reg = BMA400_ACC_Z_LSB_REG;
+ break;
+ default:
+ dev_err(data->dev, "invalid axis channel modifier\n");
+ return -EINVAL;
+ }
+
+ /* bulk read two registers, with the base being the LSB register */
+ ret = regmap_bulk_read(data->regmap, lsb_reg, &raw_accel,
+ sizeof(raw_accel));
+ if (ret)
+ return ret;
+
+ *val = sign_extend32(le16_to_cpu(raw_accel), 11);
+ return IIO_VAL_INT;
+}
+
+static void bma400_output_data_rate_from_raw(int raw, unsigned int *val,
+ unsigned int *val2)
+{
+ *val = BMA400_ACC_CONFIG1_ODR_MAX_HZ >> (BMA400_ACC_CONFIG1_ODR_MAX_RAW - raw);
+ if (raw > BMA400_ACC_CONFIG1_ODR_MIN_RAW)
+ *val2 = 0;
+ else
+ *val2 = 500000;
+}
+
+static int bma400_get_accel_output_data_rate(struct bma400_data *data)
+{
+ unsigned int val;
+ unsigned int odr;
+ int ret;
+
+ switch (data->power_mode) {
+ case POWER_MODE_LOW:
+ /*
+ * Runs at a fixed rate in low-power mode. See section 4.3
+ * in the datasheet.
+ */
+ bma400_output_data_rate_from_raw(BMA400_ACC_CONFIG1_ODR_LP_RAW,
+ &data->sample_freq.hz,
+ &data->sample_freq.uhz);
+ return 0;
+ case POWER_MODE_NORMAL:
+ /*
+ * In normal mode the ODR can be found in the ACC_CONFIG1
+ * register.
+ */
+ ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, &val);
+ if (ret)
+ goto error;
+
+ odr = val & BMA400_ACC_CONFIG1_ODR_MASK;
+ if (odr < BMA400_ACC_CONFIG1_ODR_MIN_RAW ||
+ odr > BMA400_ACC_CONFIG1_ODR_MAX_RAW) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ bma400_output_data_rate_from_raw(odr, &data->sample_freq.hz,
+ &data->sample_freq.uhz);
+ return 0;
+ case POWER_MODE_SLEEP:
+ data->sample_freq.hz = 0;
+ data->sample_freq.uhz = 0;
+ return 0;
+ default:
+ ret = 0;
+ goto error;
+ }
+error:
+ data->sample_freq.hz = -1;
+ data->sample_freq.uhz = -1;
+ return ret;
+}
+
+static int bma400_set_accel_output_data_rate(struct bma400_data *data,
+ int hz, int uhz)
+{
+ unsigned int idx;
+ unsigned int odr;
+ unsigned int val;
+ int ret;
+
+ if (hz >= BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ) {
+ if (uhz || hz > BMA400_ACC_CONFIG1_ODR_MAX_HZ)
+ return -EINVAL;
+
+ /* Note this works because MIN_WHOLE_HZ is odd */
+ idx = __ffs(hz);
+
+ if (hz >> idx != BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ)
+ return -EINVAL;
+
+ idx += BMA400_ACC_CONFIG1_ODR_MIN_RAW + 1;
+ } else if (hz == BMA400_ACC_CONFIG1_ODR_MIN_HZ && uhz == 500000) {
+ idx = BMA400_ACC_CONFIG1_ODR_MIN_RAW;
+ } else {
+ return -EINVAL;
+ }
+
+ ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, &val);
+ if (ret)
+ return ret;
+
+ /* preserve the range and normal mode osr */
+ odr = (~BMA400_ACC_CONFIG1_ODR_MASK & val) | idx;
+
+ ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG, odr);
+ if (ret)
+ return ret;
+
+ bma400_output_data_rate_from_raw(idx, &data->sample_freq.hz,
+ &data->sample_freq.uhz);
+ return 0;
+}
+
+static int bma400_get_accel_oversampling_ratio(struct bma400_data *data)
+{
+ unsigned int val;
+ unsigned int osr;
+ int ret;
+
+ /*
+ * The oversampling ratio is stored in a different register
+ * based on the power-mode. In normal mode the OSR is stored
+ * in ACC_CONFIG1. In low-power mode it is stored in
+ * ACC_CONFIG0.
+ */
+ switch (data->power_mode) {
+ case POWER_MODE_LOW:
+ ret = regmap_read(data->regmap, BMA400_ACC_CONFIG0_REG, &val);
+ if (ret) {
+ data->oversampling_ratio = -1;
+ return ret;
+ }
+
+ osr = FIELD_GET(BMA400_ACC_CONFIG0_LP_OSR_MASK, val);
+
+ data->oversampling_ratio = osr;
+ return 0;
+ case POWER_MODE_NORMAL:
+ ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, &val);
+ if (ret) {
+ data->oversampling_ratio = -1;
+ return ret;
+ }
+
+ osr = FIELD_GET(BMA400_ACC_CONFIG1_NP_OSR_MASK, val);
+
+ data->oversampling_ratio = osr;
+ return 0;
+ case POWER_MODE_SLEEP:
+ data->oversampling_ratio = 0;
+ return 0;
+ default:
+ data->oversampling_ratio = -1;
+ return -EINVAL;
+ }
+}
+
+static int bma400_set_accel_oversampling_ratio(struct bma400_data *data,
+ int val)
+{
+ unsigned int acc_config;
+ int ret;
+
+ if (val & ~BMA400_TWO_BITS_MASK)
+ return -EINVAL;
+
+ /*
+ * The oversampling ratio is stored in a different register
+ * based on the power-mode.
+ */
+ switch (data->power_mode) {
+ case POWER_MODE_LOW:
+ ret = regmap_read(data->regmap, BMA400_ACC_CONFIG0_REG,
+ &acc_config);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, BMA400_ACC_CONFIG0_REG,
+ (acc_config & ~BMA400_ACC_CONFIG0_LP_OSR_MASK) |
+ FIELD_PREP(BMA400_ACC_CONFIG0_LP_OSR_MASK, val));
+ if (ret) {
+ dev_err(data->dev, "Failed to write out OSR\n");
+ return ret;
+ }
+
+ data->oversampling_ratio = val;
+ return 0;
+ case POWER_MODE_NORMAL:
+ ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG,
+ &acc_config);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG,
+ (acc_config & ~BMA400_ACC_CONFIG1_NP_OSR_MASK) |
+ FIELD_PREP(BMA400_ACC_CONFIG1_NP_OSR_MASK, val));
+ if (ret) {
+ dev_err(data->dev, "Failed to write out OSR\n");
+ return ret;
+ }
+
+ data->oversampling_ratio = val;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ return ret;
+}
+
+static int bma400_accel_scale_to_raw(struct bma400_data *data,
+ unsigned int val)
+{
+ int raw;
+
+ if (val == 0)
+ return -EINVAL;
+
+ /* Note this works because BMA400_SCALE_MIN is odd */
+ raw = __ffs(val);
+
+ if (val >> raw != BMA400_ACC_SCALE_MIN)
+ return -EINVAL;
+
+ return raw;
+}
+
+static int bma400_get_accel_scale(struct bma400_data *data)
+{
+ unsigned int raw_scale;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, &val);
+ if (ret)
+ return ret;
+
+ raw_scale = FIELD_GET(BMA400_ACC_CONFIG1_ACC_RANGE_MASK, val);
+ if (raw_scale > BMA400_TWO_BITS_MASK)
+ return -EINVAL;
+
+ data->scale = BMA400_ACC_SCALE_MIN << raw_scale;
+
+ return 0;
+}
+
+static int bma400_set_accel_scale(struct bma400_data *data, unsigned int val)
+{
+ unsigned int acc_config;
+ int raw;
+ int ret;
+
+ ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, &acc_config);
+ if (ret)
+ return ret;
+
+ raw = bma400_accel_scale_to_raw(data, val);
+ if (raw < 0)
+ return raw;
+
+ ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG,
+ (acc_config & ~BMA400_ACC_CONFIG1_ACC_RANGE_MASK) |
+ FIELD_PREP(BMA400_ACC_CONFIG1_ACC_RANGE_MASK, raw));
+ if (ret)
+ return ret;
+
+ data->scale = val;
+ return 0;
+}
+
+static int bma400_get_power_mode(struct bma400_data *data)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(data->regmap, BMA400_STATUS_REG, &val);
+ if (ret) {
+ dev_err(data->dev, "Failed to read status register\n");
+ return ret;
+ }
+
+ data->power_mode = (val >> 1) & BMA400_TWO_BITS_MASK;
+ return 0;
+}
+
+static int bma400_set_power_mode(struct bma400_data *data,
+ enum bma400_power_mode mode)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(data->regmap, BMA400_ACC_CONFIG0_REG, &val);
+ if (ret)
+ return ret;
+
+ if (data->power_mode == mode)
+ return 0;
+
+ if (mode == POWER_MODE_INVALID)
+ return -EINVAL;
+
+ /* Preserve the low-power oversample ratio etc */
+ ret = regmap_write(data->regmap, BMA400_ACC_CONFIG0_REG,
+ mode | (val & ~BMA400_TWO_BITS_MASK));
+ if (ret) {
+ dev_err(data->dev, "Failed to write to power-mode\n");
+ return ret;
+ }
+
+ data->power_mode = mode;
+
+ /*
+ * Update our cached osr and odr based on the new
+ * power-mode.
+ */
+ bma400_get_accel_output_data_rate(data);
+ bma400_get_accel_oversampling_ratio(data);
+ return 0;
+}
+
+static int bma400_enable_steps(struct bma400_data *data, int val)
+{
+ int ret;
+
+ if (data->steps_enabled == val)
+ return 0;
+
+ ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG1_REG,
+ BMA400_INT_CONFIG1_STEP_INT_MASK,
+ FIELD_PREP(BMA400_INT_CONFIG1_STEP_INT_MASK, val ? 1 : 0));
+ if (ret)
+ return ret;
+ data->steps_enabled = val;
+ return ret;
+}
+
+static int bma400_get_steps_reg(struct bma400_data *data, int *val)
+{
+ int ret;
+
+ u8 *steps_raw __free(kfree) = kmalloc(BMA400_STEP_RAW_LEN, GFP_KERNEL);
+ if (!steps_raw)
+ return -ENOMEM;
+
+ ret = regmap_bulk_read(data->regmap, BMA400_STEP_CNT0_REG,
+ steps_raw, BMA400_STEP_RAW_LEN);
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_le24(steps_raw);
+
+ return IIO_VAL_INT;
+}
+
+static void bma400_init_tables(void)
+{
+ int raw;
+ int i;
+
+ for (i = 0; i + 1 < ARRAY_SIZE(bma400_sample_freqs); i += 2) {
+ raw = (i / 2) + 5;
+ bma400_output_data_rate_from_raw(raw, &bma400_sample_freqs[i],
+ &bma400_sample_freqs[i + 1]);
+ }
+
+ for (i = 0; i + 1 < ARRAY_SIZE(bma400_scales); i += 2) {
+ raw = i / 2;
+ bma400_scales[i] = 0;
+ bma400_scales[i + 1] = BMA400_ACC_SCALE_MIN << raw;
+ }
+}
+
+static void bma400_power_disable(void *data_ptr)
+{
+ struct bma400_data *data = data_ptr;
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = bma400_set_power_mode(data, POWER_MODE_SLEEP);
+ mutex_unlock(&data->mutex);
+ if (ret)
+ dev_warn(data->dev, "Failed to put device into sleep mode (%pe)\n",
+ ERR_PTR(ret));
+}
+
+static enum iio_modifier bma400_act_to_mod(enum bma400_activity activity)
+{
+ switch (activity) {
+ case BMA400_STILL:
+ return IIO_MOD_STILL;
+ case BMA400_WALKING:
+ return IIO_MOD_WALKING;
+ case BMA400_RUNNING:
+ return IIO_MOD_RUNNING;
+ default:
+ return IIO_NO_MOD;
+ }
+}
+
+static int bma400_init(struct bma400_data *data)
+{
+ static const char * const regulator_names[] = { "vdd", "vddio" };
+ unsigned int val;
+ int ret;
+
+ ret = devm_regulator_bulk_get_enable(data->dev,
+ ARRAY_SIZE(regulator_names),
+ regulator_names);
+ if (ret)
+ return dev_err_probe(data->dev, ret, "Failed to get regulators\n");
+
+ /* Try to read chip_id register. It must return 0x90. */
+ ret = regmap_read(data->regmap, BMA400_CHIP_ID_REG, &val);
+ if (ret) {
+ dev_err(data->dev, "Failed to read chip id register\n");
+ return ret;
+ }
+
+ if (val != BMA400_ID_REG_VAL) {
+ dev_err(data->dev, "Chip ID mismatch\n");
+ return -ENODEV;
+ }
+
+ ret = bma400_get_power_mode(data);
+ if (ret) {
+ dev_err(data->dev, "Failed to get the initial power-mode\n");
+ return ret;
+ }
+
+ if (data->power_mode != POWER_MODE_NORMAL) {
+ ret = bma400_set_power_mode(data, POWER_MODE_NORMAL);
+ if (ret) {
+ dev_err(data->dev, "Failed to wake up the device\n");
+ return ret;
+ }
+ /*
+ * TODO: The datasheet waits 1500us here in the example, but
+ * lists 2/ODR as the wakeup time.
+ */
+ usleep_range(1500, 2000);
+ }
+
+ ret = devm_add_action_or_reset(data->dev, bma400_power_disable, data);
+ if (ret)
+ return ret;
+
+ bma400_init_tables();
+
+ ret = bma400_get_accel_output_data_rate(data);
+ if (ret)
+ return ret;
+
+ ret = bma400_get_accel_oversampling_ratio(data);
+ if (ret)
+ return ret;
+
+ ret = bma400_get_accel_scale(data);
+ if (ret)
+ return ret;
+
+ /* Configure INT1 pin to open drain */
+ ret = regmap_write(data->regmap, BMA400_INT_IO_CTRL_REG, 0x06);
+ if (ret)
+ return ret;
+ /*
+ * Once the interrupt engine is supported we might use the
+ * data_src_reg, but for now ensure this is set to the
+ * variable ODR filter selectable by the sample frequency
+ * channel.
+ */
+ return regmap_write(data->regmap, BMA400_ACC_CONFIG2_REG, 0x00);
+}
+
+static int bma400_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct bma400_data *data = iio_priv(indio_dev);
+ unsigned int activity;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (chan->type) {
+ case IIO_TEMP:
+ mutex_lock(&data->mutex);
+ ret = bma400_get_temp_reg(data, val, val2);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_STEPS:
+ return bma400_get_steps_reg(data, val);
+ case IIO_ACTIVITY:
+ ret = regmap_read(data->regmap, BMA400_STEP_STAT_REG,
+ &activity);
+ if (ret)
+ return ret;
+ /*
+ * The device does not support confidence value levels,
+ * so we will always have 100% for current activity and
+ * 0% for the others.
+ */
+ if (chan->channel2 == bma400_act_to_mod(activity))
+ *val = 100;
+ else
+ *val = 0;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&data->mutex);
+ ret = bma400_get_accel_reg(data, chan, val);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ if (data->sample_freq.hz < 0)
+ return -EINVAL;
+
+ *val = data->sample_freq.hz;
+ *val2 = data->sample_freq.uhz;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ /*
+ * Runs at a fixed sampling frequency. See Section 4.4
+ * of the datasheet.
+ */
+ *val = 6;
+ *val2 = 250000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = data->scale;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ /*
+ * TODO: We could avoid this logic and returning -EINVAL here if
+ * we set both the low-power and normal mode OSR registers when
+ * we configure the device.
+ */
+ if (data->oversampling_ratio < 0)
+ return -EINVAL;
+
+ *val = data->oversampling_ratio;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_ENABLE:
+ *val = data->steps_enabled;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bma400_read_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_SCALE:
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *vals = bma400_scales;
+ *length = ARRAY_SIZE(bma400_scales);
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *type = IIO_VAL_INT;
+ *vals = bma400_osr_range;
+ *length = ARRAY_SIZE(bma400_osr_range);
+ return IIO_AVAIL_RANGE;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *vals = bma400_sample_freqs;
+ *length = ARRAY_SIZE(bma400_sample_freqs);
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bma400_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val, int val2,
+ long mask)
+{
+ struct bma400_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ /*
+ * The sample frequency is readonly for the temperature
+ * register and a fixed value in low-power mode.
+ */
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ mutex_lock(&data->mutex);
+ ret = bma400_set_accel_output_data_rate(data, val, val2);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_CHAN_INFO_SCALE:
+ if (val != 0 ||
+ val2 < BMA400_ACC_SCALE_MIN || val2 > BMA400_ACC_SCALE_MAX)
+ return -EINVAL;
+
+ mutex_lock(&data->mutex);
+ ret = bma400_set_accel_scale(data, val2);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ mutex_lock(&data->mutex);
+ ret = bma400_set_accel_oversampling_ratio(data, val);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_CHAN_INFO_ENABLE:
+ mutex_lock(&data->mutex);
+ ret = bma400_enable_steps(data, val);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bma400_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_ENABLE:
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bma400_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 bma400_data *data = iio_priv(indio_dev);
+
+ switch (chan->type) {
+ case IIO_ACCEL:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return FIELD_GET(BMA400_INT_CONFIG0_GEN1_MASK,
+ data->generic_event_en);
+ case IIO_EV_DIR_FALLING:
+ return FIELD_GET(BMA400_INT_CONFIG0_GEN2_MASK,
+ data->generic_event_en);
+ case IIO_EV_DIR_SINGLETAP:
+ return FIELD_GET(BMA400_INT_CONFIG1_S_TAP_MASK,
+ data->tap_event_en_bitmask);
+ case IIO_EV_DIR_DOUBLETAP:
+ return FIELD_GET(BMA400_INT_CONFIG1_D_TAP_MASK,
+ data->tap_event_en_bitmask);
+ default:
+ return -EINVAL;
+ }
+ case IIO_STEPS:
+ return data->step_event_en;
+ case IIO_ACTIVITY:
+ return data->activity_event_en;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bma400_steps_event_enable(struct bma400_data *data, int state)
+{
+ int ret;
+
+ ret = bma400_enable_steps(data, 1);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, BMA400_INT12_MAP_REG,
+ BMA400_INT_CONFIG1_STEP_INT_MASK,
+ FIELD_PREP(BMA400_INT_CONFIG1_STEP_INT_MASK,
+ state));
+ if (ret)
+ return ret;
+ data->step_event_en = state;
+ return 0;
+}
+
+static int bma400_generic_event_en(struct bma400_data *data,
+ enum iio_event_direction dir,
+ int state)
+{
+ int ret;
+ unsigned int intrmask, regval;
+ enum bma400_generic_intr genintr;
+ enum bma400_detect_criterion detect_criterion;
+ const struct bma400_genintr_info *bma400_genintr;
+
+ bma400_genintr = get_bma400_genintr_info(dir);
+ if (!bma400_genintr)
+ return -EINVAL;
+
+ genintr = bma400_genintr->genintr;
+ detect_criterion = bma400_genintr->detect_mode;
+ intrmask = bma400_genintr->intrmask;
+
+ /*
+ * Enabling all axis for interrupt evaluation
+ * Acc_filt2 is recommended as data source in datasheet (Section 4.7)
+ */
+ ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 0),
+ BMA400_GENINT_CONFIG0_X_EN_MASK |
+ BMA400_GENINT_CONFIG0_Y_EN_MASK |
+ BMA400_GENINT_CONFIG0_Z_EN_MASK|
+ FIELD_PREP(BMA400_GENINT_CONFIG0_DATA_SRC_MASK, ACCEL_FILT2)|
+ FIELD_PREP(BMA400_GENINT_CONFIG0_REF_UPD_MODE_MASK,
+ BMA400_REF_EVERYTIME_UPDT_MODE));
+ if (ret)
+ return ret;
+
+ /* OR combination of all axis for interrupt evaluation */
+ regval = FIELD_PREP(BMA400_GENINT_CONFIG1_AXES_COMB_MASK, BMA400_EVAL_X_OR_Y_OR_Z) |
+ FIELD_PREP(BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK, detect_criterion);
+ ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 1), regval);
+ if (ret)
+ return ret;
+
+ /*
+ * Initial value to avoid interrupts while enabling
+ * Value is in units of 8mg/lsb, i.e. effective val is val * 8mg/lsb
+ */
+ ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 2), 0x0A);
+ if (ret)
+ return ret;
+
+ /* Initial duration value to avoid interrupts while enabling*/
+ ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 4), 0x0F);
+ if (ret)
+ return ret;
+
+ regval = state ? intrmask : 0;
+ ret = regmap_update_bits(data->regmap, BMA400_INT1_MAP_REG, intrmask, regval);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG0_REG, intrmask, regval);
+ if (ret)
+ return ret;
+
+ set_mask_bits(&data->generic_event_en, intrmask, regval);
+ return 0;
+}
+
+static int bma400_tap_event_en(struct bma400_data *data,
+ enum iio_event_direction dir, int state)
+{
+ unsigned int mask;
+ unsigned int field_value = 0;
+ int ret;
+
+ /*
+ * Tap interrupts can be configured only in normal mode.
+ * See table in section 4.3 "Power modes - performance modes" of
+ * datasheet v1.2.
+ */
+ if (data->power_mode != POWER_MODE_NORMAL)
+ return -EINVAL;
+
+ /*
+ * Tap interrupts are operating with a data rate of 200Hz.
+ * See section 4.7 "Tap sensing interrupt" in datasheet v1.2.
+ */
+ if (data->sample_freq.hz != 200 && state) {
+ dev_err(data->dev, "Invalid data rate for tap interrupts.\n");
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(data->regmap, BMA400_INT12_MAP_REG,
+ BMA400_INT_CONFIG1_S_TAP_MASK,
+ FIELD_PREP(BMA400_INT_CONFIG1_S_TAP_MASK, state));
+ if (ret)
+ return ret;
+
+ switch (dir) {
+ case IIO_EV_DIR_SINGLETAP:
+ mask = BMA400_INT_CONFIG1_S_TAP_MASK;
+ set_mask_bits(&field_value, BMA400_INT_CONFIG1_S_TAP_MASK,
+ FIELD_PREP(BMA400_INT_CONFIG1_S_TAP_MASK, state));
+ break;
+ case IIO_EV_DIR_DOUBLETAP:
+ mask = BMA400_INT_CONFIG1_D_TAP_MASK;
+ set_mask_bits(&field_value, BMA400_INT_CONFIG1_D_TAP_MASK,
+ FIELD_PREP(BMA400_INT_CONFIG1_D_TAP_MASK, state));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG1_REG, mask,
+ field_value);
+ if (ret)
+ return ret;
+
+ set_mask_bits(&data->tap_event_en_bitmask, mask, field_value);
+
+ return 0;
+}
+
+static int bma400_disable_adv_interrupt(struct bma400_data *data)
+{
+ int ret;
+
+ ret = regmap_write(data->regmap, BMA400_INT_CONFIG0_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, BMA400_INT_CONFIG1_REG, 0);
+ if (ret)
+ return ret;
+
+ data->tap_event_en_bitmask = 0;
+ data->generic_event_en = 0;
+ data->step_event_en = false;
+ data->activity_event_en = false;
+
+ return 0;
+}
+
+static int bma400_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir, bool state)
+{
+ struct bma400_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (chan->type) {
+ case IIO_ACCEL:
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ mutex_lock(&data->mutex);
+ ret = bma400_generic_event_en(data, dir, state);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_EV_TYPE_GESTURE:
+ mutex_lock(&data->mutex);
+ ret = bma400_tap_event_en(data, dir, state);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ case IIO_STEPS:
+ mutex_lock(&data->mutex);
+ ret = bma400_steps_event_enable(data, state);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_ACTIVITY:
+ mutex_lock(&data->mutex);
+ if (!data->step_event_en) {
+ ret = bma400_steps_event_enable(data, true);
+ if (ret) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ }
+ data->activity_event_en = state;
+ mutex_unlock(&data->mutex);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bma400_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct bma400_data *data = iio_priv(indio_dev);
+ int ret, reg_val, raw;
+ enum bma400_generic_intr genintr;
+ const struct bma400_genintr_info *bma400_genintr;
+
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ bma400_genintr = get_bma400_genintr_info(dir);
+ if (!bma400_genintr)
+ return -EINVAL;
+ genintr = bma400_genintr->genintr;
+
+ *val2 = 0;
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = regmap_read(data->regmap,
+ BMA400_GENINT_CONFIG_REG(genintr, 2),
+ val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_EV_INFO_PERIOD:
+ mutex_lock(&data->mutex);
+ ret = regmap_bulk_read(data->regmap,
+ BMA400_GENINT_CONFIG_REG(genintr, 3),
+ &data->duration,
+ sizeof(data->duration));
+ if (ret) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ *val = be16_to_cpu(data->duration);
+ mutex_unlock(&data->mutex);
+ return IIO_VAL_INT;
+ case IIO_EV_INFO_HYSTERESIS:
+ ret = regmap_read(data->regmap,
+ BMA400_GENINT_CONFIG_REG(genintr, 0),
+ val);
+ if (ret)
+ return ret;
+ *val = FIELD_GET(BMA400_GENINT_CONFIG0_HYST_MASK, *val);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = regmap_read(data->regmap, BMA400_TAP_CONFIG_REG,
+ &reg_val);
+ if (ret)
+ return ret;
+
+ *val = FIELD_GET(BMA400_TAP_CONFIG_SEN_MASK, reg_val);
+ return IIO_VAL_INT;
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG,
+ &reg_val);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(BMA400_TAP_CONFIG1_QUIET_MASK, reg_val);
+ *val = 0;
+ *val2 = tap_reset_timeout[raw];
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG,
+ &reg_val);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(BMA400_TAP_CONFIG1_QUIETDT_MASK, reg_val);
+ *val = 0;
+ *val2 = double_tap2_min_delay[raw];
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bma400_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct bma400_data *data = iio_priv(indio_dev);
+ int ret, raw;
+ enum bma400_generic_intr genintr;
+ const struct bma400_genintr_info *bma400_genintr;
+
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ bma400_genintr = get_bma400_genintr_info(dir);
+ if (!bma400_genintr)
+ return -EINVAL;
+ genintr = bma400_genintr->genintr;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ if (val < 1 || val > 255)
+ return -EINVAL;
+
+ return regmap_write(data->regmap,
+ BMA400_GENINT_CONFIG_REG(genintr, 2),
+ val);
+ case IIO_EV_INFO_PERIOD:
+ if (val < 1 || val > 65535)
+ return -EINVAL;
+
+ mutex_lock(&data->mutex);
+ put_unaligned_be16(val, &data->duration);
+ ret = regmap_bulk_write(data->regmap,
+ BMA400_GENINT_CONFIG_REG(genintr, 3),
+ &data->duration,
+ sizeof(data->duration));
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_EV_INFO_HYSTERESIS:
+ if (val < 0 || val > 3)
+ return -EINVAL;
+
+ return regmap_update_bits(data->regmap,
+ BMA400_GENINT_CONFIG_REG(genintr, 0),
+ BMA400_GENINT_CONFIG0_HYST_MASK,
+ FIELD_PREP(BMA400_GENINT_CONFIG0_HYST_MASK,
+ val));
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ if (val < 0 || val > 7)
+ return -EINVAL;
+
+ return regmap_update_bits(data->regmap,
+ BMA400_TAP_CONFIG_REG,
+ BMA400_TAP_CONFIG_SEN_MASK,
+ FIELD_PREP(BMA400_TAP_CONFIG_SEN_MASK,
+ val));
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ raw = usec_to_tapreg_raw(val2, tap_reset_timeout);
+ if (raw < 0)
+ return -EINVAL;
+
+ return regmap_update_bits(data->regmap,
+ BMA400_TAP_CONFIG1_REG,
+ BMA400_TAP_CONFIG1_QUIET_MASK,
+ FIELD_PREP(BMA400_TAP_CONFIG1_QUIET_MASK,
+ raw));
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ raw = usec_to_tapreg_raw(val2, double_tap2_min_delay);
+ if (raw < 0)
+ return -EINVAL;
+
+ return regmap_update_bits(data->regmap,
+ BMA400_TAP_CONFIG1_REG,
+ BMA400_TAP_CONFIG1_QUIETDT_MASK,
+ FIELD_PREP(BMA400_TAP_CONFIG1_QUIETDT_MASK,
+ raw));
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bma400_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct bma400_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG0_REG,
+ BMA400_INT_CONFIG0_DRDY_MASK,
+ FIELD_PREP(BMA400_INT_CONFIG0_DRDY_MASK, state));
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(data->regmap, BMA400_INT1_MAP_REG,
+ BMA400_INT_CONFIG0_DRDY_MASK,
+ FIELD_PREP(BMA400_INT_CONFIG0_DRDY_MASK, state));
+}
+
+static const unsigned long bma400_avail_scan_masks[] = {
+ BIT(BMA400_ACCL_X) | BIT(BMA400_ACCL_Y) | BIT(BMA400_ACCL_Z),
+ BIT(BMA400_ACCL_X) | BIT(BMA400_ACCL_Y) | BIT(BMA400_ACCL_Z)
+ | BIT(BMA400_TEMP),
+ 0
+};
+
+static const struct iio_info bma400_info = {
+ .read_raw = bma400_read_raw,
+ .read_avail = bma400_read_avail,
+ .write_raw = bma400_write_raw,
+ .write_raw_get_fmt = bma400_write_raw_get_fmt,
+ .read_event_config = bma400_read_event_config,
+ .write_event_config = bma400_write_event_config,
+ .write_event_value = bma400_write_event_value,
+ .read_event_value = bma400_read_event_value,
+ .event_attrs = &bma400_event_attribute_group,
+};
+
+static const struct iio_trigger_ops bma400_trigger_ops = {
+ .set_trigger_state = &bma400_data_rdy_trigger_set_state,
+ .validate_device = &iio_trigger_validate_own_device,
+};
+
+static irqreturn_t bma400_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct bma400_data *data = iio_priv(indio_dev);
+ int ret, temp;
+
+ /* Lock to protect the data->buffer */
+ mutex_lock(&data->mutex);
+
+ /* bulk read six registers, with the base being the LSB register */
+ ret = regmap_bulk_read(data->regmap, BMA400_ACC_X_LSB_REG,
+ &data->buffer.buff, sizeof(data->buffer.buff));
+ if (ret)
+ goto unlock_err;
+
+ if (test_bit(BMA400_TEMP, indio_dev->active_scan_mask)) {
+ ret = regmap_read(data->regmap, BMA400_TEMP_DATA_REG, &temp);
+ if (ret)
+ goto unlock_err;
+
+ data->buffer.temperature = temp;
+ }
+
+ iio_push_to_buffers_with_ts(indio_dev, &data->buffer,
+ sizeof(data->buffer),
+ iio_get_time_ns(indio_dev));
+
+ mutex_unlock(&data->mutex);
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+
+unlock_err:
+ mutex_unlock(&data->mutex);
+ return IRQ_NONE;
+}
+
+static irqreturn_t bma400_interrupt(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct bma400_data *data = iio_priv(indio_dev);
+ s64 timestamp = iio_get_time_ns(indio_dev);
+ unsigned int act, ev_dir = IIO_EV_DIR_NONE;
+ int ret;
+
+ /* Lock to protect the data->status */
+ mutex_lock(&data->mutex);
+ ret = regmap_bulk_read(data->regmap, BMA400_INT_STAT0_REG,
+ &data->status,
+ sizeof(data->status));
+ /*
+ * if none of the bit is set in the status register then it is
+ * spurious interrupt.
+ */
+ if (ret || !data->status)
+ goto unlock_err;
+
+ /*
+ * Disable all advance interrupts if interrupt engine overrun occurs.
+ * See section 4.7 "Interrupt engine overrun" in datasheet v1.2.
+ */
+ if (FIELD_GET(BMA400_INT_STAT_ENG_OVRRUN_MASK, le16_to_cpu(data->status))) {
+ bma400_disable_adv_interrupt(data);
+ dev_err(data->dev, "Interrupt engine overrun\n");
+ goto unlock_err;
+ }
+
+ if (FIELD_GET(BMA400_INT_STAT1_S_TAP_MASK, le16_to_cpu(data->status)))
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_GESTURE,
+ IIO_EV_DIR_SINGLETAP),
+ timestamp);
+
+ if (FIELD_GET(BMA400_INT_STAT1_D_TAP_MASK, le16_to_cpu(data->status)))
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_GESTURE,
+ IIO_EV_DIR_DOUBLETAP),
+ timestamp);
+
+ if (FIELD_GET(BMA400_INT_STAT0_GEN1_MASK, le16_to_cpu(data->status)))
+ ev_dir = IIO_EV_DIR_RISING;
+
+ if (FIELD_GET(BMA400_INT_STAT0_GEN2_MASK, le16_to_cpu(data->status)))
+ ev_dir = IIO_EV_DIR_FALLING;
+
+ if (ev_dir != IIO_EV_DIR_NONE) {
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_MAG, ev_dir),
+ timestamp);
+ }
+
+ if (FIELD_GET(BMA400_INT_STAT1_STEP_INT_MASK, le16_to_cpu(data->status))) {
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_STEPS, 0, IIO_NO_MOD,
+ IIO_EV_TYPE_CHANGE,
+ IIO_EV_DIR_NONE),
+ timestamp);
+
+ if (data->activity_event_en) {
+ ret = regmap_read(data->regmap, BMA400_STEP_STAT_REG,
+ &act);
+ if (ret)
+ goto unlock_err;
+
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACTIVITY, 0,
+ bma400_act_to_mod(act),
+ IIO_EV_TYPE_CHANGE,
+ IIO_EV_DIR_NONE),
+ timestamp);
+ }
+ }
+
+ if (FIELD_GET(BMA400_INT_STAT0_DRDY_MASK, le16_to_cpu(data->status))) {
+ mutex_unlock(&data->mutex);
+ iio_trigger_poll_nested(data->trig);
+ return IRQ_HANDLED;
+ }
+
+ mutex_unlock(&data->mutex);
+ return IRQ_HANDLED;
+
+unlock_err:
+ mutex_unlock(&data->mutex);
+ return IRQ_NONE;
+}
+
+int bma400_probe(struct device *dev, struct regmap *regmap, int irq,
+ const char *name)
+{
+ struct iio_dev *indio_dev;
+ struct bma400_data *data;
+ int ret;
+
+ 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;
+
+ ret = bma400_init(data);
+ if (ret)
+ return ret;
+
+ ret = iio_read_mount_matrix(dev, &data->orientation);
+ if (ret)
+ return ret;
+
+ mutex_init(&data->mutex);
+ indio_dev->name = name;
+ indio_dev->info = &bma400_info;
+ indio_dev->channels = bma400_channels;
+ indio_dev->num_channels = ARRAY_SIZE(bma400_channels);
+ indio_dev->available_scan_masks = bma400_avail_scan_masks;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ if (irq > 0) {
+ data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->trig)
+ return -ENOMEM;
+
+ data->trig->ops = &bma400_trigger_ops;
+ iio_trigger_set_drvdata(data->trig, indio_dev);
+
+ ret = devm_iio_trigger_register(data->dev, data->trig);
+ if (ret)
+ return dev_err_probe(data->dev, ret,
+ "iio trigger register fail\n");
+
+ indio_dev->trig = iio_trigger_get(data->trig);
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ &bma400_interrupt,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
+ &bma400_trigger_handler, NULL);
+ if (ret)
+ return dev_err_probe(data->dev, ret,
+ "iio triggered buffer setup failed\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS(bma400_probe, "IIO_BMA400");
+
+MODULE_AUTHOR("Dan Robertson <dan@dlrobertson.com>");
+MODULE_AUTHOR("Jagath Jog J <jagathjog1996@gmail.com>");
+MODULE_DESCRIPTION("Bosch BMA400 triaxial acceleration sensor core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/bma400_i2c.c b/drivers/iio/accel/bma400_i2c.c
new file mode 100644
index 000000000..a3fb3b81b
--- /dev/null
+++ b/drivers/iio/accel/bma400_i2c.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * I2C IIO driver for Bosch BMA400 triaxial acceleration sensor.
+ *
+ * Copyright 2019 Dan Robertson <dan@dlrobertson.com>
+ *
+ * I2C address is either 0x14 or 0x15 depending on SDO
+ */
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "bma400.h"
+
+static int bma400_i2c_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_i2c(client, &bma400_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "failed to create regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ return bma400_probe(&client->dev, regmap, client->irq, id->name);
+}
+
+static const struct i2c_device_id bma400_i2c_ids[] = {
+ { .name = "bma400" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, bma400_i2c_ids);
+
+static const struct of_device_id bma400_of_i2c_match[] = {
+ { .compatible = "bosch,bma400" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bma400_of_i2c_match);
+
+static struct i2c_driver bma400_i2c_driver = {
+ .driver = {
+ .name = "bma400",
+ .of_match_table = bma400_of_i2c_match,
+ },
+ .probe = bma400_i2c_probe,
+ .id_table = bma400_i2c_ids,
+};
+
+module_i2c_driver(bma400_i2c_driver);
+
+MODULE_AUTHOR("Dan Robertson <dan@dlrobertson.com>");
+MODULE_DESCRIPTION("Bosch BMA400 triaxial acceleration sensor (I2C)");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_BMA400");
diff --git a/drivers/iio/accel/bma400_spi.c b/drivers/iio/accel/bma400_spi.c
new file mode 100644
index 000000000..7ac0a7881
--- /dev/null
+++ b/drivers/iio/accel/bma400_spi.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SPI IIO driver for Bosch BMA400 triaxial acceleration sensor.
+ *
+ * Copyright 2020 Dan Robertson <dan@dlrobertson.com>
+ *
+ */
+#include <linux/bits.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "bma400.h"
+
+#define BMA400_MAX_SPI_READ 2
+#define BMA400_SPI_READ_BUFFER_SIZE (BMA400_MAX_SPI_READ + 1)
+
+static int bma400_regmap_spi_read(void *context,
+ const void *reg, size_t reg_size,
+ void *val, size_t val_size)
+{
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ u8 result[BMA400_SPI_READ_BUFFER_SIZE];
+ ssize_t status;
+
+ if (val_size > BMA400_MAX_SPI_READ)
+ return -EINVAL;
+
+ status = spi_write_then_read(spi, reg, 1, result, val_size + 1);
+ if (status)
+ return status;
+
+ /*
+ * From the BMA400 datasheet:
+ *
+ * > For a basic read operation two bytes have to be read and the first
+ * > has to be dropped and the second byte must be interpreted.
+ */
+ memcpy(val, result + 1, val_size);
+
+ return 0;
+}
+
+static int bma400_regmap_spi_write(void *context, const void *data,
+ size_t count)
+{
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+
+ return spi_write(spi, data, count);
+}
+
+static const struct regmap_bus bma400_regmap_bus = {
+ .read = bma400_regmap_spi_read,
+ .write = bma400_regmap_spi_write,
+ .read_flag_mask = BIT(7),
+ .max_raw_read = BMA400_MAX_SPI_READ,
+};
+
+static int bma400_spi_probe(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+ struct regmap *regmap;
+ unsigned int val;
+ int ret;
+
+ regmap = devm_regmap_init(&spi->dev, &bma400_regmap_bus,
+ &spi->dev, &bma400_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&spi->dev, "failed to create regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ /*
+ * Per the bma400 datasheet, the first SPI read may
+ * return garbage. As the datasheet recommends, the
+ * chip ID register will be read here and checked
+ * again in the following probe.
+ */
+ ret = regmap_read(regmap, BMA400_CHIP_ID_REG, &val);
+ if (ret)
+ dev_err(&spi->dev, "Failed to read chip id register\n");
+
+ return bma400_probe(&spi->dev, regmap, spi->irq, id->name);
+}
+
+static const struct spi_device_id bma400_spi_ids[] = {
+ { .name = "bma400" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, bma400_spi_ids);
+
+static const struct of_device_id bma400_of_spi_match[] = {
+ { .compatible = "bosch,bma400" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bma400_of_spi_match);
+
+static struct spi_driver bma400_spi_driver = {
+ .driver = {
+ .name = "bma400",
+ .of_match_table = bma400_of_spi_match,
+ },
+ .probe = bma400_spi_probe,
+ .id_table = bma400_spi_ids,
+};
+
+module_spi_driver(bma400_spi_driver);
+MODULE_AUTHOR("Dan Robertson <dan@dlrobertson.com>");
+MODULE_DESCRIPTION("Bosch BMA400 triaxial acceleration sensor (SPI)");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_BMA400");
diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c
new file mode 100644
index 000000000..46bb36d93
--- /dev/null
+++ b/drivers/iio/accel/bmc150-accel-core.c
@@ -0,0 +1,1868 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 3-axis accelerometer driver supporting many Bosch-Sensortec chips
+ * Copyright (c) 2014, Intel Corporation.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include "bmc150-accel.h"
+
+#define BMC150_ACCEL_REG_CHIP_ID 0x00
+
+#define BMC150_ACCEL_REG_INT_STATUS_2 0x0B
+#define BMC150_ACCEL_ANY_MOTION_MASK 0x07
+#define BMC150_ACCEL_ANY_MOTION_BIT_X BIT(0)
+#define BMC150_ACCEL_ANY_MOTION_BIT_Y BIT(1)
+#define BMC150_ACCEL_ANY_MOTION_BIT_Z BIT(2)
+#define BMC150_ACCEL_ANY_MOTION_BIT_SIGN BIT(3)
+
+#define BMC150_ACCEL_REG_PMU_LPW 0x11
+#define BMC150_ACCEL_PMU_MODE_MASK 0xE0
+#define BMC150_ACCEL_PMU_MODE_SHIFT 5
+#define BMC150_ACCEL_PMU_BIT_SLEEP_DUR_MASK 0x17
+#define BMC150_ACCEL_PMU_BIT_SLEEP_DUR_SHIFT 1
+
+#define BMC150_ACCEL_REG_PMU_RANGE 0x0F
+
+#define BMC150_ACCEL_DEF_RANGE_2G 0x03
+#define BMC150_ACCEL_DEF_RANGE_4G 0x05
+#define BMC150_ACCEL_DEF_RANGE_8G 0x08
+#define BMC150_ACCEL_DEF_RANGE_16G 0x0C
+
+/* Default BW: 125Hz */
+#define BMC150_ACCEL_REG_PMU_BW 0x10
+#define BMC150_ACCEL_DEF_BW 125
+
+#define BMC150_ACCEL_REG_RESET 0x14
+#define BMC150_ACCEL_RESET_VAL 0xB6
+
+#define BMC150_ACCEL_REG_INT_MAP_0 0x19
+#define BMC150_ACCEL_INT_MAP_0_BIT_INT1_SLOPE BIT(2)
+
+#define BMC150_ACCEL_REG_INT_MAP_1 0x1A
+#define BMC150_ACCEL_INT_MAP_1_BIT_INT1_DATA BIT(0)
+#define BMC150_ACCEL_INT_MAP_1_BIT_INT1_FWM BIT(1)
+#define BMC150_ACCEL_INT_MAP_1_BIT_INT1_FFULL BIT(2)
+#define BMC150_ACCEL_INT_MAP_1_BIT_INT2_FFULL BIT(5)
+#define BMC150_ACCEL_INT_MAP_1_BIT_INT2_FWM BIT(6)
+#define BMC150_ACCEL_INT_MAP_1_BIT_INT2_DATA BIT(7)
+
+#define BMC150_ACCEL_REG_INT_MAP_2 0x1B
+#define BMC150_ACCEL_INT_MAP_2_BIT_INT2_SLOPE BIT(2)
+
+#define BMC150_ACCEL_REG_INT_RST_LATCH 0x21
+#define BMC150_ACCEL_INT_MODE_LATCH_RESET 0x80
+#define BMC150_ACCEL_INT_MODE_LATCH_INT 0x0F
+#define BMC150_ACCEL_INT_MODE_NON_LATCH_INT 0x00
+
+#define BMC150_ACCEL_REG_INT_EN_0 0x16
+#define BMC150_ACCEL_INT_EN_BIT_SLP_X BIT(0)
+#define BMC150_ACCEL_INT_EN_BIT_SLP_Y BIT(1)
+#define BMC150_ACCEL_INT_EN_BIT_SLP_Z BIT(2)
+
+#define BMC150_ACCEL_REG_INT_EN_1 0x17
+#define BMC150_ACCEL_INT_EN_BIT_DATA_EN BIT(4)
+#define BMC150_ACCEL_INT_EN_BIT_FFULL_EN BIT(5)
+#define BMC150_ACCEL_INT_EN_BIT_FWM_EN BIT(6)
+
+#define BMC150_ACCEL_REG_INT_OUT_CTRL 0x20
+#define BMC150_ACCEL_INT_OUT_CTRL_INT1_LVL BIT(0)
+#define BMC150_ACCEL_INT_OUT_CTRL_INT2_LVL BIT(2)
+
+#define BMC150_ACCEL_REG_INT_5 0x27
+#define BMC150_ACCEL_SLOPE_DUR_MASK 0x03
+
+#define BMC150_ACCEL_REG_INT_6 0x28
+#define BMC150_ACCEL_SLOPE_THRES_MASK 0xFF
+
+/* Slope duration in terms of number of samples */
+#define BMC150_ACCEL_DEF_SLOPE_DURATION 1
+/* in terms of multiples of g's/LSB, based on range */
+#define BMC150_ACCEL_DEF_SLOPE_THRESHOLD 1
+
+#define BMC150_ACCEL_REG_XOUT_L 0x02
+
+#define BMC150_ACCEL_MAX_STARTUP_TIME_MS 100
+
+/* Sleep Duration values */
+#define BMC150_ACCEL_SLEEP_500_MICRO 0x05
+#define BMC150_ACCEL_SLEEP_1_MS 0x06
+#define BMC150_ACCEL_SLEEP_2_MS 0x07
+#define BMC150_ACCEL_SLEEP_4_MS 0x08
+#define BMC150_ACCEL_SLEEP_6_MS 0x09
+#define BMC150_ACCEL_SLEEP_10_MS 0x0A
+#define BMC150_ACCEL_SLEEP_25_MS 0x0B
+#define BMC150_ACCEL_SLEEP_50_MS 0x0C
+#define BMC150_ACCEL_SLEEP_100_MS 0x0D
+#define BMC150_ACCEL_SLEEP_500_MS 0x0E
+#define BMC150_ACCEL_SLEEP_1_SEC 0x0F
+
+#define BMC150_ACCEL_REG_TEMP 0x08
+#define BMC150_ACCEL_TEMP_CENTER_VAL 23
+
+#define BMC150_ACCEL_AXIS_TO_REG(axis) (BMC150_ACCEL_REG_XOUT_L + (axis * 2))
+#define BMC150_AUTO_SUSPEND_DELAY_MS 2000
+
+#define BMC150_ACCEL_REG_FIFO_STATUS 0x0E
+#define BMC150_ACCEL_REG_FIFO_CONFIG0 0x30
+#define BMC150_ACCEL_REG_FIFO_CONFIG1 0x3E
+#define BMC150_ACCEL_REG_FIFO_DATA 0x3F
+#define BMC150_ACCEL_FIFO_LENGTH 32
+
+enum bmc150_accel_axis {
+ AXIS_X,
+ AXIS_Y,
+ AXIS_Z,
+ AXIS_MAX,
+};
+
+enum bmc150_power_modes {
+ BMC150_ACCEL_SLEEP_MODE_NORMAL,
+ BMC150_ACCEL_SLEEP_MODE_DEEP_SUSPEND,
+ BMC150_ACCEL_SLEEP_MODE_LPM,
+ BMC150_ACCEL_SLEEP_MODE_SUSPEND = 0x04,
+};
+
+struct bmc150_scale_info {
+ int scale;
+ u8 reg_range;
+};
+
+struct bmc150_accel_chip_info {
+ const char *name;
+ u8 chip_id;
+ const struct iio_chan_spec *channels;
+ int num_channels;
+ const struct bmc150_scale_info scale_table[4];
+};
+
+static const struct {
+ int val;
+ int val2;
+ u8 bw_bits;
+} bmc150_accel_samp_freq_table[] = { {15, 620000, 0x08},
+ {31, 260000, 0x09},
+ {62, 500000, 0x0A},
+ {125, 0, 0x0B},
+ {250, 0, 0x0C},
+ {500, 0, 0x0D},
+ {1000, 0, 0x0E},
+ {2000, 0, 0x0F} };
+
+static __maybe_unused const struct {
+ int bw_bits;
+ int msec;
+} bmc150_accel_sample_upd_time[] = { {0x08, 64},
+ {0x09, 32},
+ {0x0A, 16},
+ {0x0B, 8},
+ {0x0C, 4},
+ {0x0D, 2},
+ {0x0E, 1},
+ {0x0F, 1} };
+
+static const struct {
+ int sleep_dur;
+ u8 reg_value;
+} bmc150_accel_sleep_value_table[] = { {0, 0},
+ {500, BMC150_ACCEL_SLEEP_500_MICRO},
+ {1000, BMC150_ACCEL_SLEEP_1_MS},
+ {2000, BMC150_ACCEL_SLEEP_2_MS},
+ {4000, BMC150_ACCEL_SLEEP_4_MS},
+ {6000, BMC150_ACCEL_SLEEP_6_MS},
+ {10000, BMC150_ACCEL_SLEEP_10_MS},
+ {25000, BMC150_ACCEL_SLEEP_25_MS},
+ {50000, BMC150_ACCEL_SLEEP_50_MS},
+ {100000, BMC150_ACCEL_SLEEP_100_MS},
+ {500000, BMC150_ACCEL_SLEEP_500_MS},
+ {1000000, BMC150_ACCEL_SLEEP_1_SEC} };
+
+const struct regmap_config bmc150_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x3f,
+};
+EXPORT_SYMBOL_NS_GPL(bmc150_regmap_conf, "IIO_BMC150");
+
+static int bmc150_accel_set_mode(struct bmc150_accel_data *data,
+ enum bmc150_power_modes mode,
+ int dur_us)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int i;
+ int ret;
+ u8 lpw_bits;
+ int dur_val = -1;
+
+ if (dur_us > 0) {
+ for (i = 0; i < ARRAY_SIZE(bmc150_accel_sleep_value_table);
+ ++i) {
+ if (bmc150_accel_sleep_value_table[i].sleep_dur ==
+ dur_us)
+ dur_val =
+ bmc150_accel_sleep_value_table[i].reg_value;
+ }
+ } else {
+ dur_val = 0;
+ }
+
+ if (dur_val < 0)
+ return -EINVAL;
+
+ lpw_bits = mode << BMC150_ACCEL_PMU_MODE_SHIFT;
+ lpw_bits |= (dur_val << BMC150_ACCEL_PMU_BIT_SLEEP_DUR_SHIFT);
+
+ dev_dbg(dev, "Set Mode bits %x\n", lpw_bits);
+
+ ret = regmap_write(data->regmap, BMC150_ACCEL_REG_PMU_LPW, lpw_bits);
+ if (ret < 0) {
+ dev_err(dev, "Error writing reg_pmu_lpw\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bmc150_accel_set_bw(struct bmc150_accel_data *data, int val,
+ int val2)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(bmc150_accel_samp_freq_table); ++i) {
+ if (bmc150_accel_samp_freq_table[i].val == val &&
+ bmc150_accel_samp_freq_table[i].val2 == val2) {
+ ret = regmap_write(data->regmap,
+ BMC150_ACCEL_REG_PMU_BW,
+ bmc150_accel_samp_freq_table[i].bw_bits);
+ if (ret < 0)
+ return ret;
+
+ data->bw_bits =
+ bmc150_accel_samp_freq_table[i].bw_bits;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int bmc150_accel_update_slope(struct bmc150_accel_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_6,
+ data->slope_thres);
+ if (ret < 0) {
+ dev_err(dev, "Error writing reg_int_6\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(data->regmap, BMC150_ACCEL_REG_INT_5,
+ BMC150_ACCEL_SLOPE_DUR_MASK, data->slope_dur);
+ if (ret < 0) {
+ dev_err(dev, "Error updating reg_int_5\n");
+ return ret;
+ }
+
+ dev_dbg(dev, "%x %x\n", data->slope_thres, data->slope_dur);
+
+ return ret;
+}
+
+static int bmc150_accel_any_motion_setup(struct bmc150_accel_trigger *t,
+ bool state)
+{
+ if (state)
+ return bmc150_accel_update_slope(t->data);
+
+ return 0;
+}
+
+static int bmc150_accel_get_bw(struct bmc150_accel_data *data, int *val,
+ int *val2)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bmc150_accel_samp_freq_table); ++i) {
+ if (bmc150_accel_samp_freq_table[i].bw_bits == data->bw_bits) {
+ *val = bmc150_accel_samp_freq_table[i].val;
+ *val2 = bmc150_accel_samp_freq_table[i].val2;
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+ }
+
+ return -EINVAL;
+}
+
+#ifdef CONFIG_PM
+static int bmc150_accel_get_startup_times(struct bmc150_accel_data *data)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bmc150_accel_sample_upd_time); ++i) {
+ if (bmc150_accel_sample_upd_time[i].bw_bits == data->bw_bits)
+ return bmc150_accel_sample_upd_time[i].msec;
+ }
+
+ return BMC150_ACCEL_MAX_STARTUP_TIME_MS;
+}
+
+static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ if (on)
+ ret = pm_runtime_resume_and_get(dev);
+ else
+ ret = pm_runtime_put_autosuspend(dev);
+ if (ret < 0) {
+ dev_err(dev,
+ "Failed: %s for %d\n", __func__, on);
+ return ret;
+ }
+
+ return 0;
+}
+#else
+static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on)
+{
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_ACPI
+/*
+ * Support for getting accelerometer information from BOSC0200 ACPI nodes.
+ *
+ * There are 2 variants of the BOSC0200 ACPI node. Some 2-in-1s with 360 degree
+ * hinges declare 2 I2C ACPI-resources for 2 accelerometers, 1 in the display
+ * and 1 in the base of the 2-in-1. On these 2-in-1s the ROMS ACPI object
+ * contains the mount-matrix for the sensor in the display and ROMK contains
+ * the mount-matrix for the sensor in the base. On devices using a single
+ * sensor there is a ROTM ACPI object which contains the mount-matrix.
+ *
+ * Here is an incomplete list of devices known to use 1 of these setups:
+ *
+ * Yoga devices with 2 accelerometers using ROMS + ROMK for the mount-matrices:
+ * Lenovo Thinkpad Yoga 11e 3th gen
+ * Lenovo Thinkpad Yoga 11e 4th gen
+ *
+ * Tablets using a single accelerometer using ROTM for the mount-matrix:
+ * Chuwi Hi8 Pro (CWI513)
+ * Chuwi Vi8 Plus (CWI519)
+ * Chuwi Hi13
+ * Irbis TW90
+ * Jumper EZpad mini 3
+ * Onda V80 plus
+ * Predia Basic Tablet
+ */
+static bool bmc150_apply_bosc0200_acpi_orientation(struct device *dev,
+ struct iio_mount_matrix *orientation)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ acpi_handle handle = ACPI_HANDLE(dev);
+ char *name, *alt_name, *label;
+
+ if (strcmp(dev_name(dev), "i2c-BOSC0200:base") == 0) {
+ alt_name = "ROMK";
+ label = "accel-base";
+ } else {
+ alt_name = "ROMS";
+ label = "accel-display";
+ }
+
+ if (acpi_has_method(handle, "ROTM")) {
+ name = "ROTM";
+ } else if (acpi_has_method(handle, alt_name)) {
+ name = alt_name;
+ indio_dev->label = label;
+ } else {
+ return false;
+ }
+
+ return iio_read_acpi_mount_matrix(dev, orientation, name);
+}
+
+static bool bmc150_apply_dual250e_acpi_orientation(struct device *dev,
+ struct iio_mount_matrix *orientation)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+
+ if (strcmp(dev_name(dev), "i2c-DUAL250E:base") == 0)
+ indio_dev->label = "accel-base";
+ else
+ indio_dev->label = "accel-display";
+
+ return false; /* DUAL250E fwnodes have no mount matrix info */
+}
+
+static bool bmc150_apply_acpi_orientation(struct device *dev,
+ struct iio_mount_matrix *orientation)
+{
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+
+ if (adev && acpi_dev_hid_uid_match(adev, "BOSC0200", NULL))
+ return bmc150_apply_bosc0200_acpi_orientation(dev, orientation);
+
+ if (adev && acpi_dev_hid_uid_match(adev, "DUAL250E", NULL))
+ return bmc150_apply_dual250e_acpi_orientation(dev, orientation);
+
+ return false;
+}
+#else
+static bool bmc150_apply_acpi_orientation(struct device *dev,
+ struct iio_mount_matrix *orientation)
+{
+ return false;
+}
+#endif
+
+struct bmc150_accel_interrupt_info {
+ u8 map_reg;
+ u8 map_bitmask;
+ u8 en_reg;
+ u8 en_bitmask;
+};
+
+static const struct bmc150_accel_interrupt_info
+bmc150_accel_interrupts_int1[BMC150_ACCEL_INTERRUPTS] = {
+ { /* data ready interrupt */
+ .map_reg = BMC150_ACCEL_REG_INT_MAP_1,
+ .map_bitmask = BMC150_ACCEL_INT_MAP_1_BIT_INT1_DATA,
+ .en_reg = BMC150_ACCEL_REG_INT_EN_1,
+ .en_bitmask = BMC150_ACCEL_INT_EN_BIT_DATA_EN,
+ },
+ { /* motion interrupt */
+ .map_reg = BMC150_ACCEL_REG_INT_MAP_0,
+ .map_bitmask = BMC150_ACCEL_INT_MAP_0_BIT_INT1_SLOPE,
+ .en_reg = BMC150_ACCEL_REG_INT_EN_0,
+ .en_bitmask = BMC150_ACCEL_INT_EN_BIT_SLP_X |
+ BMC150_ACCEL_INT_EN_BIT_SLP_Y |
+ BMC150_ACCEL_INT_EN_BIT_SLP_Z
+ },
+ { /* fifo watermark interrupt */
+ .map_reg = BMC150_ACCEL_REG_INT_MAP_1,
+ .map_bitmask = BMC150_ACCEL_INT_MAP_1_BIT_INT1_FWM,
+ .en_reg = BMC150_ACCEL_REG_INT_EN_1,
+ .en_bitmask = BMC150_ACCEL_INT_EN_BIT_FWM_EN,
+ },
+};
+
+static const struct bmc150_accel_interrupt_info
+bmc150_accel_interrupts_int2[BMC150_ACCEL_INTERRUPTS] = {
+ { /* data ready interrupt */
+ .map_reg = BMC150_ACCEL_REG_INT_MAP_1,
+ .map_bitmask = BMC150_ACCEL_INT_MAP_1_BIT_INT2_DATA,
+ .en_reg = BMC150_ACCEL_REG_INT_EN_1,
+ .en_bitmask = BMC150_ACCEL_INT_EN_BIT_DATA_EN,
+ },
+ { /* motion interrupt */
+ .map_reg = BMC150_ACCEL_REG_INT_MAP_2,
+ .map_bitmask = BMC150_ACCEL_INT_MAP_2_BIT_INT2_SLOPE,
+ .en_reg = BMC150_ACCEL_REG_INT_EN_0,
+ .en_bitmask = BMC150_ACCEL_INT_EN_BIT_SLP_X |
+ BMC150_ACCEL_INT_EN_BIT_SLP_Y |
+ BMC150_ACCEL_INT_EN_BIT_SLP_Z
+ },
+ { /* fifo watermark interrupt */
+ .map_reg = BMC150_ACCEL_REG_INT_MAP_1,
+ .map_bitmask = BMC150_ACCEL_INT_MAP_1_BIT_INT2_FWM,
+ .en_reg = BMC150_ACCEL_REG_INT_EN_1,
+ .en_bitmask = BMC150_ACCEL_INT_EN_BIT_FWM_EN,
+ },
+};
+
+static void bmc150_accel_interrupts_setup(struct iio_dev *indio_dev,
+ struct bmc150_accel_data *data, int irq)
+{
+ const struct bmc150_accel_interrupt_info *irq_info = NULL;
+ struct device *dev = regmap_get_device(data->regmap);
+ int i;
+
+ /*
+ * For now we map all interrupts to the same output pin.
+ * However, some boards may have just INT2 (and not INT1) connected,
+ * so we try to detect which IRQ it is based on the interrupt-names.
+ * Without interrupt-names, we assume the irq belongs to INT1.
+ */
+ irq_info = bmc150_accel_interrupts_int1;
+ if (data->type == BOSCH_BMC156 ||
+ irq == fwnode_irq_get_byname(dev_fwnode(dev), "INT2"))
+ irq_info = bmc150_accel_interrupts_int2;
+
+ for (i = 0; i < BMC150_ACCEL_INTERRUPTS; i++)
+ data->interrupts[i].info = &irq_info[i];
+}
+
+static int bmc150_accel_set_interrupt(struct bmc150_accel_data *data, int i,
+ bool state)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ struct bmc150_accel_interrupt *intr = &data->interrupts[i];
+ const struct bmc150_accel_interrupt_info *info = intr->info;
+ int ret;
+
+ /* We do not always have an IRQ */
+ if (data->irq <= 0)
+ return 0;
+
+ if (state) {
+ if (atomic_inc_return(&intr->users) > 1)
+ return 0;
+ } else {
+ if (atomic_dec_return(&intr->users) > 0)
+ return 0;
+ }
+
+ /*
+ * We will expect the enable and disable to do operation in reverse
+ * order. This will happen here anyway, as our resume operation uses
+ * sync mode runtime pm calls. The suspend operation will be delayed
+ * by autosuspend delay.
+ * So the disable operation will still happen in reverse order of
+ * enable operation. When runtime pm is disabled the mode is always on,
+ * so sequence doesn't matter.
+ */
+ ret = bmc150_accel_set_power_state(data, state);
+ if (ret < 0)
+ return ret;
+
+ /* map the interrupt to the appropriate pins */
+ ret = regmap_update_bits(data->regmap, info->map_reg, info->map_bitmask,
+ (state ? info->map_bitmask : 0));
+ if (ret < 0) {
+ dev_err(dev, "Error updating reg_int_map\n");
+ goto out_fix_power_state;
+ }
+
+ /* enable/disable the interrupt */
+ ret = regmap_update_bits(data->regmap, info->en_reg, info->en_bitmask,
+ (state ? info->en_bitmask : 0));
+ if (ret < 0) {
+ dev_err(dev, "Error updating reg_int_en\n");
+ goto out_fix_power_state;
+ }
+
+ return 0;
+
+out_fix_power_state:
+ bmc150_accel_set_power_state(data, false);
+ return ret;
+}
+
+static int bmc150_accel_set_scale(struct bmc150_accel_data *data, int val)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(data->chip_info->scale_table); ++i) {
+ if (data->chip_info->scale_table[i].scale == val) {
+ ret = regmap_write(data->regmap,
+ BMC150_ACCEL_REG_PMU_RANGE,
+ data->chip_info->scale_table[i].reg_range);
+ if (ret < 0) {
+ dev_err(dev, "Error writing pmu_range\n");
+ return ret;
+ }
+
+ data->range = data->chip_info->scale_table[i].reg_range;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int bmc150_accel_get_temp(struct bmc150_accel_data *data, int *val)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+ unsigned int value;
+
+ mutex_lock(&data->mutex);
+
+ ret = regmap_read(data->regmap, BMC150_ACCEL_REG_TEMP, &value);
+ if (ret < 0) {
+ dev_err(dev, "Error reading reg_temp\n");
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ *val = sign_extend32(value, 7);
+
+ mutex_unlock(&data->mutex);
+
+ return IIO_VAL_INT;
+}
+
+static int bmc150_accel_get_axis(struct bmc150_accel_data *data,
+ struct iio_chan_spec const *chan,
+ int *val)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+ int axis = chan->scan_index;
+ __le16 raw_val;
+
+ mutex_lock(&data->mutex);
+ ret = bmc150_accel_set_power_state(data, true);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+
+ ret = regmap_bulk_read(data->regmap, BMC150_ACCEL_AXIS_TO_REG(axis),
+ &raw_val, sizeof(raw_val));
+ if (ret < 0) {
+ dev_err(dev, "Error reading axis %d\n", axis);
+ bmc150_accel_set_power_state(data, false);
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ *val = sign_extend32(le16_to_cpu(raw_val) >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+ ret = bmc150_accel_set_power_state(data, false);
+ mutex_unlock(&data->mutex);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+}
+
+static int bmc150_accel_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_TEMP:
+ return bmc150_accel_get_temp(data, val);
+ case IIO_ACCEL:
+ if (iio_buffer_enabled(indio_dev))
+ return -EBUSY;
+ else
+ return bmc150_accel_get_axis(data, chan, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ if (chan->type == IIO_TEMP) {
+ *val = BMC150_ACCEL_TEMP_CENTER_VAL;
+ return IIO_VAL_INT;
+ } else {
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ switch (chan->type) {
+ case IIO_TEMP:
+ *val2 = 500000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ {
+ int i;
+ const struct bmc150_scale_info *si;
+ int st_size = ARRAY_SIZE(data->chip_info->scale_table);
+
+ for (i = 0; i < st_size; ++i) {
+ si = &data->chip_info->scale_table[i];
+ if (si->reg_range == data->range) {
+ *val2 = si->scale;
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+ }
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ mutex_lock(&data->mutex);
+ ret = bmc150_accel_get_bw(data, val, val2);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bmc150_accel_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ mutex_lock(&data->mutex);
+ ret = bmc150_accel_set_bw(data, val, val2);
+ mutex_unlock(&data->mutex);
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ if (val)
+ return -EINVAL;
+
+ mutex_lock(&data->mutex);
+ ret = bmc150_accel_set_scale(data, val2);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int bmc150_accel_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 bmc150_accel_data *data = iio_priv(indio_dev);
+
+ *val2 = 0;
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ *val = data->slope_thres;
+ break;
+ case IIO_EV_INFO_PERIOD:
+ *val = data->slope_dur;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return IIO_VAL_INT;
+}
+
+static int bmc150_accel_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 bmc150_accel_data *data = iio_priv(indio_dev);
+
+ if (data->ev_enable_state)
+ return -EBUSY;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ data->slope_thres = val & BMC150_ACCEL_SLOPE_THRES_MASK;
+ break;
+ case IIO_EV_INFO_PERIOD:
+ data->slope_dur = val & BMC150_ACCEL_SLOPE_DUR_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bmc150_accel_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 bmc150_accel_data *data = iio_priv(indio_dev);
+
+ return data->ev_enable_state;
+}
+
+static int bmc150_accel_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (state == data->ev_enable_state)
+ return 0;
+
+ mutex_lock(&data->mutex);
+
+ ret = bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_ANY_MOTION,
+ state);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+
+ data->ev_enable_state = state;
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static int bmc150_accel_validate_trigger(struct iio_dev *indio_dev,
+ struct iio_trigger *trig)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ int i;
+
+ for (i = 0; i < BMC150_ACCEL_TRIGGERS; i++) {
+ if (data->triggers[i].indio_trig == trig)
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static ssize_t bmc150_accel_get_fifo_watermark(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ int wm;
+
+ mutex_lock(&data->mutex);
+ wm = data->watermark;
+ mutex_unlock(&data->mutex);
+
+ return sysfs_emit(buf, "%d\n", wm);
+}
+
+static ssize_t bmc150_accel_get_fifo_state(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ bool state;
+
+ mutex_lock(&data->mutex);
+ state = data->fifo_mode;
+ mutex_unlock(&data->mutex);
+
+ return sysfs_emit(buf, "%d\n", state);
+}
+
+static const struct iio_mount_matrix *
+bmc150_accel_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info bmc150_accel_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bmc150_accel_get_mount_matrix),
+ { }
+};
+
+IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1");
+IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max,
+ __stringify(BMC150_ACCEL_FIFO_LENGTH));
+static IIO_DEVICE_ATTR(hwfifo_enabled, S_IRUGO,
+ bmc150_accel_get_fifo_state, NULL, 0);
+static IIO_DEVICE_ATTR(hwfifo_watermark, S_IRUGO,
+ bmc150_accel_get_fifo_watermark, NULL, 0);
+
+static const struct iio_dev_attr *bmc150_accel_fifo_attributes[] = {
+ &iio_dev_attr_hwfifo_watermark_min,
+ &iio_dev_attr_hwfifo_watermark_max,
+ &iio_dev_attr_hwfifo_watermark,
+ &iio_dev_attr_hwfifo_enabled,
+ NULL,
+};
+
+static int bmc150_accel_set_watermark(struct iio_dev *indio_dev, unsigned val)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+
+ if (val > BMC150_ACCEL_FIFO_LENGTH)
+ val = BMC150_ACCEL_FIFO_LENGTH;
+
+ mutex_lock(&data->mutex);
+ data->watermark = val;
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+/*
+ * We must read at least one full frame in one burst, otherwise the rest of the
+ * frame data is discarded.
+ */
+static int bmc150_accel_fifo_transfer(struct bmc150_accel_data *data,
+ char *buffer, int samples)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int sample_length = 3 * 2;
+ int ret;
+ int total_length = samples * sample_length;
+
+ ret = regmap_raw_read(data->regmap, BMC150_ACCEL_REG_FIFO_DATA,
+ buffer, total_length);
+ if (ret)
+ dev_err(dev,
+ "Error transferring data from fifo: %d\n", ret);
+
+ return ret;
+}
+
+static int __bmc150_accel_fifo_flush(struct iio_dev *indio_dev,
+ unsigned samples, bool irq)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret, i;
+ u8 count;
+ u16 buffer[BMC150_ACCEL_FIFO_LENGTH * 3];
+ int64_t tstamp;
+ uint64_t sample_period;
+ unsigned int val;
+
+ ret = regmap_read(data->regmap, BMC150_ACCEL_REG_FIFO_STATUS, &val);
+ if (ret < 0) {
+ dev_err(dev, "Error reading reg_fifo_status\n");
+ return ret;
+ }
+
+ count = val & 0x7F;
+
+ if (!count)
+ return 0;
+
+ /*
+ * If we getting called from IRQ handler we know the stored timestamp is
+ * fairly accurate for the last stored sample. Otherwise, if we are
+ * called as a result of a read operation from userspace and hence
+ * before the watermark interrupt was triggered, take a timestamp
+ * now. We can fall anywhere in between two samples so the error in this
+ * case is at most one sample period.
+ */
+ if (!irq) {
+ data->old_timestamp = data->timestamp;
+ data->timestamp = iio_get_time_ns(indio_dev);
+ }
+
+ /*
+ * Approximate timestamps for each of the sample based on the sampling
+ * frequency, timestamp for last sample and number of samples.
+ *
+ * Note that we can't use the current bandwidth settings to compute the
+ * sample period because the sample rate varies with the device
+ * (e.g. between 31.70ms to 32.20ms for a bandwidth of 15.63HZ). That
+ * small variation adds when we store a large number of samples and
+ * creates significant jitter between the last and first samples in
+ * different batches (e.g. 32ms vs 21ms).
+ *
+ * To avoid this issue we compute the actual sample period ourselves
+ * based on the timestamp delta between the last two flush operations.
+ */
+ sample_period = (data->timestamp - data->old_timestamp);
+ do_div(sample_period, count);
+ tstamp = data->timestamp - (count - 1) * sample_period;
+
+ if (samples)
+ count = min3(count, samples, BMC150_ACCEL_FIFO_LENGTH);
+ else
+ count = min(count, BMC150_ACCEL_FIFO_LENGTH);
+
+ count = min_t(u8, count, BMC150_ACCEL_FIFO_LENGTH);
+
+ ret = bmc150_accel_fifo_transfer(data, (u8 *)buffer, count);
+ if (ret)
+ return ret;
+
+ /*
+ * Ideally we want the IIO core to handle the demux when running in fifo
+ * mode but not when running in triggered buffer mode. Unfortunately
+ * this does not seem to be possible, so stick with driver demux for
+ * now.
+ */
+ for (i = 0; i < count; i++) {
+ int j, bit;
+
+ j = 0;
+ iio_for_each_active_channel(indio_dev, bit)
+ memcpy(&data->scan.channels[j++], &buffer[i * 3 + bit],
+ sizeof(data->scan.channels[0]));
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ tstamp);
+
+ tstamp += sample_period;
+ }
+
+ return count;
+}
+
+static int bmc150_accel_fifo_flush(struct iio_dev *indio_dev, unsigned samples)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = __bmc150_accel_fifo_flush(indio_dev, samples, false);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(
+ "15.620000 31.260000 62.50000 125 250 500 1000 2000");
+
+static struct attribute *bmc150_accel_attributes[] = {
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group bmc150_accel_attrs_group = {
+ .attrs = bmc150_accel_attributes,
+};
+
+static const struct iio_event_spec bmc150_accel_event = {
+ .type = IIO_EV_TYPE_ROC,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_PERIOD)
+};
+
+#define BMC150_ACCEL_CHANNEL(_axis, bits) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = AXIS_##_axis, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = (bits), \
+ .storagebits = 16, \
+ .shift = 16 - (bits), \
+ .endianness = IIO_LE, \
+ }, \
+ .ext_info = bmc150_accel_ext_info, \
+ .event_spec = &bmc150_accel_event, \
+ .num_event_specs = 1 \
+}
+
+#define BMC150_ACCEL_CHANNELS(bits) { \
+ { \
+ .type = IIO_TEMP, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET), \
+ .scan_index = -1, \
+ }, \
+ BMC150_ACCEL_CHANNEL(X, bits), \
+ BMC150_ACCEL_CHANNEL(Y, bits), \
+ BMC150_ACCEL_CHANNEL(Z, bits), \
+ IIO_CHAN_SOFT_TIMESTAMP(3), \
+}
+
+static const struct iio_chan_spec bma222e_accel_channels[] =
+ BMC150_ACCEL_CHANNELS(8);
+static const struct iio_chan_spec bma250e_accel_channels[] =
+ BMC150_ACCEL_CHANNELS(10);
+static const struct iio_chan_spec bmc150_accel_channels[] =
+ BMC150_ACCEL_CHANNELS(12);
+static const struct iio_chan_spec bma280_accel_channels[] =
+ BMC150_ACCEL_CHANNELS(14);
+
+/*
+ * The range for the Bosch sensors is typically +-2g/4g/8g/16g, distributed
+ * over the amount of bits (see above). The scale table can be calculated using
+ * (range / 2^bits) * g = (range / 2^bits) * 9.80665 m/s^2
+ * e.g. for +-2g and 12 bits: (4 / 2^12) * 9.80665 m/s^2 = 0.0095768... m/s^2
+ * Multiply 10^6 and round to get the values listed below.
+ */
+static const struct bmc150_accel_chip_info bmc150_accel_chip_info_tbl[] = {
+ {
+ .name = "BMA222",
+ .chip_id = 0x03,
+ .channels = bma222e_accel_channels,
+ .num_channels = ARRAY_SIZE(bma222e_accel_channels),
+ .scale_table = { {153229, BMC150_ACCEL_DEF_RANGE_2G},
+ {306458, BMC150_ACCEL_DEF_RANGE_4G},
+ {612916, BMC150_ACCEL_DEF_RANGE_8G},
+ {1225831, BMC150_ACCEL_DEF_RANGE_16G} },
+ },
+ {
+ .name = "BMA222E",
+ .chip_id = 0xF8,
+ .channels = bma222e_accel_channels,
+ .num_channels = ARRAY_SIZE(bma222e_accel_channels),
+ .scale_table = { {153229, BMC150_ACCEL_DEF_RANGE_2G},
+ {306458, BMC150_ACCEL_DEF_RANGE_4G},
+ {612916, BMC150_ACCEL_DEF_RANGE_8G},
+ {1225831, BMC150_ACCEL_DEF_RANGE_16G} },
+ },
+ {
+ .name = "BMA250E",
+ .chip_id = 0xF9,
+ .channels = bma250e_accel_channels,
+ .num_channels = ARRAY_SIZE(bma250e_accel_channels),
+ .scale_table = { {38307, BMC150_ACCEL_DEF_RANGE_2G},
+ {76614, BMC150_ACCEL_DEF_RANGE_4G},
+ {153229, BMC150_ACCEL_DEF_RANGE_8G},
+ {306458, BMC150_ACCEL_DEF_RANGE_16G} },
+ },
+ {
+ .name = "BMA253/BMA254/BMA255/BMC150/BMC156/BMI055",
+ .chip_id = 0xFA,
+ .channels = bmc150_accel_channels,
+ .num_channels = ARRAY_SIZE(bmc150_accel_channels),
+ .scale_table = { {9577, BMC150_ACCEL_DEF_RANGE_2G},
+ {19154, BMC150_ACCEL_DEF_RANGE_4G},
+ {38307, BMC150_ACCEL_DEF_RANGE_8G},
+ {76614, BMC150_ACCEL_DEF_RANGE_16G} },
+ },
+ {
+ .name = "BMA280",
+ .chip_id = 0xFB,
+ .channels = bma280_accel_channels,
+ .num_channels = ARRAY_SIZE(bma280_accel_channels),
+ .scale_table = { {2394, BMC150_ACCEL_DEF_RANGE_2G},
+ {4788, BMC150_ACCEL_DEF_RANGE_4G},
+ {9577, BMC150_ACCEL_DEF_RANGE_8G},
+ {19154, BMC150_ACCEL_DEF_RANGE_16G} },
+ },
+};
+
+static const struct iio_info bmc150_accel_info = {
+ .attrs = &bmc150_accel_attrs_group,
+ .read_raw = bmc150_accel_read_raw,
+ .write_raw = bmc150_accel_write_raw,
+ .read_event_value = bmc150_accel_read_event,
+ .write_event_value = bmc150_accel_write_event,
+ .write_event_config = bmc150_accel_write_event_config,
+ .read_event_config = bmc150_accel_read_event_config,
+};
+
+static const struct iio_info bmc150_accel_info_fifo = {
+ .attrs = &bmc150_accel_attrs_group,
+ .read_raw = bmc150_accel_read_raw,
+ .write_raw = bmc150_accel_write_raw,
+ .read_event_value = bmc150_accel_read_event,
+ .write_event_value = bmc150_accel_write_event,
+ .write_event_config = bmc150_accel_write_event_config,
+ .read_event_config = bmc150_accel_read_event_config,
+ .validate_trigger = bmc150_accel_validate_trigger,
+ .hwfifo_set_watermark = bmc150_accel_set_watermark,
+ .hwfifo_flush_to_buffer = bmc150_accel_fifo_flush,
+};
+
+static const unsigned long bmc150_accel_scan_masks[] = {
+ BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z),
+ 0};
+
+static irqreturn_t bmc150_accel_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = regmap_bulk_read(data->regmap, BMC150_ACCEL_REG_XOUT_L,
+ data->buffer, AXIS_MAX * 2);
+ mutex_unlock(&data->mutex);
+ if (ret < 0)
+ goto err_read;
+
+ iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
+ pf->timestamp);
+err_read:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static void bmc150_accel_trig_reen(struct iio_trigger *trig)
+{
+ struct bmc150_accel_trigger *t = iio_trigger_get_drvdata(trig);
+ struct bmc150_accel_data *data = t->data;
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ /* new data interrupts don't need ack */
+ if (t == &t->data->triggers[BMC150_ACCEL_TRIGGER_DATA_READY])
+ return;
+
+ mutex_lock(&data->mutex);
+ /* clear any latched interrupt */
+ ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_RST_LATCH,
+ BMC150_ACCEL_INT_MODE_LATCH_INT |
+ BMC150_ACCEL_INT_MODE_LATCH_RESET);
+ mutex_unlock(&data->mutex);
+ if (ret < 0)
+ dev_err(dev, "Error writing reg_int_rst_latch\n");
+}
+
+static int bmc150_accel_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct bmc150_accel_trigger *t = iio_trigger_get_drvdata(trig);
+ struct bmc150_accel_data *data = t->data;
+ int ret;
+
+ mutex_lock(&data->mutex);
+
+ if (t->enabled == state) {
+ mutex_unlock(&data->mutex);
+ return 0;
+ }
+
+ if (t->setup) {
+ ret = t->setup(t, state);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ }
+
+ ret = bmc150_accel_set_interrupt(data, t->intr, state);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+
+ t->enabled = state;
+
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static const struct iio_trigger_ops bmc150_accel_trigger_ops = {
+ .set_trigger_state = bmc150_accel_trigger_set_state,
+ .reenable = bmc150_accel_trig_reen,
+};
+
+static int bmc150_accel_handle_roc_event(struct iio_dev *indio_dev)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+ int dir;
+ int ret;
+ unsigned int val;
+
+ ret = regmap_read(data->regmap, BMC150_ACCEL_REG_INT_STATUS_2, &val);
+ if (ret < 0) {
+ dev_err(dev, "Error reading reg_int_status_2\n");
+ return ret;
+ }
+
+ if (val & BMC150_ACCEL_ANY_MOTION_BIT_SIGN)
+ dir = IIO_EV_DIR_FALLING;
+ else
+ dir = IIO_EV_DIR_RISING;
+
+ if (val & BMC150_ACCEL_ANY_MOTION_BIT_X)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_X,
+ IIO_EV_TYPE_ROC,
+ dir),
+ data->timestamp);
+
+ if (val & BMC150_ACCEL_ANY_MOTION_BIT_Y)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_Y,
+ IIO_EV_TYPE_ROC,
+ dir),
+ data->timestamp);
+
+ if (val & BMC150_ACCEL_ANY_MOTION_BIT_Z)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_Z,
+ IIO_EV_TYPE_ROC,
+ dir),
+ data->timestamp);
+
+ return ret;
+}
+
+static irqreturn_t bmc150_accel_irq_thread_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+ bool ack = false;
+ int ret;
+
+ mutex_lock(&data->mutex);
+
+ if (data->fifo_mode) {
+ ret = __bmc150_accel_fifo_flush(indio_dev,
+ BMC150_ACCEL_FIFO_LENGTH, true);
+ if (ret > 0)
+ ack = true;
+ }
+
+ if (data->ev_enable_state) {
+ ret = bmc150_accel_handle_roc_event(indio_dev);
+ if (ret > 0)
+ ack = true;
+ }
+
+ if (ack) {
+ ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_RST_LATCH,
+ BMC150_ACCEL_INT_MODE_LATCH_INT |
+ BMC150_ACCEL_INT_MODE_LATCH_RESET);
+ if (ret)
+ dev_err(dev, "Error writing reg_int_rst_latch\n");
+
+ ret = IRQ_HANDLED;
+ } else {
+ ret = IRQ_NONE;
+ }
+
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static irqreturn_t bmc150_accel_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ bool ack = false;
+ int i;
+
+ data->old_timestamp = data->timestamp;
+ data->timestamp = iio_get_time_ns(indio_dev);
+
+ for (i = 0; i < BMC150_ACCEL_TRIGGERS; i++) {
+ if (data->triggers[i].enabled) {
+ iio_trigger_poll(data->triggers[i].indio_trig);
+ ack = true;
+ break;
+ }
+ }
+
+ if (data->ev_enable_state || data->fifo_mode)
+ return IRQ_WAKE_THREAD;
+
+ if (ack)
+ return IRQ_HANDLED;
+
+ return IRQ_NONE;
+}
+
+static const struct {
+ int intr;
+ const char *name;
+ int (*setup)(struct bmc150_accel_trigger *t, bool state);
+} bmc150_accel_triggers[BMC150_ACCEL_TRIGGERS] = {
+ {
+ .intr = 0,
+ .name = "%s-dev%d",
+ },
+ {
+ .intr = 1,
+ .name = "%s-any-motion-dev%d",
+ .setup = bmc150_accel_any_motion_setup,
+ },
+};
+
+static void bmc150_accel_unregister_triggers(struct bmc150_accel_data *data,
+ int from)
+{
+ int i;
+
+ for (i = from; i >= 0; i--) {
+ if (data->triggers[i].indio_trig) {
+ iio_trigger_unregister(data->triggers[i].indio_trig);
+ data->triggers[i].indio_trig = NULL;
+ }
+ }
+}
+
+static int bmc150_accel_triggers_setup(struct iio_dev *indio_dev,
+ struct bmc150_accel_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int i, ret;
+
+ for (i = 0; i < BMC150_ACCEL_TRIGGERS; i++) {
+ struct bmc150_accel_trigger *t = &data->triggers[i];
+
+ t->indio_trig = devm_iio_trigger_alloc(dev,
+ bmc150_accel_triggers[i].name,
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!t->indio_trig) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ t->indio_trig->ops = &bmc150_accel_trigger_ops;
+ t->intr = bmc150_accel_triggers[i].intr;
+ t->data = data;
+ t->setup = bmc150_accel_triggers[i].setup;
+ iio_trigger_set_drvdata(t->indio_trig, t);
+
+ ret = iio_trigger_register(t->indio_trig);
+ if (ret)
+ break;
+ }
+
+ if (ret)
+ bmc150_accel_unregister_triggers(data, i - 1);
+
+ return ret;
+}
+
+#define BMC150_ACCEL_FIFO_MODE_STREAM 0x80
+#define BMC150_ACCEL_FIFO_MODE_FIFO 0x40
+#define BMC150_ACCEL_FIFO_MODE_BYPASS 0x00
+
+static int bmc150_accel_fifo_set_mode(struct bmc150_accel_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ u8 reg = BMC150_ACCEL_REG_FIFO_CONFIG1;
+ int ret;
+
+ ret = regmap_write(data->regmap, reg, data->fifo_mode);
+ if (ret < 0) {
+ dev_err(dev, "Error writing reg_fifo_config1\n");
+ return ret;
+ }
+
+ if (!data->fifo_mode)
+ return 0;
+
+ ret = regmap_write(data->regmap, BMC150_ACCEL_REG_FIFO_CONFIG0,
+ data->watermark);
+ if (ret < 0)
+ dev_err(dev, "Error writing reg_fifo_config0\n");
+
+ return ret;
+}
+
+static int bmc150_accel_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+
+ return bmc150_accel_set_power_state(data, true);
+}
+
+static int bmc150_accel_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ int ret = 0;
+
+ if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED)
+ return 0;
+
+ mutex_lock(&data->mutex);
+
+ if (!data->watermark)
+ goto out;
+
+ ret = bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_WATERMARK,
+ true);
+ if (ret)
+ goto out;
+
+ data->fifo_mode = BMC150_ACCEL_FIFO_MODE_FIFO;
+
+ ret = bmc150_accel_fifo_set_mode(data);
+ if (ret) {
+ data->fifo_mode = 0;
+ bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_WATERMARK,
+ false);
+ }
+
+out:
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int bmc150_accel_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+
+ if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED)
+ return 0;
+
+ mutex_lock(&data->mutex);
+
+ if (!data->fifo_mode)
+ goto out;
+
+ bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_WATERMARK, false);
+ __bmc150_accel_fifo_flush(indio_dev, BMC150_ACCEL_FIFO_LENGTH, false);
+ data->fifo_mode = 0;
+ bmc150_accel_fifo_set_mode(data);
+
+out:
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static int bmc150_accel_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+
+ return bmc150_accel_set_power_state(data, false);
+}
+
+static const struct iio_buffer_setup_ops bmc150_accel_buffer_ops = {
+ .preenable = bmc150_accel_buffer_preenable,
+ .postenable = bmc150_accel_buffer_postenable,
+ .predisable = bmc150_accel_buffer_predisable,
+ .postdisable = bmc150_accel_buffer_postdisable,
+};
+
+static int bmc150_accel_chip_init(struct bmc150_accel_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret, i;
+ unsigned int val;
+
+ /*
+ * Reset chip to get it in a known good state. A delay of 1.8ms after
+ * reset is required according to the data sheets of supported chips.
+ */
+ regmap_write(data->regmap, BMC150_ACCEL_REG_RESET,
+ BMC150_ACCEL_RESET_VAL);
+ usleep_range(1800, 2500);
+
+ ret = regmap_read(data->regmap, BMC150_ACCEL_REG_CHIP_ID, &val);
+ if (ret < 0) {
+ dev_err(dev, "Error: Reading chip id\n");
+ return ret;
+ }
+
+ dev_dbg(dev, "Chip Id %x\n", val);
+ for (i = 0; i < ARRAY_SIZE(bmc150_accel_chip_info_tbl); i++) {
+ if (bmc150_accel_chip_info_tbl[i].chip_id == val) {
+ data->chip_info = &bmc150_accel_chip_info_tbl[i];
+ break;
+ }
+ }
+
+ if (!data->chip_info) {
+ dev_err(dev, "Invalid chip %x\n", val);
+ return -ENODEV;
+ }
+
+ ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Set Bandwidth */
+ ret = bmc150_accel_set_bw(data, BMC150_ACCEL_DEF_BW, 0);
+ if (ret < 0)
+ return ret;
+
+ /* Set Default Range */
+ ret = regmap_write(data->regmap, BMC150_ACCEL_REG_PMU_RANGE,
+ BMC150_ACCEL_DEF_RANGE_4G);
+ if (ret < 0) {
+ dev_err(dev, "Error writing reg_pmu_range\n");
+ return ret;
+ }
+
+ data->range = BMC150_ACCEL_DEF_RANGE_4G;
+
+ /* Set default slope duration and thresholds */
+ data->slope_thres = BMC150_ACCEL_DEF_SLOPE_THRESHOLD;
+ data->slope_dur = BMC150_ACCEL_DEF_SLOPE_DURATION;
+ ret = bmc150_accel_update_slope(data);
+ if (ret < 0)
+ return ret;
+
+ /* Set default as latched interrupts */
+ ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_RST_LATCH,
+ BMC150_ACCEL_INT_MODE_LATCH_INT |
+ BMC150_ACCEL_INT_MODE_LATCH_RESET);
+ if (ret < 0) {
+ dev_err(dev, "Error writing reg_int_rst_latch\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq,
+ enum bmc150_type type, const char *name,
+ bool block_supported)
+{
+ const struct iio_dev_attr **fifo_attrs;
+ struct bmc150_accel_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);
+ dev_set_drvdata(dev, indio_dev);
+
+ data->regmap = regmap;
+ data->type = type;
+
+ if (!bmc150_apply_acpi_orientation(dev, &data->orientation)) {
+ ret = iio_read_mount_matrix(dev, &data->orientation);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * VDD is the analog and digital domain voltage supply
+ * VDDIO is the digital I/O voltage supply
+ */
+ data->regulators[0].supply = "vdd";
+ data->regulators[1].supply = "vddio";
+ ret = devm_regulator_bulk_get(dev,
+ ARRAY_SIZE(data->regulators),
+ data->regulators);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get regulators\n");
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(data->regulators),
+ data->regulators);
+ if (ret) {
+ dev_err(dev, "failed to enable regulators: %d\n", ret);
+ return ret;
+ }
+ /*
+ * 2ms or 3ms power-on time according to datasheets, let's better
+ * be safe than sorry and set this delay to 5ms.
+ */
+ msleep(5);
+
+ ret = bmc150_accel_chip_init(data);
+ if (ret < 0)
+ goto err_disable_regulators;
+
+ mutex_init(&data->mutex);
+
+ indio_dev->channels = data->chip_info->channels;
+ indio_dev->num_channels = data->chip_info->num_channels;
+ indio_dev->name = name ? name : data->chip_info->name;
+ indio_dev->available_scan_masks = bmc150_accel_scan_masks;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &bmc150_accel_info;
+
+ if (block_supported) {
+ indio_dev->modes |= INDIO_BUFFER_SOFTWARE;
+ indio_dev->info = &bmc150_accel_info_fifo;
+ fifo_attrs = bmc150_accel_fifo_attributes;
+ } else {
+ fifo_attrs = NULL;
+ }
+
+ 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) {
+ dev_err(dev, "Failed: iio triggered buffer setup\n");
+ goto err_disable_regulators;
+ }
+
+ if (irq > 0) {
+ data->irq = irq;
+ ret = devm_request_threaded_irq(dev, irq,
+ bmc150_accel_irq_handler,
+ bmc150_accel_irq_thread_handler,
+ IRQF_TRIGGER_RISING,
+ "bmc150_accel_event",
+ indio_dev);
+ if (ret)
+ goto err_buffer_cleanup;
+
+ /*
+ * Set latched mode interrupt. While certain interrupts are
+ * non-latched regardless of this settings (e.g. new data) we
+ * want to use latch mode when we can to prevent interrupt
+ * flooding.
+ */
+ ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_RST_LATCH,
+ BMC150_ACCEL_INT_MODE_LATCH_RESET);
+ if (ret < 0) {
+ dev_err(dev, "Error writing reg_int_rst_latch\n");
+ goto err_buffer_cleanup;
+ }
+
+ bmc150_accel_interrupts_setup(indio_dev, data, irq);
+
+ ret = bmc150_accel_triggers_setup(indio_dev, data);
+ if (ret)
+ goto err_buffer_cleanup;
+ }
+
+ ret = pm_runtime_set_active(dev);
+ if (ret)
+ goto err_trigger_unregister;
+
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, BMC150_AUTO_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(dev, "Unable to register iio device\n");
+ goto err_pm_cleanup;
+ }
+
+ return 0;
+
+err_pm_cleanup:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_disable(dev);
+err_trigger_unregister:
+ bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1);
+err_buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+err_disable_regulators:
+ regulator_bulk_disable(ARRAY_SIZE(data->regulators),
+ data->regulators);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(bmc150_accel_core_probe, "IIO_BMC150");
+
+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);
+
+ iio_device_unregister(indio_dev);
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+
+ bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1);
+
+ iio_triggered_buffer_cleanup(indio_dev);
+
+ mutex_lock(&data->mutex);
+ bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_DEEP_SUSPEND, 0);
+ mutex_unlock(&data->mutex);
+
+ regulator_bulk_disable(ARRAY_SIZE(data->regulators),
+ data->regulators);
+}
+EXPORT_SYMBOL_NS_GPL(bmc150_accel_core_remove, "IIO_BMC150");
+
+#ifdef CONFIG_PM_SLEEP
+static int bmc150_accel_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+
+ mutex_lock(&data->mutex);
+ bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0);
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static int bmc150_accel_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+
+ mutex_lock(&data->mutex);
+ bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0);
+ bmc150_accel_fifo_set_mode(data);
+ mutex_unlock(&data->mutex);
+
+ if (data->resume_callback)
+ data->resume_callback(dev);
+
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int bmc150_accel_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0);
+ if (ret < 0)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int bmc150_accel_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmc150_accel_data *data = iio_priv(indio_dev);
+ int ret;
+ int sleep_val;
+
+ ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0);
+ if (ret < 0)
+ return ret;
+ ret = bmc150_accel_fifo_set_mode(data);
+ if (ret < 0)
+ return ret;
+
+ sleep_val = bmc150_accel_get_startup_times(data);
+ if (sleep_val < 20)
+ usleep_range(sleep_val * 1000, 20000);
+ else
+ msleep_interruptible(sleep_val);
+
+ return 0;
+}
+#endif
+
+const struct dev_pm_ops bmc150_accel_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(bmc150_accel_suspend, bmc150_accel_resume)
+ SET_RUNTIME_PM_OPS(bmc150_accel_runtime_suspend,
+ bmc150_accel_runtime_resume, NULL)
+};
+EXPORT_SYMBOL_NS_GPL(bmc150_accel_pm_ops, "IIO_BMC150");
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BMC150 accelerometer driver");
diff --git a/drivers/iio/accel/bmc150-accel-i2c.c b/drivers/iio/accel/bmc150-accel-i2c.c
new file mode 100644
index 000000000..336866aad
--- /dev/null
+++ b/drivers/iio/accel/bmc150-accel-i2c.c
@@ -0,0 +1,293 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 3-axis accelerometer driver supporting many I2C Bosch-Sensortec chips
+ * Copyright (c) 2014, Intel Corporation.
+ */
+
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/regmap.h>
+
+#include "bmc150-accel.h"
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id bmc150_acpi_dual_accel_ids[] = {
+ {"BOSC0200"},
+ {"DUAL250E"},
+ { }
+};
+
+/*
+ * The DUAL250E ACPI device for 360° hinges type 2-in-1s with 1 accelerometer
+ * in the display and 1 in the hinge has an ACPI-method (DSM) to tell the
+ * ACPI code about the angle between the 2 halves. This will make the ACPI
+ * code enable/disable the keyboard and touchpad. We need to call this to avoid
+ * the keyboard being disabled when the 2-in-1 is turned-on or resumed while
+ * fully folded into tablet mode (which gets detected with a HALL-sensor).
+ * If we don't call this then the keyboard won't work even when the 2-in-1 is
+ * changed to be used in laptop mode after the power-on / resume.
+ *
+ * This DSM takes 2 angles, selected by setting aux0 to 0 or 1, these presumably
+ * define the angle between the gravity vector measured by the accelerometer in
+ * the display (aux0=0) resp. the base (aux0=1) and some reference vector.
+ * The 2 angles get subtracted from each other so the reference vector does
+ * not matter and we can simply leave the second angle at 0.
+ */
+
+#define BMC150_DSM_GUID "7681541e-8827-4239-8d9d-36be7fe12542"
+#define DUAL250E_SET_ANGLE_FN_INDEX 3
+
+struct dual250e_set_angle_args {
+ u32 aux0;
+ u32 ang0;
+ u32 rawx;
+ u32 rawy;
+ u32 rawz;
+} __packed;
+
+static bool bmc150_acpi_set_angle_dsm(struct i2c_client *client, u32 aux0, u32 ang0)
+{
+ struct acpi_device *adev = ACPI_COMPANION(&client->dev);
+ struct dual250e_set_angle_args args = {
+ .aux0 = aux0,
+ .ang0 = ang0,
+ };
+ union acpi_object args_obj, *obj;
+ guid_t guid;
+
+ if (!acpi_dev_hid_uid_match(adev, "DUAL250E", NULL))
+ return false;
+
+ guid_parse(BMC150_DSM_GUID, &guid);
+
+ if (!acpi_check_dsm(adev->handle, &guid, 0, BIT(DUAL250E_SET_ANGLE_FN_INDEX)))
+ return false;
+
+ /*
+ * Note this triggers the following warning:
+ * "ACPI Warning: \_SB.PCI0.I2C2.ACC1._DSM: Argument #4 type mismatch -
+ * Found [Buffer], ACPI requires [Package]"
+ * This is unavoidable since the _DSM implementation expects a "naked"
+ * buffer, so wrapping it in a package will _not_ work.
+ */
+ args_obj.type = ACPI_TYPE_BUFFER;
+ args_obj.buffer.length = sizeof(args);
+ args_obj.buffer.pointer = (u8 *)&args;
+
+ obj = acpi_evaluate_dsm(adev->handle, &guid, 0, DUAL250E_SET_ANGLE_FN_INDEX, &args_obj);
+ if (!obj) {
+ dev_err(&client->dev, "Failed to call DSM to enable keyboard and touchpad\n");
+ return false;
+ }
+
+ ACPI_FREE(obj);
+ return true;
+}
+
+static bool bmc150_acpi_enable_keyboard(struct i2c_client *client)
+{
+ /*
+ * The EC must see a change for it to re-enable the kbd, so first
+ * set the angle to 270° (tent/stand mode) and then change it to
+ * 90° (laptop mode).
+ */
+ if (!bmc150_acpi_set_angle_dsm(client, 0, 270))
+ return false;
+
+ /* The EC needs some time to notice the angle being changed */
+ msleep(100);
+
+ return bmc150_acpi_set_angle_dsm(client, 0, 90);
+}
+
+static void bmc150_acpi_resume_work(struct work_struct *work)
+{
+ struct bmc150_accel_data *data =
+ container_of(work, struct bmc150_accel_data, resume_work.work);
+
+ bmc150_acpi_enable_keyboard(data->second_device);
+}
+
+static void bmc150_acpi_resume_handler(struct device *dev)
+{
+ struct bmc150_accel_data *data = iio_priv(dev_get_drvdata(dev));
+
+ /*
+ * Delay the bmc150_acpi_enable_keyboard() call till after the system
+ * resume has completed, otherwise it will not work.
+ */
+ schedule_delayed_work(&data->resume_work, msecs_to_jiffies(1000));
+}
+
+/*
+ * Some acpi_devices describe 2 accelerometers in a single ACPI device,
+ * try instantiating a second i2c_client for an I2cSerialBusV2 ACPI resource
+ * with index 1.
+ */
+static void bmc150_acpi_dual_accel_probe(struct i2c_client *client)
+{
+ struct bmc150_accel_data *data = iio_priv(i2c_get_clientdata(client));
+ struct acpi_device *adev = ACPI_COMPANION(&client->dev);
+ char dev_name[16];
+ struct i2c_board_info board_info = {
+ .type = "bmc150_accel",
+ .dev_name = dev_name,
+ .fwnode = client->dev.fwnode,
+ };
+
+ if (acpi_match_device_ids(adev, bmc150_acpi_dual_accel_ids))
+ return;
+
+ /*
+ * The 2nd accel sits in the base of 2-in-1s. The suffix is static, as
+ * there should never be more then 1 ACPI node with 2 accelerometers.
+ */
+ snprintf(dev_name, sizeof(dev_name), "%s:base", acpi_device_hid(adev));
+
+ board_info.irq = acpi_dev_gpio_irq_get(adev, 1);
+
+ data->second_device = i2c_acpi_new_device(&client->dev, 1, &board_info);
+
+ if (!IS_ERR(data->second_device) && bmc150_acpi_enable_keyboard(data->second_device)) {
+ INIT_DELAYED_WORK(&data->resume_work, bmc150_acpi_resume_work);
+ data->resume_callback = bmc150_acpi_resume_handler;
+ }
+}
+
+static void bmc150_acpi_dual_accel_remove(struct i2c_client *client)
+{
+ struct bmc150_accel_data *data = iio_priv(i2c_get_clientdata(client));
+
+ if (data->resume_callback)
+ cancel_delayed_work_sync(&data->resume_work);
+
+ i2c_unregister_device(data->second_device);
+}
+#else
+static void bmc150_acpi_dual_accel_probe(struct i2c_client *client) {}
+static void bmc150_acpi_dual_accel_remove(struct i2c_client *client) {}
+#endif
+
+static int bmc150_accel_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct regmap *regmap;
+ const char *name = NULL;
+ enum bmc150_type type = BOSCH_UNKNOWN;
+ bool block_supported =
+ i2c_check_functionality(client->adapter, I2C_FUNC_I2C) ||
+ i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK);
+ int ret;
+
+ regmap = devm_regmap_init_i2c(client, &bmc150_regmap_conf);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "Failed to initialize i2c regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ if (id) {
+ name = id->name;
+ type = id->driver_data;
+ }
+
+ ret = bmc150_accel_core_probe(&client->dev, regmap, client->irq,
+ type, name, block_supported);
+ if (ret)
+ return ret;
+
+ /*
+ * The !id check avoids recursion when probe() gets called
+ * for the second client.
+ */
+ if (!id && has_acpi_companion(&client->dev))
+ bmc150_acpi_dual_accel_probe(client);
+
+ return 0;
+}
+
+static void bmc150_accel_remove(struct i2c_client *client)
+{
+ bmc150_acpi_dual_accel_remove(client);
+
+ bmc150_accel_core_remove(&client->dev);
+}
+
+static const struct acpi_device_id bmc150_accel_acpi_match[] = {
+ {"BMA0255"},
+ {"BMA0280"},
+ {"BMA222"},
+ {"BMA222E"},
+ {"BMA250E"},
+ {"BMC150A"},
+ {"BMI055A"},
+ /*
+ * The "BOSC0200" identifier used here is not unique to devices using
+ * bmc150. The same "BOSC0200" identifier is found in the ACPI tables
+ * of the ASUS ROG ALLY and Ayaneo AIR Plus which both use a Bosch
+ * BMI323 chip. This creates a conflict with duplicate ACPI identifiers
+ * which multiple drivers want to use. Fortunately, when the bmc150
+ * driver starts to load on the ASUS ROG ALLY, the chip ID check
+ * portion fails (correctly) because the chip IDs received (via i2c)
+ * are unique between bmc150 and bmi323 and a dmesg output similar to
+ * this: "bmc150_accel_i2c i2c-BOSC0200:00: Invalid chip 0" can be
+ * seen. This allows the bmi323 driver to take over for ASUS ROG ALLY,
+ * and other devices using the bmi323 chip.
+ */
+ {"BOSC0200"},
+ {"BSBA0150"},
+ {"DUAL250E"},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, bmc150_accel_acpi_match);
+
+static const struct i2c_device_id bmc150_accel_id[] = {
+ { .name = "bma222", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma222e", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma250e", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma253", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma254", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma255", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma280", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bmc150_accel", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bmc156_accel", .driver_data = BOSCH_BMC156 },
+ { .name = "bmi055_accel", .driver_data = BOSCH_UNKNOWN },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, bmc150_accel_id);
+
+static const struct of_device_id bmc150_accel_of_match[] = {
+ { .compatible = "bosch,bma222" },
+ { .compatible = "bosch,bma222e" },
+ { .compatible = "bosch,bma250e" },
+ { .compatible = "bosch,bma253" },
+ { .compatible = "bosch,bma254" },
+ { .compatible = "bosch,bma255" },
+ { .compatible = "bosch,bma280" },
+ { .compatible = "bosch,bmc150_accel" },
+ { .compatible = "bosch,bmc156_accel" },
+ { .compatible = "bosch,bmi055_accel" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bmc150_accel_of_match);
+
+static struct i2c_driver bmc150_accel_driver = {
+ .driver = {
+ .name = "bmc150_accel_i2c",
+ .of_match_table = bmc150_accel_of_match,
+ .acpi_match_table = bmc150_accel_acpi_match,
+ .pm = &bmc150_accel_pm_ops,
+ },
+ .probe = bmc150_accel_probe,
+ .remove = bmc150_accel_remove,
+ .id_table = bmc150_accel_id,
+};
+module_i2c_driver(bmc150_accel_driver);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BMC150 I2C accelerometer driver");
+MODULE_IMPORT_NS("IIO_BMC150");
diff --git a/drivers/iio/accel/bmc150-accel-spi.c b/drivers/iio/accel/bmc150-accel-spi.c
new file mode 100644
index 000000000..405bd7b2d
--- /dev/null
+++ b/drivers/iio/accel/bmc150-accel-spi.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 3-axis accelerometer driver supporting SPI Bosch-Sensortec accelerometer chip
+ * Copyright © 2015 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "bmc150-accel.h"
+
+static int bmc150_accel_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+ const char *name = NULL;
+ enum bmc150_type type = BOSCH_UNKNOWN;
+ const struct spi_device_id *id = spi_get_device_id(spi);
+
+ regmap = devm_regmap_init_spi(spi, &bmc150_regmap_conf);
+ if (IS_ERR(regmap)) {
+ dev_err(&spi->dev, "Failed to initialize spi regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ if (id) {
+ name = id->name;
+ type = id->driver_data;
+ }
+
+ return bmc150_accel_core_probe(&spi->dev, regmap, spi->irq, type, name,
+ true);
+}
+
+static void bmc150_accel_remove(struct spi_device *spi)
+{
+ bmc150_accel_core_remove(&spi->dev);
+}
+
+static const struct acpi_device_id bmc150_accel_acpi_match[] = {
+ {"BMA0255"},
+ {"BMA0280"},
+ {"BMA222"},
+ {"BMA222E"},
+ {"BMA250E"},
+ {"BMC150A"},
+ {"BMI055A"},
+ {"BSBA0150"},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, bmc150_accel_acpi_match);
+
+static const struct spi_device_id bmc150_accel_id[] = {
+ { .name = "bma222", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma222e", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma250e", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma253", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma255", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bma280", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bmc150_accel", .driver_data = BOSCH_UNKNOWN },
+ { .name = "bmc156_accel", .driver_data = BOSCH_BMC156 },
+ { .name = "bmi055_accel", .driver_data = BOSCH_UNKNOWN },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, bmc150_accel_id);
+
+static struct spi_driver bmc150_accel_driver = {
+ .driver = {
+ .name = "bmc150_accel_spi",
+ .acpi_match_table = bmc150_accel_acpi_match,
+ .pm = &bmc150_accel_pm_ops,
+ },
+ .probe = bmc150_accel_probe,
+ .remove = bmc150_accel_remove,
+ .id_table = bmc150_accel_id,
+};
+module_spi_driver(bmc150_accel_driver);
+
+MODULE_AUTHOR("Markus Pargmann <mpa@pengutronix.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BMC150 SPI accelerometer driver");
+MODULE_IMPORT_NS("IIO_BMC150");
diff --git a/drivers/iio/accel/bmc150-accel.h b/drivers/iio/accel/bmc150-accel.h
new file mode 100644
index 000000000..e8f261983
--- /dev/null
+++ b/drivers/iio/accel/bmc150-accel.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _BMC150_ACCEL_H_
+#define _BMC150_ACCEL_H_
+
+#include <linux/atomic.h>
+#include <linux/iio/iio.h>
+#include <linux/mutex.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+struct regmap;
+struct i2c_client;
+struct bmc150_accel_chip_info;
+struct bmc150_accel_interrupt_info;
+
+/*
+ * We can often guess better than "UNKNOWN" based on the device IDs
+ * but unfortunately this information is not always accurate. There are some
+ * devices where ACPI firmware specifies an ID like "BMA250E" when the device
+ * actually has a BMA222E. The driver attempts to detect those by reading the
+ * chip ID from the registers but this information is not always enough either.
+ *
+ * Therefore, this enum should be only used when the chip ID detection is not
+ * enough and we can be reasonably sure that the device IDs are reliable
+ * in practice (e.g. for device tree platforms).
+ */
+enum bmc150_type {
+ BOSCH_UNKNOWN,
+ BOSCH_BMC156,
+};
+
+struct bmc150_accel_interrupt {
+ const struct bmc150_accel_interrupt_info *info;
+ atomic_t users;
+};
+
+struct bmc150_accel_trigger {
+ struct bmc150_accel_data *data;
+ struct iio_trigger *indio_trig;
+ int (*setup)(struct bmc150_accel_trigger *t, bool state);
+ int intr;
+ bool enabled;
+};
+
+enum bmc150_accel_interrupt_id {
+ BMC150_ACCEL_INT_DATA_READY,
+ BMC150_ACCEL_INT_ANY_MOTION,
+ BMC150_ACCEL_INT_WATERMARK,
+ BMC150_ACCEL_INTERRUPTS,
+};
+
+enum bmc150_accel_trigger_id {
+ BMC150_ACCEL_TRIGGER_DATA_READY,
+ BMC150_ACCEL_TRIGGER_ANY_MOTION,
+ BMC150_ACCEL_TRIGGERS,
+};
+
+struct bmc150_accel_data {
+ struct regmap *regmap;
+ int irq;
+ struct regulator_bulk_data regulators[2];
+ struct bmc150_accel_interrupt interrupts[BMC150_ACCEL_INTERRUPTS];
+ struct bmc150_accel_trigger triggers[BMC150_ACCEL_TRIGGERS];
+ struct mutex mutex;
+ u8 fifo_mode, watermark;
+ s16 buffer[8];
+ /*
+ * Ensure there is sufficient space and correct alignment for
+ * the timestamp if enabled
+ */
+ struct {
+ __le16 channels[3];
+ aligned_s64 ts;
+ } scan;
+ u8 bw_bits;
+ u32 slope_dur;
+ u32 slope_thres;
+ u32 range;
+ int ev_enable_state;
+ int64_t timestamp, old_timestamp; /* Only used in hw fifo mode. */
+ const struct bmc150_accel_chip_info *chip_info;
+ enum bmc150_type type;
+ struct i2c_client *second_device;
+ void (*resume_callback)(struct device *dev);
+ struct delayed_work resume_work;
+ struct iio_mount_matrix orientation;
+};
+
+int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq,
+ enum bmc150_type type, const char *name,
+ bool block_supported);
+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;
+
+#endif /* _BMC150_ACCEL_H_ */
diff --git a/drivers/iio/accel/bmi088-accel-core.c b/drivers/iio/accel/bmi088-accel-core.c
new file mode 100644
index 000000000..c7da90af0
--- /dev/null
+++ b/drivers/iio/accel/bmi088-accel-core.c
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 3-axis accelerometer driver supporting following Bosch-Sensortec chips:
+ * - BMI088
+ * - BMI085
+ * - BMI090L
+ *
+ * Copyright (c) 2018-2021, Topic Embedded Products
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#include "bmi088-accel.h"
+
+#define BMI088_ACCEL_REG_CHIP_ID 0x00
+#define BMI088_ACCEL_REG_ERROR 0x02
+
+#define BMI088_ACCEL_REG_INT_STATUS 0x1D
+#define BMI088_ACCEL_INT_STATUS_BIT_DRDY BIT(7)
+
+#define BMI088_ACCEL_REG_RESET 0x7E
+#define BMI088_ACCEL_RESET_VAL 0xB6
+
+#define BMI088_ACCEL_REG_PWR_CTRL 0x7D
+#define BMI088_ACCEL_REG_PWR_CONF 0x7C
+
+#define BMI088_ACCEL_REG_INT_MAP_DATA 0x58
+#define BMI088_ACCEL_INT_MAP_DATA_BIT_INT1_DRDY BIT(2)
+#define BMI088_ACCEL_INT_MAP_DATA_BIT_INT2_FWM BIT(5)
+
+#define BMI088_ACCEL_REG_INT1_IO_CONF 0x53
+#define BMI088_ACCEL_INT1_IO_CONF_BIT_ENABLE_OUT BIT(3)
+#define BMI088_ACCEL_INT1_IO_CONF_BIT_LVL BIT(1)
+
+#define BMI088_ACCEL_REG_INT2_IO_CONF 0x54
+#define BMI088_ACCEL_INT2_IO_CONF_BIT_ENABLE_OUT BIT(3)
+#define BMI088_ACCEL_INT2_IO_CONF_BIT_LVL BIT(1)
+
+#define BMI088_ACCEL_REG_ACC_CONF 0x40
+#define BMI088_ACCEL_MODE_ODR_MASK 0x0f
+
+#define BMI088_ACCEL_REG_ACC_RANGE 0x41
+#define BMI088_ACCEL_RANGE_3G 0x00
+#define BMI088_ACCEL_RANGE_6G 0x01
+#define BMI088_ACCEL_RANGE_12G 0x02
+#define BMI088_ACCEL_RANGE_24G 0x03
+
+#define BMI088_ACCEL_REG_TEMP 0x22
+#define BMI088_ACCEL_REG_TEMP_SHIFT 5
+#define BMI088_ACCEL_TEMP_UNIT 125
+#define BMI088_ACCEL_TEMP_OFFSET 23000
+
+#define BMI088_ACCEL_REG_XOUT_L 0x12
+#define BMI088_ACCEL_AXIS_TO_REG(axis) \
+ (BMI088_ACCEL_REG_XOUT_L + (axis * 2))
+
+#define BMI088_ACCEL_MAX_STARTUP_TIME_US 1000
+#define BMI088_AUTO_SUSPEND_DELAY_MS 2000
+
+#define BMI088_ACCEL_REG_FIFO_STATUS 0x0E
+#define BMI088_ACCEL_REG_FIFO_CONFIG0 0x48
+#define BMI088_ACCEL_REG_FIFO_CONFIG1 0x49
+#define BMI088_ACCEL_REG_FIFO_DATA 0x3F
+#define BMI088_ACCEL_FIFO_LENGTH 100
+
+#define BMI088_ACCEL_FIFO_MODE_FIFO 0x40
+#define BMI088_ACCEL_FIFO_MODE_STREAM 0x80
+
+#define BMIO088_ACCEL_ACC_RANGE_MSK GENMASK(1, 0)
+
+enum bmi088_accel_axis {
+ AXIS_X,
+ AXIS_Y,
+ AXIS_Z,
+};
+
+static const int bmi088_sample_freqs[] = {
+ 12, 500000,
+ 25, 0,
+ 50, 0,
+ 100, 0,
+ 200, 0,
+ 400, 0,
+ 800, 0,
+ 1600, 0,
+};
+
+/* Available OSR (over sampling rate) sets the 3dB cut-off frequency */
+enum bmi088_osr_modes {
+ BMI088_ACCEL_MODE_OSR_NORMAL = 0xA,
+ BMI088_ACCEL_MODE_OSR_2 = 0x9,
+ BMI088_ACCEL_MODE_OSR_4 = 0x8,
+};
+
+/* Available ODR (output data rates) in Hz */
+enum bmi088_odr_modes {
+ BMI088_ACCEL_MODE_ODR_12_5 = 0x5,
+ BMI088_ACCEL_MODE_ODR_25 = 0x6,
+ BMI088_ACCEL_MODE_ODR_50 = 0x7,
+ BMI088_ACCEL_MODE_ODR_100 = 0x8,
+ BMI088_ACCEL_MODE_ODR_200 = 0x9,
+ BMI088_ACCEL_MODE_ODR_400 = 0xa,
+ BMI088_ACCEL_MODE_ODR_800 = 0xb,
+ BMI088_ACCEL_MODE_ODR_1600 = 0xc,
+};
+
+struct bmi088_accel_chip_info {
+ const char *name;
+ u8 chip_id;
+ const struct iio_chan_spec *channels;
+ int num_channels;
+ const int scale_table[4][2];
+};
+
+struct bmi088_accel_data {
+ struct regmap *regmap;
+ const struct bmi088_accel_chip_info *chip_info;
+ u8 buffer[2] __aligned(IIO_DMA_MINALIGN); /* shared DMA safe buffer */
+};
+
+static const struct regmap_range bmi088_volatile_ranges[] = {
+ /* All registers below 0x40 are volatile, except the CHIP ID. */
+ regmap_reg_range(BMI088_ACCEL_REG_ERROR, 0x3f),
+ /* Mark the RESET as volatile too, it is self-clearing */
+ regmap_reg_range(BMI088_ACCEL_REG_RESET, BMI088_ACCEL_REG_RESET),
+};
+
+static const struct regmap_access_table bmi088_volatile_table = {
+ .yes_ranges = bmi088_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(bmi088_volatile_ranges),
+};
+
+const struct regmap_config bmi088_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x7E,
+ .volatile_table = &bmi088_volatile_table,
+ .cache_type = REGCACHE_MAPLE,
+};
+EXPORT_SYMBOL_NS_GPL(bmi088_regmap_conf, "IIO_BMI088");
+
+static int bmi088_accel_power_up(struct bmi088_accel_data *data)
+{
+ int ret;
+
+ /* Enable accelerometer and temperature sensor */
+ ret = regmap_write(data->regmap, BMI088_ACCEL_REG_PWR_CTRL, 0x4);
+ if (ret)
+ return ret;
+
+ /* Datasheet recommends to wait at least 5ms before communication */
+ usleep_range(5000, 6000);
+
+ /* Disable suspend mode */
+ ret = regmap_write(data->regmap, BMI088_ACCEL_REG_PWR_CONF, 0x0);
+ if (ret)
+ return ret;
+
+ /* Recommended at least 1ms before further communication */
+ usleep_range(1000, 1200);
+
+ return 0;
+}
+
+static int bmi088_accel_power_down(struct bmi088_accel_data *data)
+{
+ int ret;
+
+ /* Enable suspend mode */
+ ret = regmap_write(data->regmap, BMI088_ACCEL_REG_PWR_CONF, 0x3);
+ if (ret)
+ return ret;
+
+ /* Recommended at least 1ms before further communication */
+ usleep_range(1000, 1200);
+
+ /* Disable accelerometer and temperature sensor */
+ ret = regmap_write(data->regmap, BMI088_ACCEL_REG_PWR_CTRL, 0x0);
+ if (ret)
+ return ret;
+
+ /* Datasheet recommends to wait at least 5ms before communication */
+ usleep_range(5000, 6000);
+
+ return 0;
+}
+
+static int bmi088_accel_get_sample_freq(struct bmi088_accel_data *data,
+ int *val, int *val2)
+{
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, BMI088_ACCEL_REG_ACC_CONF,
+ &value);
+ if (ret)
+ return ret;
+
+ value &= BMI088_ACCEL_MODE_ODR_MASK;
+ value -= BMI088_ACCEL_MODE_ODR_12_5;
+ value <<= 1;
+
+ if (value >= ARRAY_SIZE(bmi088_sample_freqs) - 1)
+ return -EINVAL;
+
+ *val = bmi088_sample_freqs[value];
+ *val2 = bmi088_sample_freqs[value + 1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int bmi088_accel_set_sample_freq(struct bmi088_accel_data *data, int val)
+{
+ unsigned int regval;
+ int index = 0;
+
+ while (index < ARRAY_SIZE(bmi088_sample_freqs) &&
+ bmi088_sample_freqs[index] != val)
+ index += 2;
+
+ if (index >= ARRAY_SIZE(bmi088_sample_freqs))
+ return -EINVAL;
+
+ regval = (index >> 1) + BMI088_ACCEL_MODE_ODR_12_5;
+
+ return regmap_update_bits(data->regmap, BMI088_ACCEL_REG_ACC_CONF,
+ BMI088_ACCEL_MODE_ODR_MASK, regval);
+}
+
+static int bmi088_accel_set_scale(struct bmi088_accel_data *data, int val, int val2)
+{
+ unsigned int i;
+
+ for (i = 0; i < 4; i++)
+ if (val == data->chip_info->scale_table[i][0] &&
+ val2 == data->chip_info->scale_table[i][1])
+ break;
+
+ if (i == 4)
+ return -EINVAL;
+
+ return regmap_write(data->regmap, BMI088_ACCEL_REG_ACC_RANGE, i);
+}
+
+static int bmi088_accel_get_temp(struct bmi088_accel_data *data, int *val)
+{
+ int ret;
+ s16 temp;
+
+ ret = regmap_bulk_read(data->regmap, BMI088_ACCEL_REG_TEMP,
+ &data->buffer, sizeof(__be16));
+ if (ret)
+ return ret;
+
+ /* data->buffer is cacheline aligned */
+ temp = be16_to_cpu(*(__be16 *)data->buffer);
+
+ *val = temp >> BMI088_ACCEL_REG_TEMP_SHIFT;
+
+ return IIO_VAL_INT;
+}
+
+static int bmi088_accel_get_axis(struct bmi088_accel_data *data,
+ struct iio_chan_spec const *chan,
+ int *val)
+{
+ int ret;
+ s16 raw_val;
+
+ ret = regmap_bulk_read(data->regmap,
+ BMI088_ACCEL_AXIS_TO_REG(chan->scan_index),
+ data->buffer, sizeof(__le16));
+ if (ret)
+ return ret;
+
+ raw_val = le16_to_cpu(*(__le16 *)data->buffer);
+ *val = raw_val;
+
+ return IIO_VAL_INT;
+}
+
+static int bmi088_accel_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct bmi088_accel_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+ int reg;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_TEMP:
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = bmi088_accel_get_temp(data, val);
+ goto out_read_raw_pm_put;
+ case IIO_ACCEL:
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ if (!iio_device_claim_direct(indio_dev)) {
+ ret = -EBUSY;
+ goto out_read_raw_pm_put;
+ }
+
+ ret = bmi088_accel_get_axis(data, chan, val);
+ iio_device_release_direct(indio_dev);
+ if (!ret)
+ ret = IIO_VAL_INT;
+
+ goto out_read_raw_pm_put;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_TEMP:
+ /* Offset applies before scale */
+ *val = BMI088_ACCEL_TEMP_OFFSET/BMI088_ACCEL_TEMP_UNIT;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_TEMP:
+ /* 0.125 degrees per LSB */
+ *val = BMI088_ACCEL_TEMP_UNIT;
+ return IIO_VAL_INT;
+ case IIO_ACCEL:
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap,
+ BMI088_ACCEL_REG_ACC_RANGE, &reg);
+ if (ret)
+ goto out_read_raw_pm_put;
+
+ reg = FIELD_GET(BMIO088_ACCEL_ACC_RANGE_MSK, reg);
+ *val = data->chip_info->scale_table[reg][0];
+ *val2 = data->chip_info->scale_table[reg][1];
+ ret = IIO_VAL_INT_PLUS_MICRO;
+
+ goto out_read_raw_pm_put;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = bmi088_accel_get_sample_freq(data, val, val2);
+ goto out_read_raw_pm_put;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+
+out_read_raw_pm_put:
+ pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+
+static int bmi088_accel_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct bmi088_accel_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *vals = (const int *)data->chip_info->scale_table;
+ *length = 8;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *vals = bmi088_sample_freqs;
+ *length = ARRAY_SIZE(bmi088_sample_freqs);
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bmi088_accel_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct bmi088_accel_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = bmi088_accel_set_scale(data, val, val2);
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = bmi088_accel_set_sample_freq(data, val);
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+#define BMI088_ACCEL_CHANNEL(_axis) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .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) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = AXIS_##_axis, \
+}
+
+static const struct iio_chan_spec bmi088_accel_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ .scan_index = -1,
+ },
+ BMI088_ACCEL_CHANNEL(X),
+ BMI088_ACCEL_CHANNEL(Y),
+ BMI088_ACCEL_CHANNEL(Z),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const struct bmi088_accel_chip_info bmi088_accel_chip_info_tbl[] = {
+ [BOSCH_BMI085] = {
+ .name = "bmi085-accel",
+ .chip_id = 0x1F,
+ .channels = bmi088_accel_channels,
+ .num_channels = ARRAY_SIZE(bmi088_accel_channels),
+ .scale_table = {{0, 598}, {0, 1196}, {0, 2393}, {0, 4785}},
+ },
+ [BOSCH_BMI088] = {
+ .name = "bmi088-accel",
+ .chip_id = 0x1E,
+ .channels = bmi088_accel_channels,
+ .num_channels = ARRAY_SIZE(bmi088_accel_channels),
+ .scale_table = {{0, 897}, {0, 1794}, {0, 3589}, {0, 7178}},
+ },
+ [BOSCH_BMI090L] = {
+ .name = "bmi090l-accel",
+ .chip_id = 0x1A,
+ .channels = bmi088_accel_channels,
+ .num_channels = ARRAY_SIZE(bmi088_accel_channels),
+ .scale_table = {{0, 897}, {0, 1794}, {0, 3589}, {0, 7178}},
+ },
+};
+
+static const struct iio_info bmi088_accel_info = {
+ .read_raw = bmi088_accel_read_raw,
+ .write_raw = bmi088_accel_write_raw,
+ .read_avail = bmi088_accel_read_avail,
+};
+
+static const unsigned long bmi088_accel_scan_masks[] = {
+ BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z),
+ 0
+};
+
+static int bmi088_accel_chip_init(struct bmi088_accel_data *data, enum bmi_device_type type)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret, i;
+ unsigned int val;
+
+ if (type >= BOSCH_UNKNOWN)
+ return -ENODEV;
+
+ /* Do a dummy read to enable SPI interface, won't harm I2C */
+ regmap_read(data->regmap, BMI088_ACCEL_REG_INT_STATUS, &val);
+
+ /*
+ * Reset chip to get it in a known good state. A delay of 1ms after
+ * reset is required according to the data sheet
+ */
+ ret = regmap_write(data->regmap, BMI088_ACCEL_REG_RESET,
+ BMI088_ACCEL_RESET_VAL);
+ if (ret)
+ return ret;
+
+ usleep_range(1000, 2000);
+
+ /* Do a dummy read again after a reset to enable the SPI interface */
+ regmap_read(data->regmap, BMI088_ACCEL_REG_INT_STATUS, &val);
+
+ /* Read chip ID */
+ ret = regmap_read(data->regmap, BMI088_ACCEL_REG_CHIP_ID, &val);
+ if (ret) {
+ dev_err(dev, "Error: Reading chip id\n");
+ return ret;
+ }
+
+ /* Validate chip ID */
+ for (i = 0; i < ARRAY_SIZE(bmi088_accel_chip_info_tbl); i++)
+ if (bmi088_accel_chip_info_tbl[i].chip_id == val)
+ break;
+
+ if (i == ARRAY_SIZE(bmi088_accel_chip_info_tbl))
+ data->chip_info = &bmi088_accel_chip_info_tbl[type];
+ else
+ data->chip_info = &bmi088_accel_chip_info_tbl[i];
+
+ if (i != type)
+ dev_warn(dev, "unexpected chip id 0x%X\n", val);
+
+ return 0;
+}
+
+int bmi088_accel_core_probe(struct device *dev, struct regmap *regmap,
+ int irq, enum bmi_device_type type)
+{
+ struct bmi088_accel_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);
+ dev_set_drvdata(dev, indio_dev);
+
+ data->regmap = regmap;
+
+ ret = bmi088_accel_chip_init(data, type);
+ if (ret)
+ return ret;
+
+ indio_dev->channels = data->chip_info->channels;
+ indio_dev->num_channels = data->chip_info->num_channels;
+ indio_dev->name = data->chip_info->name;
+ indio_dev->available_scan_masks = bmi088_accel_scan_masks;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &bmi088_accel_info;
+
+ /* Enable runtime PM */
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_enable(dev);
+ /* We need ~6ms to startup, so set the delay to 6 seconds */
+ pm_runtime_set_autosuspend_delay(dev, 6000);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_put(dev);
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ dev_err(dev, "Unable to register iio device\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(bmi088_accel_core_probe, "IIO_BMI088");
+
+
+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);
+
+ iio_device_unregister(indio_dev);
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ bmi088_accel_power_down(data);
+}
+EXPORT_SYMBOL_NS_GPL(bmi088_accel_core_remove, "IIO_BMI088");
+
+static int bmi088_accel_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmi088_accel_data *data = iio_priv(indio_dev);
+
+ return bmi088_accel_power_down(data);
+}
+
+static int bmi088_accel_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bmi088_accel_data *data = iio_priv(indio_dev);
+
+ return bmi088_accel_power_up(data);
+}
+
+EXPORT_NS_GPL_RUNTIME_DEV_PM_OPS(bmi088_accel_pm_ops,
+ bmi088_accel_runtime_suspend,
+ bmi088_accel_runtime_resume, NULL,
+ IIO_BMI088);
+
+MODULE_AUTHOR("Niek van Agt <niek.van.agt@topicproducts.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BMI088 accelerometer driver (core)");
diff --git a/drivers/iio/accel/bmi088-accel-i2c.c b/drivers/iio/accel/bmi088-accel-i2c.c
new file mode 100644
index 000000000..aecd66e56
--- /dev/null
+++ b/drivers/iio/accel/bmi088-accel-i2c.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 3-axis accelerometer driver supporting following Bosch-Sensortec chips:
+ * - BMI088
+ * - BMI085
+ * - BMI090L
+ *
+ * Copyright 2023 Jun Yan <jerrysteve1101@gmail.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "bmi088-accel.h"
+
+static int bmi088_accel_probe(struct i2c_client *i2c)
+{
+ struct regmap *regmap;
+ const struct i2c_device_id *id = i2c_client_get_device_id(i2c);
+
+ regmap = devm_regmap_init_i2c(i2c, &bmi088_regmap_conf);
+ if (IS_ERR(regmap)) {
+ dev_err(&i2c->dev, "Failed to initialize i2c regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ return bmi088_accel_core_probe(&i2c->dev, regmap, i2c->irq,
+ id->driver_data);
+}
+
+static void bmi088_accel_remove(struct i2c_client *i2c)
+{
+ bmi088_accel_core_remove(&i2c->dev);
+}
+
+static const struct of_device_id bmi088_of_match[] = {
+ { .compatible = "bosch,bmi085-accel" },
+ { .compatible = "bosch,bmi088-accel" },
+ { .compatible = "bosch,bmi090l-accel" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bmi088_of_match);
+
+static const struct i2c_device_id bmi088_accel_id[] = {
+ { .name = "bmi085-accel", .driver_data = BOSCH_BMI085 },
+ { .name = "bmi088-accel", .driver_data = BOSCH_BMI088 },
+ { .name = "bmi090l-accel", .driver_data = BOSCH_BMI090L },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, bmi088_accel_id);
+
+static struct i2c_driver bmi088_accel_driver = {
+ .driver = {
+ .name = "bmi088_accel_i2c",
+ .pm = pm_ptr(&bmi088_accel_pm_ops),
+ .of_match_table = bmi088_of_match,
+ },
+ .probe = bmi088_accel_probe,
+ .remove = bmi088_accel_remove,
+ .id_table = bmi088_accel_id,
+};
+module_i2c_driver(bmi088_accel_driver);
+
+MODULE_AUTHOR("Jun Yan <jerrysteve1101@gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("BMI088 accelerometer driver (I2C)");
+MODULE_IMPORT_NS("IIO_BMI088");
diff --git a/drivers/iio/accel/bmi088-accel-spi.c b/drivers/iio/accel/bmi088-accel-spi.c
new file mode 100644
index 000000000..ae89e162e
--- /dev/null
+++ b/drivers/iio/accel/bmi088-accel-spi.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 3-axis accelerometer driver supporting following Bosch-Sensortec chips:
+ * - BMI088
+ * - BMI085
+ * - BMI090L
+ *
+ * Copyright (c) 2018-2020, Topic Embedded Products
+ */
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+
+#include "bmi088-accel.h"
+
+static int bmi088_regmap_spi_write(void *context, const void *data, size_t count)
+{
+ struct spi_device *spi = context;
+
+ /* Write register is same as generic SPI */
+ return spi_write(spi, data, count);
+}
+
+static int bmi088_regmap_spi_read(void *context, const void *reg,
+ size_t reg_size, void *val, size_t val_size)
+{
+ struct spi_device *spi = context;
+ u8 addr[2];
+
+ addr[0] = *(u8 *)reg;
+ addr[0] |= BIT(7); /* Set RW = '1' */
+ addr[1] = 0; /* Read requires a dummy byte transfer */
+
+ return spi_write_then_read(spi, addr, sizeof(addr), val, val_size);
+}
+
+static const struct regmap_bus bmi088_regmap_bus = {
+ .write = bmi088_regmap_spi_write,
+ .read = bmi088_regmap_spi_read,
+};
+
+static int bmi088_accel_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+ const struct spi_device_id *id = spi_get_device_id(spi);
+
+ regmap = devm_regmap_init(&spi->dev, &bmi088_regmap_bus,
+ spi, &bmi088_regmap_conf);
+
+ if (IS_ERR(regmap)) {
+ dev_err(&spi->dev, "Failed to initialize spi regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ return bmi088_accel_core_probe(&spi->dev, regmap, spi->irq,
+ id->driver_data);
+}
+
+static void bmi088_accel_remove(struct spi_device *spi)
+{
+ bmi088_accel_core_remove(&spi->dev);
+}
+
+static const struct of_device_id bmi088_of_match[] = {
+ { .compatible = "bosch,bmi085-accel" },
+ { .compatible = "bosch,bmi088-accel" },
+ { .compatible = "bosch,bmi090l-accel" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bmi088_of_match);
+
+static const struct spi_device_id bmi088_accel_id[] = {
+ { .name = "bmi085-accel", .driver_data = BOSCH_BMI085 },
+ { .name = "bmi088-accel", .driver_data = BOSCH_BMI088 },
+ { .name = "bmi090l-accel", .driver_data = BOSCH_BMI090L },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, bmi088_accel_id);
+
+static struct spi_driver bmi088_accel_driver = {
+ .driver = {
+ .name = "bmi088_accel_spi",
+ .pm = pm_ptr(&bmi088_accel_pm_ops),
+ .of_match_table = bmi088_of_match,
+ },
+ .probe = bmi088_accel_probe,
+ .remove = bmi088_accel_remove,
+ .id_table = bmi088_accel_id,
+};
+module_spi_driver(bmi088_accel_driver);
+
+MODULE_AUTHOR("Niek van Agt <niek.van.agt@topicproducts.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BMI088 accelerometer driver (SPI)");
+MODULE_IMPORT_NS("IIO_BMI088");
diff --git a/drivers/iio/accel/bmi088-accel.h b/drivers/iio/accel/bmi088-accel.h
new file mode 100644
index 000000000..80cd396a3
--- /dev/null
+++ b/drivers/iio/accel/bmi088-accel.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef BMI088_ACCEL_H
+#define BMI088_ACCEL_H
+
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+struct device;
+
+enum bmi_device_type {
+ BOSCH_BMI085,
+ BOSCH_BMI088,
+ BOSCH_BMI090L,
+ BOSCH_UNKNOWN,
+};
+
+extern const struct regmap_config bmi088_regmap_conf;
+extern const struct dev_pm_ops bmi088_accel_pm_ops;
+
+int bmi088_accel_core_probe(struct device *dev, struct regmap *regmap, int irq,
+ enum bmi_device_type type);
+void bmi088_accel_core_remove(struct device *dev);
+
+#endif /* BMI088_ACCEL_H */
diff --git a/drivers/iio/accel/cros_ec_accel_legacy.c b/drivers/iio/accel/cros_ec_accel_legacy.c
new file mode 100644
index 000000000..f7e4dc02b
--- /dev/null
+++ b/drivers/iio/accel/cros_ec_accel_legacy.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for older Chrome OS EC accelerometer
+ *
+ * Copyright 2017 Google, Inc
+ *
+ * This driver uses the memory mapper cros-ec interface to communicate
+ * with the Chrome OS EC about accelerometer data or older commands.
+ * Accelerometer access is presented through iio sysfs.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/common/cros_ec_sensors_core.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
+#include <linux/platform_device.h>
+
+#define DRV_NAME "cros-ec-accel-legacy"
+
+#define CROS_EC_SENSOR_LEGACY_NUM 2
+/*
+ * Sensor scale hard coded at 10 bits per g, computed as:
+ * g / (2^10 - 1) = 0.009586168; with g = 9.80665 m.s^-2
+ */
+#define ACCEL_LEGACY_NSCALE 9586168
+
+/*
+ * Sensor frequency is hard-coded to 10Hz.
+ */
+static const int cros_ec_legacy_sample_freq[] = { 10, 0 };
+
+static int cros_ec_accel_legacy_read_cmd(struct iio_dev *indio_dev,
+ unsigned long scan_mask, s16 *data)
+{
+ struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
+ int ret;
+ unsigned int i;
+ u8 sensor_num;
+
+ /*
+ * Read all sensor data through a command.
+ * Save sensor_num, it is assumed to stay.
+ */
+ sensor_num = st->param.info.sensor_num;
+ st->param.cmd = MOTIONSENSE_CMD_DUMP;
+ st->param.dump.max_sensor_count = CROS_EC_SENSOR_LEGACY_NUM;
+ ret = cros_ec_motion_send_host_cmd(st,
+ sizeof(st->resp->dump) + CROS_EC_SENSOR_LEGACY_NUM *
+ sizeof(struct ec_response_motion_sensor_data));
+ st->param.info.sensor_num = sensor_num;
+ if (ret != 0) {
+ dev_warn(&indio_dev->dev, "Unable to read sensor data\n");
+ return ret;
+ }
+
+ for_each_set_bit(i, &scan_mask, iio_get_masklength(indio_dev)) {
+ *data = st->resp->dump.sensor[sensor_num].data[i] *
+ st->sign[i];
+ data++;
+ }
+
+ return 0;
+}
+
+static int cros_ec_accel_legacy_read(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
+ s16 data = 0;
+ int ret;
+ int idx = chan->scan_index;
+
+ mutex_lock(&st->cmd_lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = st->read_ec_sensors_data(indio_dev, 1 << idx, &data);
+ if (ret < 0)
+ break;
+ ret = IIO_VAL_INT;
+ *val = data;
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ WARN_ON(st->type != MOTIONSENSE_TYPE_ACCEL);
+ *val = 0;
+ *val2 = ACCEL_LEGACY_NSCALE;
+ ret = IIO_VAL_INT_PLUS_NANO;
+ break;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ /* Calibration not supported. */
+ *val = 0;
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = cros_ec_legacy_sample_freq[0];
+ *val2 = cros_ec_legacy_sample_freq[1];
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ default:
+ ret = cros_ec_sensors_core_read(st, chan, val, val2,
+ mask);
+ break;
+ }
+ mutex_unlock(&st->cmd_lock);
+
+ return ret;
+}
+
+static int cros_ec_accel_legacy_write(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ /*
+ * Do nothing but don't return an error code to allow calibration
+ * script to work.
+ */
+ if (mask == IIO_CHAN_INFO_CALIBBIAS)
+ return 0;
+
+ return -EINVAL;
+}
+
+/**
+ * cros_ec_accel_legacy_read_avail() - get available values
+ * @indio_dev: pointer to state information for device
+ * @chan: channel specification structure table
+ * @vals: list of available values
+ * @type: type of data returned
+ * @length: number of data returned in the array
+ * @mask: specifies which values to be requested
+ *
+ * Return: an error code or IIO_AVAIL_LIST
+ */
+static int cros_ec_accel_legacy_read_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:
+ *length = ARRAY_SIZE(cros_ec_legacy_sample_freq);
+ *vals = cros_ec_legacy_sample_freq;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info cros_ec_accel_legacy_info = {
+ .read_raw = &cros_ec_accel_legacy_read,
+ .write_raw = &cros_ec_accel_legacy_write,
+ .read_avail = &cros_ec_accel_legacy_read_avail,
+};
+
+/*
+ * Present the channel using HTML5 standard:
+ * need to invert X and Y and invert some lid axis.
+ */
+#define CROS_EC_ACCEL_ROTATE_AXIS(_axis) \
+ ((_axis) == CROS_EC_SENSOR_Z ? CROS_EC_SENSOR_Z : \
+ ((_axis) == CROS_EC_SENSOR_X ? CROS_EC_SENSOR_Y : \
+ CROS_EC_SENSOR_X))
+
+#define CROS_EC_ACCEL_LEGACY_CHAN(_axis) \
+ { \
+ .type = IIO_ACCEL, \
+ .channel2 = IIO_MOD_X + (_axis), \
+ .modified = 1, \
+ .info_mask_separate = \
+ BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS), \
+ .info_mask_shared_by_all = \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_all_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .ext_info = cros_ec_sensors_ext_info, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = CROS_EC_SENSOR_BITS, \
+ .storagebits = CROS_EC_SENSOR_BITS, \
+ }, \
+ .scan_index = CROS_EC_ACCEL_ROTATE_AXIS(_axis), \
+ } \
+
+static const struct iio_chan_spec cros_ec_accel_legacy_channels[] = {
+ CROS_EC_ACCEL_LEGACY_CHAN(CROS_EC_SENSOR_X),
+ CROS_EC_ACCEL_LEGACY_CHAN(CROS_EC_SENSOR_Y),
+ CROS_EC_ACCEL_LEGACY_CHAN(CROS_EC_SENSOR_Z),
+ IIO_CHAN_SOFT_TIMESTAMP(CROS_EC_SENSOR_MAX_AXIS)
+};
+
+static int cros_ec_accel_legacy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct iio_dev *indio_dev;
+ struct cros_ec_sensors_core_state *state;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ ret = cros_ec_sensors_core_init(pdev, indio_dev, true,
+ cros_ec_sensors_capture);
+ if (ret)
+ return ret;
+
+ indio_dev->info = &cros_ec_accel_legacy_info;
+ state = iio_priv(indio_dev);
+
+ if (state->ec->cmd_readmem != NULL)
+ state->read_ec_sensors_data = cros_ec_sensors_read_lpc;
+ else
+ state->read_ec_sensors_data = cros_ec_accel_legacy_read_cmd;
+
+ indio_dev->channels = cros_ec_accel_legacy_channels;
+ indio_dev->num_channels = ARRAY_SIZE(cros_ec_accel_legacy_channels);
+ /* The lid sensor needs to be presented inverted. */
+ if (!strcmp(indio_dev->label, "accel-display")) {
+ state->sign[CROS_EC_SENSOR_X] = -1;
+ state->sign[CROS_EC_SENSOR_Z] = -1;
+ }
+
+ return cros_ec_sensors_core_register(dev, indio_dev, NULL);
+}
+
+static struct platform_driver cros_ec_accel_platform_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .probe = cros_ec_accel_legacy_probe,
+};
+module_platform_driver(cros_ec_accel_platform_driver);
+
+MODULE_DESCRIPTION("ChromeOS EC legacy accelerometer driver");
+MODULE_AUTHOR("Gwendal Grignou <gwendal@chromium.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/iio/accel/da280.c b/drivers/iio/accel/da280.c
new file mode 100644
index 000000000..15acbe1c2
--- /dev/null
+++ b/drivers/iio/accel/da280.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO driver for the MiraMEMS DA217 and DA280 3-axis accelerometer and
+ * IIO driver for the MiraMEMS DA226 2-axis accelerometer
+ *
+ * Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/byteorder/generic.h>
+
+#define DA280_REG_CHIP_ID 0x01
+#define DA280_REG_ACC_X_LSB 0x02
+#define DA280_REG_ACC_Y_LSB 0x04
+#define DA280_REG_ACC_Z_LSB 0x06
+#define DA280_REG_MODE_BW 0x11
+
+#define DA280_CHIP_ID 0x13
+#define DA280_MODE_ENABLE 0x1e
+#define DA280_MODE_DISABLE 0x9e
+
+/*
+ * a value of + or -4096 corresponds to + or - 1G
+ * scale = 9.81 / 4096 = 0.002395019
+ */
+
+static const int da280_nscale = 2395019;
+
+#define DA280_CHANNEL(reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec da280_channels[] = {
+ DA280_CHANNEL(DA280_REG_ACC_X_LSB, X),
+ DA280_CHANNEL(DA280_REG_ACC_Y_LSB, Y),
+ DA280_CHANNEL(DA280_REG_ACC_Z_LSB, Z),
+};
+
+struct da280_match_data {
+ const char *name;
+ int num_channels;
+};
+
+struct da280_data {
+ struct i2c_client *client;
+};
+
+static int da280_enable(struct i2c_client *client, bool enable)
+{
+ u8 data = enable ? DA280_MODE_ENABLE : DA280_MODE_DISABLE;
+
+ return i2c_smbus_write_byte_data(client, DA280_REG_MODE_BW, data);
+}
+
+static int da280_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct da280_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = i2c_smbus_read_word_data(data->client, chan->address);
+ if (ret < 0)
+ return ret;
+ /*
+ * Values are 14 bits, stored as 16 bits with the 2
+ * least significant bits always 0.
+ */
+ *val = (short)ret >> 2;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = da280_nscale;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info da280_info = {
+ .read_raw = da280_read_raw,
+};
+
+static void da280_disable(void *client)
+{
+ da280_enable(client, false);
+}
+
+static int da280_probe(struct i2c_client *client)
+{
+ const struct da280_match_data *match_data;
+ struct iio_dev *indio_dev;
+ struct da280_data *data;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, DA280_REG_CHIP_ID);
+ if (ret != DA280_CHIP_ID)
+ return (ret < 0) ? ret : -ENODEV;
+
+ match_data = i2c_get_match_data(client);
+ if (!match_data) {
+ dev_err(&client->dev, "Error match-data not set\n");
+ return -EINVAL;
+ }
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+
+ indio_dev->info = &da280_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = da280_channels;
+ indio_dev->num_channels = match_data->num_channels;
+ indio_dev->name = match_data->name;
+
+ ret = da280_enable(client, true);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(&client->dev, da280_disable, client);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int da280_suspend(struct device *dev)
+{
+ return da280_enable(to_i2c_client(dev), false);
+}
+
+static int da280_resume(struct device *dev)
+{
+ return da280_enable(to_i2c_client(dev), true);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(da280_pm_ops, da280_suspend, da280_resume);
+
+static const struct da280_match_data da217_match_data = { "da217", 3 };
+static const struct da280_match_data da226_match_data = { "da226", 2 };
+static const struct da280_match_data da280_match_data = { "da280", 3 };
+
+static const struct acpi_device_id da280_acpi_match[] = {
+ { "NSA2513", (kernel_ulong_t)&da217_match_data },
+ { "MIRAACC", (kernel_ulong_t)&da280_match_data },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, da280_acpi_match);
+
+static const struct i2c_device_id da280_i2c_id[] = {
+ { .name = "da217", .driver_data = (kernel_ulong_t)&da217_match_data },
+ { .name = "da226", .driver_data = (kernel_ulong_t)&da226_match_data },
+ { .name = "da280", .driver_data = (kernel_ulong_t)&da280_match_data },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, da280_i2c_id);
+
+static struct i2c_driver da280_driver = {
+ .driver = {
+ .name = "da280",
+ .acpi_match_table = da280_acpi_match,
+ .pm = pm_sleep_ptr(&da280_pm_ops),
+ },
+ .probe = da280_probe,
+ .id_table = da280_i2c_id,
+};
+
+module_i2c_driver(da280_driver);
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("MiraMEMS DA280 3-Axis Accelerometer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/da311.c b/drivers/iio/accel/da311.c
new file mode 100644
index 000000000..dcb69c8ea
--- /dev/null
+++ b/drivers/iio/accel/da311.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO driver for the MiraMEMS DA311 3-axis accelerometer
+ *
+ * Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (c) 2011-2013 MiraMEMS Sensing Technology Co., Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/byteorder/generic.h>
+
+#define DA311_CHIP_ID 0x13
+
+/*
+ * Note register addressed go from 0 - 0x3f and then wrap.
+ * For some reason there are 2 banks with 0 - 0x3f addresses,
+ * rather then a single 0-0x7f bank.
+ */
+
+/* Bank 0 regs */
+#define DA311_REG_BANK 0x0000
+#define DA311_REG_LDO_REG 0x0006
+#define DA311_REG_CHIP_ID 0x000f
+#define DA311_REG_TEMP_CFG_REG 0x001f
+#define DA311_REG_CTRL_REG1 0x0020
+#define DA311_REG_CTRL_REG3 0x0022
+#define DA311_REG_CTRL_REG4 0x0023
+#define DA311_REG_CTRL_REG5 0x0024
+#define DA311_REG_CTRL_REG6 0x0025
+#define DA311_REG_STATUS_REG 0x0027
+#define DA311_REG_OUT_X_L 0x0028
+#define DA311_REG_OUT_X_H 0x0029
+#define DA311_REG_OUT_Y_L 0x002a
+#define DA311_REG_OUT_Y_H 0x002b
+#define DA311_REG_OUT_Z_L 0x002c
+#define DA311_REG_OUT_Z_H 0x002d
+#define DA311_REG_INT1_CFG 0x0030
+#define DA311_REG_INT1_SRC 0x0031
+#define DA311_REG_INT1_THS 0x0032
+#define DA311_REG_INT1_DURATION 0x0033
+#define DA311_REG_INT2_CFG 0x0034
+#define DA311_REG_INT2_SRC 0x0035
+#define DA311_REG_INT2_THS 0x0036
+#define DA311_REG_INT2_DURATION 0x0037
+#define DA311_REG_CLICK_CFG 0x0038
+#define DA311_REG_CLICK_SRC 0x0039
+#define DA311_REG_CLICK_THS 0x003a
+#define DA311_REG_TIME_LIMIT 0x003b
+#define DA311_REG_TIME_LATENCY 0x003c
+#define DA311_REG_TIME_WINDOW 0x003d
+
+/* Bank 1 regs */
+#define DA311_REG_SOFT_RESET 0x0105
+#define DA311_REG_OTP_XOFF_L 0x0110
+#define DA311_REG_OTP_XOFF_H 0x0111
+#define DA311_REG_OTP_YOFF_L 0x0112
+#define DA311_REG_OTP_YOFF_H 0x0113
+#define DA311_REG_OTP_ZOFF_L 0x0114
+#define DA311_REG_OTP_ZOFF_H 0x0115
+#define DA311_REG_OTP_XSO 0x0116
+#define DA311_REG_OTP_YSO 0x0117
+#define DA311_REG_OTP_ZSO 0x0118
+#define DA311_REG_OTP_TRIM_OSC 0x011b
+#define DA311_REG_LPF_ABSOLUTE 0x011c
+#define DA311_REG_TEMP_OFF1 0x0127
+#define DA311_REG_TEMP_OFF2 0x0128
+#define DA311_REG_TEMP_OFF3 0x0129
+#define DA311_REG_OTP_TRIM_THERM_H 0x011a
+
+/*
+ * a value of + or -1024 corresponds to + or - 1G
+ * scale = 9.81 / 1024 = 0.009580078
+ */
+
+static const int da311_nscale = 9580078;
+
+#define DA311_CHANNEL(reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec da311_channels[] = {
+ /* | 0x80 comes from the android driver */
+ DA311_CHANNEL(DA311_REG_OUT_X_L | 0x80, X),
+ DA311_CHANNEL(DA311_REG_OUT_Y_L | 0x80, Y),
+ DA311_CHANNEL(DA311_REG_OUT_Z_L | 0x80, Z),
+};
+
+struct da311_data {
+ struct i2c_client *client;
+};
+
+static int da311_register_mask_write(struct i2c_client *client, u16 addr,
+ u8 mask, u8 data)
+{
+ int ret;
+ u8 tmp_data = 0;
+
+ if (addr & 0xff00) {
+ /* Select bank 1 */
+ ret = i2c_smbus_write_byte_data(client, DA311_REG_BANK, 0x01);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (mask != 0xff) {
+ ret = i2c_smbus_read_byte_data(client, addr);
+ if (ret < 0)
+ return ret;
+ tmp_data = ret;
+ }
+
+ tmp_data &= ~mask;
+ tmp_data |= data & mask;
+ ret = i2c_smbus_write_byte_data(client, addr & 0xff, tmp_data);
+ if (ret < 0)
+ return ret;
+
+ if (addr & 0xff00) {
+ /* Back to bank 0 */
+ ret = i2c_smbus_write_byte_data(client, DA311_REG_BANK, 0x00);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Init sequence taken from the android driver */
+static int da311_reset(struct i2c_client *client)
+{
+ static const struct {
+ u16 addr;
+ u8 mask;
+ u8 data;
+ } init_data[] = {
+ { DA311_REG_TEMP_CFG_REG, 0xff, 0x08 },
+ { DA311_REG_CTRL_REG5, 0xff, 0x80 },
+ { DA311_REG_CTRL_REG4, 0x30, 0x00 },
+ { DA311_REG_CTRL_REG1, 0xff, 0x6f },
+ { DA311_REG_TEMP_CFG_REG, 0xff, 0x88 },
+ { DA311_REG_LDO_REG, 0xff, 0x02 },
+ { DA311_REG_OTP_TRIM_OSC, 0xff, 0x27 },
+ { DA311_REG_LPF_ABSOLUTE, 0xff, 0x30 },
+ { DA311_REG_TEMP_OFF1, 0xff, 0x3f },
+ { DA311_REG_TEMP_OFF2, 0xff, 0xff },
+ { DA311_REG_TEMP_OFF3, 0xff, 0x0f },
+ };
+ int i, ret;
+
+ /* Reset */
+ ret = da311_register_mask_write(client, DA311_REG_SOFT_RESET,
+ 0xff, 0xaa);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(init_data); i++) {
+ ret = da311_register_mask_write(client,
+ init_data[i].addr,
+ init_data[i].mask,
+ init_data[i].data);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int da311_enable(struct i2c_client *client, bool enable)
+{
+ u8 data = enable ? 0x00 : 0x20;
+
+ return da311_register_mask_write(client, DA311_REG_TEMP_CFG_REG,
+ 0x20, data);
+}
+
+static int da311_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct da311_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = i2c_smbus_read_word_data(data->client, chan->address);
+ if (ret < 0)
+ return ret;
+ /*
+ * Values are 12 bits, stored as 16 bits with the 4
+ * least significant bits always 0.
+ */
+ *val = (short)ret >> 4;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = da311_nscale;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info da311_info = {
+ .read_raw = da311_read_raw,
+};
+
+static void da311_disable(void *client)
+{
+ da311_enable(client, false);
+}
+
+static int da311_probe(struct i2c_client *client)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct da311_data *data;
+
+ ret = i2c_smbus_read_byte_data(client, DA311_REG_CHIP_ID);
+ if (ret != DA311_CHIP_ID)
+ return (ret < 0) ? ret : -ENODEV;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+
+ indio_dev->info = &da311_info;
+ indio_dev->name = "da311";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = da311_channels;
+ indio_dev->num_channels = ARRAY_SIZE(da311_channels);
+
+ ret = da311_reset(client);
+ if (ret < 0)
+ return ret;
+
+ ret = da311_enable(client, true);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(&client->dev, da311_disable, client);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int da311_suspend(struct device *dev)
+{
+ return da311_enable(to_i2c_client(dev), false);
+}
+
+static int da311_resume(struct device *dev)
+{
+ return da311_enable(to_i2c_client(dev), true);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(da311_pm_ops, da311_suspend, da311_resume);
+
+static const struct i2c_device_id da311_i2c_id[] = {
+ { .name = "da311" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, da311_i2c_id);
+
+static struct i2c_driver da311_driver = {
+ .driver = {
+ .name = "da311",
+ .pm = pm_sleep_ptr(&da311_pm_ops),
+ },
+ .probe = da311_probe,
+ .id_table = da311_i2c_id,
+};
+
+module_i2c_driver(da311_driver);
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("MiraMEMS DA311 3-Axis Accelerometer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/dmard06.c b/drivers/iio/accel/dmard06.c
new file mode 100644
index 000000000..82816fa44
--- /dev/null
+++ b/drivers/iio/accel/dmard06.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO driver for Domintech DMARD06 accelerometer
+ *
+ * Copyright (C) 2016 Aleksei Mamlin <mamlinav@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+
+#define DMARD06_DRV_NAME "dmard06"
+
+/* Device data registers */
+#define DMARD06_CHIP_ID_REG 0x0f
+#define DMARD06_TOUT_REG 0x40
+#define DMARD06_XOUT_REG 0x41
+#define DMARD06_YOUT_REG 0x42
+#define DMARD06_ZOUT_REG 0x43
+#define DMARD06_CTRL1_REG 0x44
+
+/* Device ID value */
+#define DMARD05_CHIP_ID 0x05
+#define DMARD06_CHIP_ID 0x06
+#define DMARD07_CHIP_ID 0x07
+
+/* Device values */
+#define DMARD05_AXIS_SCALE_VAL 15625
+#define DMARD06_AXIS_SCALE_VAL 31250
+#define DMARD06_TEMP_CENTER_VAL 25
+#define DMARD06_SIGN_BIT 7
+
+/* Device power modes */
+#define DMARD06_MODE_NORMAL 0x27
+#define DMARD06_MODE_POWERDOWN 0x00
+
+/* Device channels */
+#define DMARD06_ACCEL_CHANNEL(_axis, _reg) { \
+ .type = IIO_ACCEL, \
+ .address = _reg, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .modified = 1, \
+}
+
+#define DMARD06_TEMP_CHANNEL(_reg) { \
+ .type = IIO_TEMP, \
+ .address = _reg, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_OFFSET), \
+}
+
+struct dmard06_data {
+ struct i2c_client *client;
+ u8 chip_id;
+};
+
+static const struct iio_chan_spec dmard06_channels[] = {
+ DMARD06_ACCEL_CHANNEL(X, DMARD06_XOUT_REG),
+ DMARD06_ACCEL_CHANNEL(Y, DMARD06_YOUT_REG),
+ DMARD06_ACCEL_CHANNEL(Z, DMARD06_ZOUT_REG),
+ DMARD06_TEMP_CHANNEL(DMARD06_TOUT_REG),
+};
+
+static int dmard06_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct dmard06_data *dmard06 = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = i2c_smbus_read_byte_data(dmard06->client,
+ chan->address);
+ if (ret < 0) {
+ dev_err(&dmard06->client->dev,
+ "Error reading data: %d\n", ret);
+ return ret;
+ }
+
+ *val = sign_extend32(ret, DMARD06_SIGN_BIT);
+
+ if (dmard06->chip_id == DMARD06_CHIP_ID)
+ *val = *val >> 1;
+
+ switch (chan->type) {
+ case IIO_ACCEL:
+ return IIO_VAL_INT;
+ case IIO_TEMP:
+ if (dmard06->chip_id != DMARD06_CHIP_ID)
+ *val = *val / 2;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_TEMP:
+ *val = DMARD06_TEMP_CENTER_VAL;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ *val = 0;
+ if (dmard06->chip_id == DMARD06_CHIP_ID)
+ *val2 = DMARD06_AXIS_SCALE_VAL;
+ else
+ *val2 = DMARD05_AXIS_SCALE_VAL;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info dmard06_info = {
+ .read_raw = dmard06_read_raw,
+};
+
+static int dmard06_probe(struct i2c_client *client)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct dmard06_data *dmard06;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "I2C check functionality failed\n");
+ return -ENXIO;
+ }
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dmard06));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ dmard06 = iio_priv(indio_dev);
+ dmard06->client = client;
+
+ ret = i2c_smbus_read_byte_data(dmard06->client, DMARD06_CHIP_ID_REG);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error reading chip id: %d\n", ret);
+ return ret;
+ }
+
+ if (ret != DMARD05_CHIP_ID && ret != DMARD06_CHIP_ID &&
+ ret != DMARD07_CHIP_ID) {
+ dev_err(&client->dev, "Invalid chip id: %02d\n", ret);
+ return -ENODEV;
+ }
+
+ dmard06->chip_id = ret;
+
+ i2c_set_clientdata(client, indio_dev);
+ indio_dev->name = DMARD06_DRV_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = dmard06_channels;
+ indio_dev->num_channels = ARRAY_SIZE(dmard06_channels);
+ indio_dev->info = &dmard06_info;
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int dmard06_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct dmard06_data *dmard06 = iio_priv(indio_dev);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(dmard06->client, DMARD06_CTRL1_REG,
+ DMARD06_MODE_POWERDOWN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int dmard06_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct dmard06_data *dmard06 = iio_priv(indio_dev);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(dmard06->client, DMARD06_CTRL1_REG,
+ DMARD06_MODE_NORMAL);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(dmard06_pm_ops, dmard06_suspend,
+ dmard06_resume);
+
+static const struct i2c_device_id dmard06_id[] = {
+ { .name = "dmard05" },
+ { .name = "dmard06" },
+ { .name = "dmard07" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, dmard06_id);
+
+static const struct of_device_id dmard06_of_match[] = {
+ { .compatible = "domintech,dmard05" },
+ { .compatible = "domintech,dmard06" },
+ { .compatible = "domintech,dmard07" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dmard06_of_match);
+
+static struct i2c_driver dmard06_driver = {
+ .probe = dmard06_probe,
+ .id_table = dmard06_id,
+ .driver = {
+ .name = DMARD06_DRV_NAME,
+ .of_match_table = dmard06_of_match,
+ .pm = pm_sleep_ptr(&dmard06_pm_ops),
+ },
+};
+module_i2c_driver(dmard06_driver);
+
+MODULE_AUTHOR("Aleksei Mamlin <mamlinav@gmail.com>");
+MODULE_DESCRIPTION("Domintech DMARD06 accelerometer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/dmard09.c b/drivers/iio/accel/dmard09.c
new file mode 100644
index 000000000..6f0497ab6
--- /dev/null
+++ b/drivers/iio/accel/dmard09.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO driver for the 3-axis accelerometer Domintech DMARD09.
+ *
+ * Copyright (c) 2016, Jelle van der Waa <jelle@vdwaa.nl>
+ */
+
+#include <linux/unaligned.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/units.h>
+#include <linux/iio/iio.h>
+
+#define DMARD09_DRV_NAME "dmard09"
+
+#define DMARD09_REG_CHIPID 0x18
+#define DMARD09_REG_STAT 0x0A
+#define DMARD09_REG_X 0x0C
+#define DMARD09_REG_Y 0x0E
+#define DMARD09_REG_Z 0x10
+#define DMARD09_CHIPID 0x95
+
+#define DMARD09_BUF_LEN 8
+#define DMARD09_AXIS_X 0
+#define DMARD09_AXIS_Y 1
+#define DMARD09_AXIS_Z 2
+#define DMARD09_AXIS_X_OFFSET ((DMARD09_AXIS_X + 1) * 2)
+#define DMARD09_AXIS_Y_OFFSET ((DMARD09_AXIS_Y + 1) * 2)
+#define DMARD09_AXIS_Z_OFFSET ((DMARD09_AXIS_Z + 1) * 2)
+
+struct dmard09_data {
+ struct i2c_client *client;
+};
+
+#define DMARD09_CHANNEL(_axis, offset) { \
+ .type = IIO_ACCEL, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .modified = 1, \
+ .address = offset, \
+ .channel2 = IIO_MOD_##_axis, \
+}
+
+static const struct iio_chan_spec dmard09_channels[] = {
+ DMARD09_CHANNEL(X, DMARD09_AXIS_X_OFFSET),
+ DMARD09_CHANNEL(Y, DMARD09_AXIS_Y_OFFSET),
+ DMARD09_CHANNEL(Z, DMARD09_AXIS_Z_OFFSET),
+};
+
+static int dmard09_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct dmard09_data *data = iio_priv(indio_dev);
+ u8 buf[DMARD09_BUF_LEN];
+ int ret;
+ s16 accel;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ /*
+ * Read from the DMAR09_REG_STAT register, since the chip
+ * caches reads from the individual X, Y, Z registers.
+ */
+ ret = i2c_smbus_read_i2c_block_data(data->client,
+ DMARD09_REG_STAT,
+ DMARD09_BUF_LEN, buf);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg %d\n",
+ DMARD09_REG_STAT);
+ return ret;
+ }
+
+ accel = get_unaligned_le16(&buf[chan->address]);
+
+ /* Remove lower 3 bits and sign extend */
+ accel <<= 4;
+ accel >>= 7;
+
+ *val = accel;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ /* 1 g / 32 LSB, in m/s^2 */
+ *val2 = IIO_G_TO_M_S_2(NANO / 32);
+
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info dmard09_info = {
+ .read_raw = dmard09_read_raw,
+};
+
+static int dmard09_probe(struct i2c_client *client)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct dmard09_data *data;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+
+ ret = i2c_smbus_read_byte_data(data->client, DMARD09_REG_CHIPID);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error reading chip id %d\n", ret);
+ return ret;
+ }
+
+ if (ret != DMARD09_CHIPID) {
+ dev_err(&client->dev, "Invalid chip id %d\n", ret);
+ return -ENODEV;
+ }
+
+ i2c_set_clientdata(client, indio_dev);
+ indio_dev->name = DMARD09_DRV_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = dmard09_channels;
+ indio_dev->num_channels = ARRAY_SIZE(dmard09_channels);
+ indio_dev->info = &dmard09_info;
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id dmard09_id[] = {
+ { .name = "dmard09" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, dmard09_id);
+
+static struct i2c_driver dmard09_driver = {
+ .driver = {
+ .name = DMARD09_DRV_NAME
+ },
+ .probe = dmard09_probe,
+ .id_table = dmard09_id,
+};
+
+module_i2c_driver(dmard09_driver);
+
+MODULE_AUTHOR("Jelle van der Waa <jelle@vdwaa.nl>");
+MODULE_DESCRIPTION("DMARD09 3-axis accelerometer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/dmard10.c b/drivers/iio/accel/dmard10.c
new file mode 100644
index 000000000..8ba00fd57
--- /dev/null
+++ b/drivers/iio/accel/dmard10.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO driver for the 3-axis accelerometer Domintech ARD10.
+ *
+ * Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (c) 2012 Domintech Technology Co., Ltd
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/byteorder/generic.h>
+
+#define DMARD10_REG_ACTR 0x00
+#define DMARD10_REG_AFEM 0x0c
+#define DMARD10_REG_STADR 0x12
+#define DMARD10_REG_STAINT 0x1c
+#define DMARD10_REG_MISC2 0x1f
+#define DMARD10_REG_PD 0x21
+
+#define DMARD10_MODE_OFF 0x00
+#define DMARD10_MODE_STANDBY 0x02
+#define DMARD10_MODE_ACTIVE 0x06
+#define DMARD10_MODE_READ_OTP 0x12
+#define DMARD10_MODE_RESET_DATA_PATH 0x82
+
+/* AFEN set 1, ATM[2:0]=b'000 (normal), EN_Z/Y/X/T=1 */
+#define DMARD10_VALUE_AFEM_AFEN_NORMAL 0x8f
+/* ODR[3:0]=b'0111 (100Hz), CCK[3:0]=b'0100 (204.8kHZ) */
+#define DMARD10_VALUE_CKSEL_ODR_100_204 0x74
+/* INTC[6:5]=b'00 */
+#define DMARD10_VALUE_INTC 0x00
+/* TAP1/TAP2 Average 2 */
+#define DMARD10_VALUE_TAPNS_AVE_2 0x11
+
+#define DMARD10_VALUE_STADR 0x55
+#define DMARD10_VALUE_STAINT 0xaa
+#define DMARD10_VALUE_MISC2_OSCA_EN 0x08
+#define DMARD10_VALUE_PD_RST 0x52
+
+/* Offsets into the buffer read in dmard10_read_raw() */
+#define DMARD10_X_OFFSET 1
+#define DMARD10_Y_OFFSET 2
+#define DMARD10_Z_OFFSET 3
+
+/*
+ * a value of + or -128 corresponds to + or - 1G
+ * scale = 9.81 / 128 = 0.076640625
+ */
+
+static const int dmard10_nscale = 76640625;
+
+#define DMARD10_CHANNEL(reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec dmard10_channels[] = {
+ DMARD10_CHANNEL(DMARD10_X_OFFSET, X),
+ DMARD10_CHANNEL(DMARD10_Y_OFFSET, Y),
+ DMARD10_CHANNEL(DMARD10_Z_OFFSET, Z),
+};
+
+struct dmard10_data {
+ struct i2c_client *client;
+};
+
+/* Init sequence taken from the android driver */
+static int dmard10_reset(struct i2c_client *client)
+{
+ unsigned char buffer[7];
+ int ret;
+
+ /* 1. Powerdown reset */
+ ret = i2c_smbus_write_byte_data(client, DMARD10_REG_PD,
+ DMARD10_VALUE_PD_RST);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * 2. ACTR => Standby mode => Download OTP to parameter reg =>
+ * Standby mode => Reset data path => Standby mode
+ */
+ buffer[0] = DMARD10_REG_ACTR;
+ buffer[1] = DMARD10_MODE_STANDBY;
+ buffer[2] = DMARD10_MODE_READ_OTP;
+ buffer[3] = DMARD10_MODE_STANDBY;
+ buffer[4] = DMARD10_MODE_RESET_DATA_PATH;
+ buffer[5] = DMARD10_MODE_STANDBY;
+ ret = i2c_master_send(client, buffer, 6);
+ if (ret < 0)
+ return ret;
+
+ /* 3. OSCA_EN = 1, TSTO = b'000 (INT1 = normal, TEST0 = normal) */
+ ret = i2c_smbus_write_byte_data(client, DMARD10_REG_MISC2,
+ DMARD10_VALUE_MISC2_OSCA_EN);
+ if (ret < 0)
+ return ret;
+
+ /* 4. AFEN = 1 (AFE will powerdown after ADC) */
+ buffer[0] = DMARD10_REG_AFEM;
+ buffer[1] = DMARD10_VALUE_AFEM_AFEN_NORMAL;
+ buffer[2] = DMARD10_VALUE_CKSEL_ODR_100_204;
+ buffer[3] = DMARD10_VALUE_INTC;
+ buffer[4] = DMARD10_VALUE_TAPNS_AVE_2;
+ buffer[5] = 0x00; /* DLYC, no delay timing */
+ buffer[6] = 0x07; /* INTD=1 push-pull, INTA=1 active high, AUTOT=1 */
+ ret = i2c_master_send(client, buffer, 7);
+ if (ret < 0)
+ return ret;
+
+ /* 5. Activation mode */
+ ret = i2c_smbus_write_byte_data(client, DMARD10_REG_ACTR,
+ DMARD10_MODE_ACTIVE);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* Shutdown sequence taken from the android driver */
+static int dmard10_shutdown(struct i2c_client *client)
+{
+ unsigned char buffer[3];
+
+ buffer[0] = DMARD10_REG_ACTR;
+ buffer[1] = DMARD10_MODE_STANDBY;
+ buffer[2] = DMARD10_MODE_OFF;
+
+ return i2c_master_send(client, buffer, 3);
+}
+
+static int dmard10_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct dmard10_data *data = iio_priv(indio_dev);
+ __le16 buf[4];
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ /*
+ * Read 8 bytes starting at the REG_STADR register, trying to
+ * read the individual X, Y, Z registers will always read 0.
+ */
+ ret = i2c_smbus_read_i2c_block_data(data->client,
+ DMARD10_REG_STADR,
+ sizeof(buf), (u8 *)buf);
+ if (ret < 0)
+ return ret;
+ ret = le16_to_cpu(buf[chan->address]);
+ *val = sign_extend32(ret, 12);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = dmard10_nscale;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info dmard10_info = {
+ .read_raw = dmard10_read_raw,
+};
+
+static void dmard10_shutdown_cleanup(void *client)
+{
+ dmard10_shutdown(client);
+}
+
+static int dmard10_probe(struct i2c_client *client)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct dmard10_data *data;
+
+ /* These 2 registers have special POR reset values used for id */
+ ret = i2c_smbus_read_byte_data(client, DMARD10_REG_STADR);
+ if (ret != DMARD10_VALUE_STADR)
+ return (ret < 0) ? ret : -ENODEV;
+
+ ret = i2c_smbus_read_byte_data(client, DMARD10_REG_STAINT);
+ if (ret != DMARD10_VALUE_STAINT)
+ return (ret < 0) ? ret : -ENODEV;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+
+ indio_dev->info = &dmard10_info;
+ indio_dev->name = "dmard10";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = dmard10_channels;
+ indio_dev->num_channels = ARRAY_SIZE(dmard10_channels);
+
+ ret = dmard10_reset(client);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(&client->dev, dmard10_shutdown_cleanup,
+ client);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int dmard10_suspend(struct device *dev)
+{
+ return dmard10_shutdown(to_i2c_client(dev));
+}
+
+static int dmard10_resume(struct device *dev)
+{
+ return dmard10_reset(to_i2c_client(dev));
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(dmard10_pm_ops, dmard10_suspend,
+ dmard10_resume);
+
+static const struct i2c_device_id dmard10_i2c_id[] = {
+ { .name = "dmard10" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, dmard10_i2c_id);
+
+static struct i2c_driver dmard10_driver = {
+ .driver = {
+ .name = "dmard10",
+ .pm = pm_sleep_ptr(&dmard10_pm_ops),
+ },
+ .probe = dmard10_probe,
+ .id_table = dmard10_i2c_id,
+};
+
+module_i2c_driver(dmard10_driver);
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Domintech ARD10 3-Axis Accelerometer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/fxls8962af-core.c b/drivers/iio/accel/fxls8962af-core.c
new file mode 100644
index 000000000..18d7b09bd
--- /dev/null
+++ b/drivers/iio/accel/fxls8962af-core.c
@@ -0,0 +1,1318 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NXP FXLS8962AF/FXLS8964AF Accelerometer Core Driver
+ *
+ * Copyright 2021 Connected Cars A/S
+ *
+ * Datasheet:
+ * https://www.nxp.com/docs/en/data-sheet/FXLS8962AF.pdf
+ * https://www.nxp.com/docs/en/data-sheet/FXLS8964AF.pdf
+ *
+ * Errata:
+ * https://www.nxp.com/docs/en/errata/ES_FXLS8962AF.pdf
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/units.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>
+
+#include "fxls8962af.h"
+
+#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
+#define FXLS8962AF_VECM_LSB 0x02
+#define FXLS8962AF_OUT_X_LSB 0x04
+#define FXLS8962AF_OUT_Y_LSB 0x06
+#define FXLS8962AF_OUT_Z_LSB 0x08
+#define FXLS8962AF_BUF_STATUS 0x0b
+#define FXLS8962AF_BUF_STATUS_BUF_CNT GENMASK(5, 0)
+#define FXLS8962AF_BUF_STATUS_BUF_OVF BIT(6)
+#define FXLS8962AF_BUF_STATUS_BUF_WMRK BIT(7)
+#define FXLS8962AF_BUF_X_LSB 0x0c
+#define FXLS8962AF_BUF_Y_LSB 0x0e
+#define FXLS8962AF_BUF_Z_LSB 0x10
+
+#define FXLS8962AF_PROD_REV 0x12
+#define FXLS8962AF_WHO_AM_I 0x13
+
+#define FXLS8962AF_SYS_MODE 0x14
+#define FXLS8962AF_SENS_CONFIG1 0x15
+#define FXLS8962AF_SENS_CONFIG1_ACTIVE BIT(0)
+#define FXLS8962AF_SENS_CONFIG1_RST BIT(7)
+#define FXLS8962AF_SC1_FSR_MASK GENMASK(2, 1)
+#define FXLS8962AF_SC1_FSR_PREP(x) FIELD_PREP(FXLS8962AF_SC1_FSR_MASK, (x))
+#define FXLS8962AF_SC1_FSR_GET(x) FIELD_GET(FXLS8962AF_SC1_FSR_MASK, (x))
+
+#define FXLS8962AF_SENS_CONFIG2 0x16
+#define FXLS8962AF_SENS_CONFIG3 0x17
+#define FXLS8962AF_SC3_WAKE_ODR_MASK GENMASK(7, 4)
+#define FXLS8962AF_SC3_WAKE_ODR_PREP(x) FIELD_PREP(FXLS8962AF_SC3_WAKE_ODR_MASK, (x))
+#define FXLS8962AF_SC3_WAKE_ODR_GET(x) FIELD_GET(FXLS8962AF_SC3_WAKE_ODR_MASK, (x))
+#define FXLS8962AF_SENS_CONFIG4 0x18
+#define FXLS8962AF_SC4_INT_PP_OD_MASK BIT(1)
+#define FXLS8962AF_SC4_INT_PP_OD_PREP(x) FIELD_PREP(FXLS8962AF_SC4_INT_PP_OD_MASK, (x))
+#define FXLS8962AF_SC4_INT_POL_MASK BIT(0)
+#define FXLS8962AF_SC4_INT_POL_PREP(x) FIELD_PREP(FXLS8962AF_SC4_INT_POL_MASK, (x))
+#define FXLS8962AF_SENS_CONFIG5 0x19
+
+#define FXLS8962AF_WAKE_IDLE_LSB 0x1b
+#define FXLS8962AF_SLEEP_IDLE_LSB 0x1c
+#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)
+#define FXLS8962AF_INT_PIN_SEL_INT1 0x00
+#define FXLS8962AF_INT_PIN_SEL_INT2 GENMASK(7, 0)
+
+#define FXLS8962AF_OFF_X 0x22
+#define FXLS8962AF_OFF_Y 0x23
+#define FXLS8962AF_OFF_Z 0x24
+
+#define FXLS8962AF_BUF_CONFIG1 0x26
+#define FXLS8962AF_BC1_BUF_MODE_MASK GENMASK(6, 5)
+#define FXLS8962AF_BC1_BUF_MODE_PREP(x) FIELD_PREP(FXLS8962AF_BC1_BUF_MODE_MASK, (x))
+#define FXLS8962AF_BUF_CONFIG2 0x27
+#define FXLS8962AF_BUF_CONFIG2_BUF_WMRK GENMASK(5, 0)
+
+#define FXLS8962AF_ORIENT_STATUS 0x28
+#define FXLS8962AF_ORIENT_CONFIG 0x29
+#define FXLS8962AF_ORIENT_DBCOUNT 0x2a
+#define FXLS8962AF_ORIENT_BF_ZCOMP 0x2b
+#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
+#define FXLS8962AF_SDCD_UTHS_LSB 0x35
+
+#define FXLS8962AF_SELF_TEST_CONFIG1 0x37
+#define FXLS8962AF_SELF_TEST_CONFIG2 0x38
+
+#define FXLS8962AF_MAX_REG 0x38
+
+#define FXLS8962AF_DEVICE_ID 0x62
+#define FXLS8964AF_DEVICE_ID 0x84
+#define FXLS8974CF_DEVICE_ID 0x86
+#define FXLS8967AF_DEVICE_ID 0x87
+
+/* Raw temp channel offset */
+#define FXLS8962AF_TEMP_CENTER_VAL 25
+
+#define FXLS8962AF_AUTO_SUSPEND_DELAY_MS 2000
+
+#define FXLS8962AF_FIFO_LENGTH 32
+#define FXLS8962AF_SCALE_TABLE_LEN 4
+#define FXLS8962AF_SAMP_FREQ_TABLE_LEN 13
+
+static const int fxls8962af_scale_table[FXLS8962AF_SCALE_TABLE_LEN][2] = {
+ {0, IIO_G_TO_M_S_2(980000)},
+ {0, IIO_G_TO_M_S_2(1950000)},
+ {0, IIO_G_TO_M_S_2(3910000)},
+ {0, IIO_G_TO_M_S_2(7810000)},
+};
+
+static const int fxls8962af_samp_freq_table[FXLS8962AF_SAMP_FREQ_TABLE_LEN][2] = {
+ {3200, 0}, {1600, 0}, {800, 0}, {400, 0}, {200, 0}, {100, 0},
+ {50, 0}, {25, 0}, {12, 500000}, {6, 250000}, {3, 125000},
+ {1, 563000}, {0, 781000},
+};
+
+struct fxls8962af_chip_info {
+ const char *name;
+ const struct iio_chan_spec *channels;
+ int num_channels;
+ u8 chip_id;
+};
+
+struct fxls8962af_data {
+ struct regmap *regmap;
+ const struct fxls8962af_chip_info *chip_info;
+ struct {
+ __le16 channels[3];
+ aligned_s64 ts;
+ } 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_i2c_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = FXLS8962AF_MAX_REG,
+};
+EXPORT_SYMBOL_NS_GPL(fxls8962af_i2c_regmap_conf, "IIO_FXLS8962AF");
+
+const struct regmap_config fxls8962af_spi_regmap_conf = {
+ .reg_bits = 8,
+ .pad_bits = 8,
+ .val_bits = 8,
+ .max_register = FXLS8962AF_MAX_REG,
+};
+EXPORT_SYMBOL_NS_GPL(fxls8962af_spi_regmap_conf, "IIO_FXLS8962AF");
+
+enum {
+ fxls8962af_idx_x,
+ fxls8962af_idx_y,
+ fxls8962af_idx_z,
+ fxls8962af_idx_ts,
+};
+
+enum fxls8962af_int_pin {
+ FXLS8962AF_PIN_INT1,
+ FXLS8962AF_PIN_INT2,
+};
+
+static int fxls8962af_power_on(struct fxls8962af_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ dev_err(dev, "failed to power on\n");
+
+ return ret;
+}
+
+static int fxls8962af_power_off(struct fxls8962af_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int ret;
+
+ ret = pm_runtime_put_autosuspend(dev);
+ if (ret)
+ dev_err(dev, "failed to power off\n");
+
+ return ret;
+}
+
+static int fxls8962af_standby(struct fxls8962af_data *data)
+{
+ return regmap_clear_bits(data->regmap, FXLS8962AF_SENS_CONFIG1,
+ FXLS8962AF_SENS_CONFIG1_ACTIVE);
+}
+
+static int fxls8962af_active(struct fxls8962af_data *data)
+{
+ return regmap_update_bits(data->regmap, FXLS8962AF_SENS_CONFIG1,
+ FXLS8962AF_SENS_CONFIG1_ACTIVE, 1);
+}
+
+static int fxls8962af_is_active(struct fxls8962af_data *data)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(data->regmap, FXLS8962AF_SENS_CONFIG1, &reg);
+ if (ret)
+ return ret;
+
+ return reg & FXLS8962AF_SENS_CONFIG1_ACTIVE;
+}
+
+static int fxls8962af_get_out(struct fxls8962af_data *data,
+ struct iio_chan_spec const *chan, int *val)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ __le16 raw_val;
+ int is_active;
+ int ret;
+
+ is_active = fxls8962af_is_active(data);
+ if (!is_active) {
+ ret = fxls8962af_power_on(data);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_bulk_read(data->regmap, chan->address,
+ &raw_val, sizeof(data->lower_thres));
+
+ if (!is_active)
+ fxls8962af_power_off(data);
+
+ if (ret) {
+ dev_err(dev, "failed to get out reg 0x%lx\n", chan->address);
+ return ret;
+ }
+
+ *val = sign_extend32(le16_to_cpu(raw_val),
+ chan->scan_type.realbits - 1);
+
+ return IIO_VAL_INT;
+}
+
+static int fxls8962af_read_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_SCALE:
+ *type = IIO_VAL_INT_PLUS_NANO;
+ *vals = (int *)fxls8962af_scale_table;
+ *length = ARRAY_SIZE(fxls8962af_scale_table) * 2;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *vals = (int *)fxls8962af_samp_freq_table;
+ *length = ARRAY_SIZE(fxls8962af_samp_freq_table) * 2;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int fxls8962af_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return IIO_VAL_INT_PLUS_NANO;
+ }
+}
+
+static int fxls8962af_update_config(struct fxls8962af_data *data, u8 reg,
+ u8 mask, u8 val)
+{
+ int ret;
+ int is_active;
+
+ is_active = fxls8962af_is_active(data);
+ if (is_active) {
+ ret = fxls8962af_standby(data);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits(data->regmap, reg, mask, val);
+ if (ret)
+ return ret;
+
+ if (is_active) {
+ ret = fxls8962af_active(data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fxls8962af_set_full_scale(struct fxls8962af_data *data, u32 scale)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fxls8962af_scale_table); i++)
+ if (scale == fxls8962af_scale_table[i][1])
+ break;
+
+ if (i == ARRAY_SIZE(fxls8962af_scale_table))
+ return -EINVAL;
+
+ return fxls8962af_update_config(data, FXLS8962AF_SENS_CONFIG1,
+ FXLS8962AF_SC1_FSR_MASK,
+ FXLS8962AF_SC1_FSR_PREP(i));
+}
+
+static unsigned int fxls8962af_read_full_scale(struct fxls8962af_data *data,
+ int *val)
+{
+ int ret;
+ unsigned int reg;
+ u8 range_idx;
+
+ ret = regmap_read(data->regmap, FXLS8962AF_SENS_CONFIG1, &reg);
+ if (ret)
+ return ret;
+
+ range_idx = FXLS8962AF_SC1_FSR_GET(reg);
+
+ *val = fxls8962af_scale_table[range_idx][1];
+
+ return IIO_VAL_INT_PLUS_NANO;
+}
+
+static int fxls8962af_set_samp_freq(struct fxls8962af_data *data, u32 val,
+ u32 val2)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fxls8962af_samp_freq_table); i++)
+ if (val == fxls8962af_samp_freq_table[i][0] &&
+ val2 == fxls8962af_samp_freq_table[i][1])
+ break;
+
+ if (i == ARRAY_SIZE(fxls8962af_samp_freq_table))
+ return -EINVAL;
+
+ return fxls8962af_update_config(data, FXLS8962AF_SENS_CONFIG3,
+ FXLS8962AF_SC3_WAKE_ODR_MASK,
+ FXLS8962AF_SC3_WAKE_ODR_PREP(i));
+}
+
+static unsigned int fxls8962af_read_samp_freq(struct fxls8962af_data *data,
+ int *val, int *val2)
+{
+ int ret;
+ unsigned int reg;
+ u8 range_idx;
+
+ ret = regmap_read(data->regmap, FXLS8962AF_SENS_CONFIG3, &reg);
+ if (ret)
+ return ret;
+
+ range_idx = FXLS8962AF_SC3_WAKE_ODR_GET(reg);
+
+ *val = fxls8962af_samp_freq_table[range_idx][0];
+ *val2 = fxls8962af_samp_freq_table[range_idx][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int fxls8962af_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct fxls8962af_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_TEMP:
+ case IIO_ACCEL:
+ return fxls8962af_get_out(data, chan, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ if (chan->type != IIO_TEMP)
+ return -EINVAL;
+
+ *val = FXLS8962AF_TEMP_CENTER_VAL;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_TEMP:
+ *val = MILLIDEGREE_PER_DEGREE;
+ return IIO_VAL_INT;
+ case IIO_ACCEL:
+ *val = 0;
+ return fxls8962af_read_full_scale(data, val2);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return fxls8962af_read_samp_freq(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int fxls8962af_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct fxls8962af_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ if (val != 0)
+ return -EINVAL;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = fxls8962af_set_full_scale(data, val2);
+
+ iio_device_release_direct(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = fxls8962af_set_samp_freq(data, val, val2);
+
+ iio_device_release_direct(indio_dev);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+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);
+
+ if (val > FXLS8962AF_FIFO_LENGTH)
+ val = FXLS8962AF_FIFO_LENGTH;
+
+ data->watermark = 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, bool 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 {
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ /* Not in buffered mode so disable power */
+ ret = fxls8962af_power_off(data);
+
+ iio_device_release_direct(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, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .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_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = idx, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .endianness = IIO_LE, \
+ }, \
+ .event_spec = fxls8962af_event, \
+ .num_event_specs = ARRAY_SIZE(fxls8962af_event), \
+}
+
+#define FXLS8962AF_TEMP_CHANNEL { \
+ .type = IIO_TEMP, \
+ .address = FXLS8962AF_TEMP_OUT, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET),\
+ .scan_index = -1, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 8, \
+ .storagebits = 8, \
+ }, \
+}
+
+static const struct iio_chan_spec fxls8962af_channels[] = {
+ FXLS8962AF_CHANNEL(X, FXLS8962AF_OUT_X_LSB, fxls8962af_idx_x),
+ FXLS8962AF_CHANNEL(Y, FXLS8962AF_OUT_Y_LSB, fxls8962af_idx_y),
+ FXLS8962AF_CHANNEL(Z, FXLS8962AF_OUT_Z_LSB, fxls8962af_idx_z),
+ IIO_CHAN_SOFT_TIMESTAMP(fxls8962af_idx_ts),
+ FXLS8962AF_TEMP_CHANNEL,
+};
+
+static const struct fxls8962af_chip_info fxls_chip_info_table[] = {
+ [fxls8962af] = {
+ .chip_id = FXLS8962AF_DEVICE_ID,
+ .name = "fxls8962af",
+ .channels = fxls8962af_channels,
+ .num_channels = ARRAY_SIZE(fxls8962af_channels),
+ },
+ [fxls8964af] = {
+ .chip_id = FXLS8964AF_DEVICE_ID,
+ .name = "fxls8964af",
+ .channels = fxls8962af_channels,
+ .num_channels = ARRAY_SIZE(fxls8962af_channels),
+ },
+ [fxls8967af] = {
+ .chip_id = FXLS8967AF_DEVICE_ID,
+ .name = "fxls8967af",
+ .channels = fxls8962af_channels,
+ .num_channels = ARRAY_SIZE(fxls8962af_channels),
+ },
+ [fxls8974cf] = {
+ .chip_id = FXLS8974CF_DEVICE_ID,
+ .name = "fxls8974cf",
+ .channels = fxls8962af_channels,
+ .num_channels = ARRAY_SIZE(fxls8962af_channels),
+ },
+};
+
+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,
+};
+
+static int fxls8962af_reset(struct fxls8962af_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_set_bits(data->regmap, FXLS8962AF_SENS_CONFIG1,
+ FXLS8962AF_SENS_CONFIG1_RST);
+ if (ret)
+ return ret;
+
+ /* TBOOT1, TBOOT2, specifies we have to wait between 1 - 17.7ms */
+ ret = regmap_read_poll_timeout(data->regmap, FXLS8962AF_INT_STATUS, reg,
+ (reg & FXLS8962AF_INT_STATUS_SRC_BOOT),
+ 1000, 18000);
+ if (ret == -ETIMEDOUT)
+ dev_err(dev, "reset timeout, int_status = 0x%x\n", reg);
+
+ return ret;
+}
+
+static int __fxls8962af_fifo_set_mode(struct fxls8962af_data *data, bool onoff)
+{
+ int ret;
+
+ /* Enable watermark at max fifo size */
+ ret = regmap_update_bits(data->regmap, FXLS8962AF_BUF_CONFIG2,
+ FXLS8962AF_BUF_CONFIG2_BUF_WMRK,
+ data->watermark);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(data->regmap, FXLS8962AF_BUF_CONFIG1,
+ FXLS8962AF_BC1_BUF_MODE_MASK,
+ FXLS8962AF_BC1_BUF_MODE_PREP(onoff));
+}
+
+static int fxls8962af_buffer_preenable(struct iio_dev *indio_dev)
+{
+ return fxls8962af_power_on(iio_priv(indio_dev));
+}
+
+static int fxls8962af_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct fxls8962af_data *data = iio_priv(indio_dev);
+ int ret;
+
+ fxls8962af_standby(data);
+
+ /* Enable buffer interrupt */
+ ret = regmap_set_bits(data->regmap, FXLS8962AF_INT_EN,
+ FXLS8962AF_INT_EN_BUF_EN);
+ if (ret)
+ return ret;
+
+ ret = __fxls8962af_fifo_set_mode(data, true);
+
+ fxls8962af_active(data);
+
+ return ret;
+}
+
+static int fxls8962af_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct fxls8962af_data *data = iio_priv(indio_dev);
+ int ret;
+
+ fxls8962af_standby(data);
+
+ /* Disable buffer interrupt */
+ ret = regmap_clear_bits(data->regmap, FXLS8962AF_INT_EN,
+ FXLS8962AF_INT_EN_BUF_EN);
+ if (ret)
+ return ret;
+
+ synchronize_irq(data->irq);
+
+ ret = __fxls8962af_fifo_set_mode(data, false);
+
+ if (data->enable_event)
+ fxls8962af_active(data);
+
+ return ret;
+}
+
+static int fxls8962af_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct fxls8962af_data *data = iio_priv(indio_dev);
+
+ if (!data->enable_event)
+ fxls8962af_power_off(data);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops fxls8962af_buffer_ops = {
+ .preenable = fxls8962af_buffer_preenable,
+ .postenable = fxls8962af_buffer_postenable,
+ .predisable = fxls8962af_buffer_predisable,
+ .postdisable = fxls8962af_buffer_postdisable,
+};
+
+static int fxls8962af_i2c_raw_read_errata3(struct fxls8962af_data *data,
+ u16 *buffer, int samples,
+ int sample_length)
+{
+ int i, ret;
+
+ for (i = 0; i < samples; i++) {
+ ret = regmap_raw_read(data->regmap, FXLS8962AF_BUF_X_LSB,
+ &buffer[i * 3], sample_length);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fxls8962af_fifo_transfer(struct fxls8962af_data *data,
+ u16 *buffer, int samples)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ int sample_length = 3 * sizeof(*buffer);
+ int total_length = samples * sample_length;
+ int ret;
+
+ if (i2c_verify_client(dev) &&
+ data->chip_info->chip_id == FXLS8962AF_DEVICE_ID)
+ /*
+ * Due to errata bug (only applicable on fxls8962af):
+ * E3: FIFO burst read operation error using I2C interface
+ * We have to avoid burst reads on I2C..
+ */
+ ret = fxls8962af_i2c_raw_read_errata3(data, buffer, samples,
+ sample_length);
+ else
+ ret = regmap_raw_read(data->regmap, FXLS8962AF_BUF_X_LSB, buffer,
+ total_length);
+
+ if (ret)
+ dev_err(dev, "Error transferring data from fifo: %d\n", ret);
+
+ return ret;
+}
+
+static int fxls8962af_fifo_flush(struct iio_dev *indio_dev)
+{
+ struct fxls8962af_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+ u16 buffer[FXLS8962AF_FIFO_LENGTH * 3];
+ uint64_t sample_period;
+ unsigned int reg;
+ int64_t tstamp;
+ int ret, i;
+ u8 count;
+
+ ret = regmap_read(data->regmap, FXLS8962AF_BUF_STATUS, &reg);
+ if (ret)
+ return ret;
+
+ if (reg & FXLS8962AF_BUF_STATUS_BUF_OVF) {
+ dev_err(dev, "Buffer overflow");
+ return -EOVERFLOW;
+ }
+
+ count = reg & FXLS8962AF_BUF_STATUS_BUF_CNT;
+ if (!count)
+ return 0;
+
+ count = min(count, FXLS8962AF_FIFO_LENGTH);
+
+ data->old_timestamp = data->timestamp;
+ data->timestamp = iio_get_time_ns(indio_dev);
+
+ /*
+ * Approximate timestamps for each of the sample based on the sampling,
+ * frequency, timestamp for last sample and number of samples.
+ */
+ sample_period = (data->timestamp - data->old_timestamp);
+ do_div(sample_period, count);
+ tstamp = data->timestamp - (count - 1) * sample_period;
+
+ ret = fxls8962af_fifo_transfer(data, buffer, count);
+ if (ret)
+ return ret;
+
+ /* Demux hw FIFO into kfifo. */
+ for (i = 0; i < count; i++) {
+ int j, bit;
+
+ j = 0;
+ iio_for_each_active_channel(indio_dev, bit) {
+ memcpy(&data->scan.channels[j++], &buffer[i * 3 + bit],
+ sizeof(data->scan.channels[0]));
+ }
+
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan,
+ sizeof(data->scan), tstamp);
+
+ tstamp += sample_period;
+ }
+
+ 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, &reg);
+ 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;
+ struct fxls8962af_data *data = iio_priv(indio_dev);
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(data->regmap, FXLS8962AF_INT_STATUS, &reg);
+ if (ret)
+ return IRQ_NONE;
+
+ if (reg & FXLS8962AF_INT_STATUS_SRC_BUF) {
+ ret = fxls8962af_fifo_flush(indio_dev);
+ if (ret < 0)
+ return IRQ_NONE;
+
+ 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;
+}
+
+static void fxls8962af_pm_disable(void *dev_ptr)
+{
+ struct device *dev = dev_ptr;
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
+
+ fxls8962af_standby(iio_priv(indio_dev));
+}
+
+static void fxls8962af_get_irq(struct device *dev,
+ enum fxls8962af_int_pin *pin)
+{
+ int irq;
+
+ irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
+ if (irq > 0) {
+ *pin = FXLS8962AF_PIN_INT2;
+ return;
+ }
+
+ *pin = FXLS8962AF_PIN_INT1;
+}
+
+static int fxls8962af_irq_setup(struct iio_dev *indio_dev, int irq)
+{
+ struct fxls8962af_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+ unsigned long irq_type;
+ bool irq_active_high;
+ enum fxls8962af_int_pin int_pin;
+ u8 int_pin_sel;
+ int ret;
+
+ fxls8962af_get_irq(dev, &int_pin);
+ switch (int_pin) {
+ case FXLS8962AF_PIN_INT1:
+ int_pin_sel = FXLS8962AF_INT_PIN_SEL_INT1;
+ break;
+ case FXLS8962AF_PIN_INT2:
+ int_pin_sel = FXLS8962AF_INT_PIN_SEL_INT2;
+ break;
+ default:
+ dev_err(dev, "unsupported int pin selected\n");
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(data->regmap, FXLS8962AF_INT_PIN_SEL,
+ FXLS8962AF_INT_PIN_SEL_MASK, int_pin_sel);
+ if (ret)
+ return ret;
+
+ irq_type = irq_get_trigger_type(irq);
+ switch (irq_type) {
+ case IRQF_TRIGGER_HIGH:
+ case IRQF_TRIGGER_RISING:
+ irq_active_high = true;
+ break;
+ case IRQF_TRIGGER_LOW:
+ case IRQF_TRIGGER_FALLING:
+ irq_active_high = false;
+ break;
+ default:
+ dev_info(dev, "mode %lx unsupported\n", irq_type);
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(data->regmap, FXLS8962AF_SENS_CONFIG4,
+ FXLS8962AF_SC4_INT_POL_MASK,
+ FXLS8962AF_SC4_INT_POL_PREP(irq_active_high));
+ if (ret)
+ return ret;
+
+ if (device_property_read_bool(dev, "drive-open-drain")) {
+ ret = regmap_update_bits(data->regmap, FXLS8962AF_SENS_CONFIG4,
+ FXLS8962AF_SC4_INT_PP_OD_MASK,
+ FXLS8962AF_SC4_INT_PP_OD_PREP(1));
+ if (ret)
+ return ret;
+
+ irq_type |= IRQF_SHARED;
+ }
+
+ return devm_request_threaded_irq(dev,
+ irq,
+ NULL, fxls8962af_interrupt,
+ irq_type | IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+}
+
+int fxls8962af_core_probe(struct device *dev, struct regmap *regmap, int irq)
+{
+ struct fxls8962af_data *data;
+ struct iio_dev *indio_dev;
+ unsigned int reg;
+ int ret, i;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ 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)
+ return ret;
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get vdd regulator\n");
+
+ ret = regmap_read(data->regmap, FXLS8962AF_WHO_AM_I, &reg);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(fxls_chip_info_table); i++) {
+ if (fxls_chip_info_table[i].chip_id == reg) {
+ data->chip_info = &fxls_chip_info_table[i];
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(fxls_chip_info_table)) {
+ dev_err(dev, "failed to match device in table\n");
+ return -ENXIO;
+ }
+
+ indio_dev->channels = data->chip_info->channels;
+ indio_dev->num_channels = data->chip_info->num_channels;
+ indio_dev->name = data->chip_info->name;
+ indio_dev->info = &fxls8962af_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = fxls8962af_reset(data);
+ if (ret)
+ return ret;
+
+ if (irq) {
+ ret = fxls8962af_irq_setup(indio_dev, irq);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
+ &fxls8962af_buffer_ops);
+ if (ret)
+ return ret;
+ }
+
+ ret = pm_runtime_set_active(dev);
+ if (ret)
+ return ret;
+
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, FXLS8962AF_AUTO_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+
+ ret = devm_add_action_or_reset(dev, fxls8962af_pm_disable, dev);
+ if (ret)
+ return ret;
+
+ if (device_property_read_bool(dev, "wakeup-source")) {
+ ret = devm_device_init_wakeup(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init wakeup\n");
+ }
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(fxls8962af_core_probe, "IIO_FXLS8962AF");
+
+static int fxls8962af_runtime_suspend(struct device *dev)
+{
+ struct fxls8962af_data *data = iio_priv(dev_get_drvdata(dev));
+ int ret;
+
+ ret = fxls8962af_standby(data);
+ if (ret) {
+ dev_err(dev, "powering off device failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fxls8962af_runtime_resume(struct device *dev)
+{
+ struct fxls8962af_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return fxls8962af_active(data);
+}
+
+static int 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 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;
+}
+
+EXPORT_NS_GPL_DEV_PM_OPS(fxls8962af_pm_ops, IIO_FXLS8962AF) = {
+ SYSTEM_SLEEP_PM_OPS(fxls8962af_suspend, fxls8962af_resume)
+ RUNTIME_PM_OPS(fxls8962af_runtime_suspend, fxls8962af_runtime_resume, NULL)
+};
+
+MODULE_AUTHOR("Sean Nyekjaer <sean@geanix.com>");
+MODULE_DESCRIPTION("NXP FXLS8962AF/FXLS8964AF accelerometer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/fxls8962af-i2c.c b/drivers/iio/accel/fxls8962af-i2c.c
new file mode 100644
index 000000000..e72842b14
--- /dev/null
+++ b/drivers/iio/accel/fxls8962af-i2c.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NXP FXLS8962AF/FXLS8964AF Accelerometer I2C Driver
+ *
+ * Copyright 2021 Connected Cars A/S
+ */
+
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "fxls8962af.h"
+
+static int fxls8962af_probe(struct i2c_client *client)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_i2c(client, &fxls8962af_i2c_regmap_conf);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "Failed to initialize i2c regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ return fxls8962af_core_probe(&client->dev, regmap, client->irq);
+}
+
+static const struct i2c_device_id fxls8962af_id[] = {
+ { .name = "fxls8962af", .driver_data = fxls8962af },
+ { .name = "fxls8964af", .driver_data = fxls8964af },
+ { .name = "fxls8967af", .driver_data = fxls8967af },
+ { .name = "fxls8974cf", .driver_data = fxls8974cf },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, fxls8962af_id);
+
+static const struct of_device_id fxls8962af_of_match[] = {
+ { .compatible = "nxp,fxls8962af" },
+ { .compatible = "nxp,fxls8964af" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, fxls8962af_of_match);
+
+static struct i2c_driver fxls8962af_driver = {
+ .driver = {
+ .name = "fxls8962af_i2c",
+ .of_match_table = fxls8962af_of_match,
+ .pm = pm_ptr(&fxls8962af_pm_ops),
+ },
+ .probe = fxls8962af_probe,
+ .id_table = fxls8962af_id,
+};
+module_i2c_driver(fxls8962af_driver);
+
+MODULE_AUTHOR("Sean Nyekjaer <sean@geanix.com>");
+MODULE_DESCRIPTION("NXP FXLS8962AF/FXLS8964AF accelerometer i2c driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_FXLS8962AF");
diff --git a/drivers/iio/accel/fxls8962af-spi.c b/drivers/iio/accel/fxls8962af-spi.c
new file mode 100644
index 000000000..baf3c4352
--- /dev/null
+++ b/drivers/iio/accel/fxls8962af-spi.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NXP FXLS8962AF/FXLS8964AF Accelerometer SPI Driver
+ *
+ * Copyright 2021 Connected Cars A/S
+ */
+
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/regmap.h>
+
+#include "fxls8962af.h"
+
+static int fxls8962af_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_spi(spi, &fxls8962af_spi_regmap_conf);
+ if (IS_ERR(regmap)) {
+ dev_err(&spi->dev, "Failed to initialize spi regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ return fxls8962af_core_probe(&spi->dev, regmap, spi->irq);
+}
+
+static const struct of_device_id fxls8962af_spi_of_match[] = {
+ { .compatible = "nxp,fxls8962af" },
+ { .compatible = "nxp,fxls8964af" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, fxls8962af_spi_of_match);
+
+static const struct spi_device_id fxls8962af_spi_id_table[] = {
+ { .name = "fxls8962af", .driver_data = fxls8962af },
+ { .name = "fxls8964af", .driver_data = fxls8964af },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, fxls8962af_spi_id_table);
+
+static struct spi_driver fxls8962af_driver = {
+ .driver = {
+ .name = "fxls8962af_spi",
+ .pm = pm_ptr(&fxls8962af_pm_ops),
+ .of_match_table = fxls8962af_spi_of_match,
+ },
+ .probe = fxls8962af_probe,
+ .id_table = fxls8962af_spi_id_table,
+};
+module_spi_driver(fxls8962af_driver);
+
+MODULE_AUTHOR("Sean Nyekjaer <sean@geanix.com>");
+MODULE_DESCRIPTION("NXP FXLS8962AF/FXLS8964AF accelerometer spi driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_FXLS8962AF");
diff --git a/drivers/iio/accel/fxls8962af.h b/drivers/iio/accel/fxls8962af.h
new file mode 100644
index 000000000..1c9adfc8c
--- /dev/null
+++ b/drivers/iio/accel/fxls8962af.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2021 Connected Cars A/S
+ */
+#ifndef _FXLS8962AF_H_
+#define _FXLS8962AF_H_
+
+struct regmap;
+struct device;
+
+enum {
+ fxls8962af,
+ fxls8964af,
+ fxls8967af,
+ fxls8974cf,
+};
+
+int fxls8962af_core_probe(struct device *dev, struct regmap *regmap, int irq);
+
+extern const struct dev_pm_ops fxls8962af_pm_ops;
+extern const struct regmap_config fxls8962af_i2c_regmap_conf;
+extern const struct regmap_config fxls8962af_spi_regmap_conf;
+
+#endif /* _FXLS8962AF_H_ */
diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c
new file mode 100644
index 000000000..eb521125f
--- /dev/null
+++ b/drivers/iio/accel/hid-sensor-accel-3d.c
@@ -0,0 +1,447 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HID Sensors Driver
+ * Copyright (c) 2012, Intel Corporation.
+ */
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/hid-sensor-hub.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include "../common/hid-sensors/hid-sensor-trigger.h"
+
+enum accel_3d_channel {
+ CHANNEL_SCAN_INDEX_X,
+ CHANNEL_SCAN_INDEX_Y,
+ CHANNEL_SCAN_INDEX_Z,
+ ACCEL_3D_CHANNEL_MAX,
+};
+
+#define CHANNEL_SCAN_INDEX_TIMESTAMP ACCEL_3D_CHANNEL_MAX
+struct accel_3d_state {
+ struct hid_sensor_hub_callbacks callbacks;
+ struct hid_sensor_common common_attributes;
+ struct hid_sensor_hub_attribute_info accel[ACCEL_3D_CHANNEL_MAX];
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ u32 accel_val[3];
+ aligned_s64 timestamp;
+ } scan;
+ int scale_pre_decml;
+ int scale_post_decml;
+ int scale_precision;
+ int value_offset;
+ int64_t timestamp;
+};
+
+static const u32 accel_3d_addresses[ACCEL_3D_CHANNEL_MAX] = {
+ HID_USAGE_SENSOR_ACCEL_X_AXIS,
+ HID_USAGE_SENSOR_ACCEL_Y_AXIS,
+ HID_USAGE_SENSOR_ACCEL_Z_AXIS
+};
+
+static const u32 accel_3d_sensitivity_addresses[] = {
+ HID_USAGE_SENSOR_DATA_ACCELERATION,
+};
+
+/* Channel definitions */
+static const struct iio_chan_spec accel_3d_channels[] = {
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ .scan_index = CHANNEL_SCAN_INDEX_X,
+ }, {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_Y,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ .scan_index = CHANNEL_SCAN_INDEX_Y,
+ }, {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_Z,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ .scan_index = CHANNEL_SCAN_INDEX_Z,
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP)
+};
+
+/* Channel definitions */
+static const struct iio_chan_spec gravity_channels[] = {
+ {
+ .type = IIO_GRAVITY,
+ .modified = 1,
+ .channel2 = IIO_MOD_X,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ .scan_index = CHANNEL_SCAN_INDEX_X,
+ }, {
+ .type = IIO_GRAVITY,
+ .modified = 1,
+ .channel2 = IIO_MOD_Y,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ .scan_index = CHANNEL_SCAN_INDEX_Y,
+ }, {
+ .type = IIO_GRAVITY,
+ .modified = 1,
+ .channel2 = IIO_MOD_Z,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ .scan_index = CHANNEL_SCAN_INDEX_Z,
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP),
+};
+
+/* Channel read_raw handler */
+static int accel_3d_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct accel_3d_state *accel_state = iio_priv(indio_dev);
+ int report_id = -1;
+ u32 address;
+ int ret_type;
+ s32 min;
+ struct hid_sensor_hub_device *hsdev =
+ accel_state->common_attributes.hsdev;
+
+ *val = 0;
+ *val2 = 0;
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ hid_sensor_power_state(&accel_state->common_attributes, true);
+ report_id = accel_state->accel[chan->scan_index].report_id;
+ min = accel_state->accel[chan->scan_index].logical_minimum;
+ address = accel_3d_addresses[chan->scan_index];
+ if (report_id >= 0)
+ *val = sensor_hub_input_attr_get_raw_value(
+ accel_state->common_attributes.hsdev,
+ hsdev->usage, address, report_id,
+ SENSOR_HUB_SYNC,
+ min < 0);
+ else {
+ *val = 0;
+ hid_sensor_power_state(&accel_state->common_attributes,
+ false);
+ return -EINVAL;
+ }
+ hid_sensor_power_state(&accel_state->common_attributes, false);
+ ret_type = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ *val = accel_state->scale_pre_decml;
+ *val2 = accel_state->scale_post_decml;
+ ret_type = accel_state->scale_precision;
+ break;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = accel_state->value_offset;
+ ret_type = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret_type = hid_sensor_read_samp_freq_value(
+ &accel_state->common_attributes, val, val2);
+ break;
+ case IIO_CHAN_INFO_HYSTERESIS:
+ ret_type = hid_sensor_read_raw_hyst_value(
+ &accel_state->common_attributes, val, val2);
+ break;
+ default:
+ ret_type = -EINVAL;
+ break;
+ }
+
+ return ret_type;
+}
+
+/* Channel write_raw handler */
+static int accel_3d_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct accel_3d_state *accel_state = iio_priv(indio_dev);
+ int ret = 0;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = hid_sensor_write_samp_freq_value(
+ &accel_state->common_attributes, val, val2);
+ break;
+ case IIO_CHAN_INFO_HYSTERESIS:
+ ret = hid_sensor_write_raw_hyst_value(
+ &accel_state->common_attributes, val, val2);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct iio_info accel_3d_info = {
+ .read_raw = &accel_3d_read_raw,
+ .write_raw = &accel_3d_write_raw,
+};
+
+/* Function to push data to buffer */
+static void hid_sensor_push_data(struct iio_dev *indio_dev, void *data,
+ int len, int64_t timestamp)
+{
+ dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n");
+ iio_push_to_buffers_with_ts(indio_dev, data, len, timestamp);
+}
+
+/* Callback handler to send event after all samples are received and captured */
+static int accel_3d_proc_event(struct hid_sensor_hub_device *hsdev,
+ u32 usage_id, void *priv)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(priv);
+ struct accel_3d_state *accel_state = iio_priv(indio_dev);
+
+ dev_dbg(&indio_dev->dev, "accel_3d_proc_event\n");
+ if (atomic_read(&accel_state->common_attributes.data_ready)) {
+ if (!accel_state->timestamp)
+ accel_state->timestamp = iio_get_time_ns(indio_dev);
+
+ hid_sensor_push_data(indio_dev,
+ &accel_state->scan,
+ sizeof(accel_state->scan),
+ accel_state->timestamp);
+
+ accel_state->timestamp = 0;
+ }
+
+ return 0;
+}
+
+/* Capture samples in local storage */
+static int accel_3d_capture_sample(struct hid_sensor_hub_device *hsdev,
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *priv)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(priv);
+ struct accel_3d_state *accel_state = iio_priv(indio_dev);
+ int offset;
+ int ret = -EINVAL;
+
+ switch (usage_id) {
+ case HID_USAGE_SENSOR_ACCEL_X_AXIS:
+ case HID_USAGE_SENSOR_ACCEL_Y_AXIS:
+ case HID_USAGE_SENSOR_ACCEL_Z_AXIS:
+ offset = usage_id - HID_USAGE_SENSOR_ACCEL_X_AXIS;
+ accel_state->scan.accel_val[CHANNEL_SCAN_INDEX_X + offset] =
+ *(u32 *)raw_data;
+ ret = 0;
+ break;
+ case HID_USAGE_SENSOR_TIME_TIMESTAMP:
+ accel_state->timestamp =
+ hid_sensor_convert_timestamp(
+ &accel_state->common_attributes,
+ *(int64_t *)raw_data);
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/* Parse report which is specific to an usage id*/
+static int accel_3d_parse_report(struct platform_device *pdev,
+ struct hid_sensor_hub_device *hsdev,
+ struct iio_chan_spec *channels,
+ u32 usage_id,
+ struct accel_3d_state *st)
+{
+ int ret;
+
+ for (unsigned int ch = CHANNEL_SCAN_INDEX_X; ch <= CHANNEL_SCAN_INDEX_Z; ch++) {
+ ret = sensor_hub_input_get_attribute_info(hsdev,
+ HID_INPUT_REPORT,
+ usage_id,
+ HID_USAGE_SENSOR_ACCEL_X_AXIS + ch,
+ &st->accel[ch]);
+ if (ret < 0)
+ break;
+ channels[ch].scan_type = (struct iio_scan_type) {
+ .format = 's',
+ .realbits = BYTES_TO_BITS(st->accel[ch].size),
+ .storagebits = BITS_PER_TYPE(u32),
+ };
+ }
+ dev_dbg(&pdev->dev, "accel_3d %x:%x, %x:%x, %x:%x\n",
+ st->accel[0].index,
+ st->accel[0].report_id,
+ st->accel[1].index, st->accel[1].report_id,
+ st->accel[2].index, st->accel[2].report_id);
+
+ st->scale_precision = hid_sensor_format_scale(
+ hsdev->usage,
+ &st->accel[CHANNEL_SCAN_INDEX_X],
+ &st->scale_pre_decml, &st->scale_post_decml);
+
+ return ret;
+}
+
+/* Function to initialize the processing for usage id */
+static int hid_accel_3d_probe(struct platform_device *pdev)
+{
+ struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev);
+ int ret = 0;
+ const char *name;
+ struct iio_dev *indio_dev;
+ struct accel_3d_state *accel_state;
+ const struct iio_chan_spec *channel_spec;
+ int channel_size;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev,
+ sizeof(struct accel_3d_state));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ accel_state = iio_priv(indio_dev);
+ accel_state->common_attributes.hsdev = hsdev;
+ accel_state->common_attributes.pdev = pdev;
+
+ if (hsdev->usage == HID_USAGE_SENSOR_ACCEL_3D) {
+ name = "accel_3d";
+ channel_spec = accel_3d_channels;
+ channel_size = sizeof(accel_3d_channels);
+ indio_dev->num_channels = ARRAY_SIZE(accel_3d_channels);
+ } else {
+ name = "gravity";
+ channel_spec = gravity_channels;
+ channel_size = sizeof(gravity_channels);
+ indio_dev->num_channels = ARRAY_SIZE(gravity_channels);
+ }
+ ret = hid_sensor_parse_common_attributes(hsdev,
+ hsdev->usage,
+ &accel_state->common_attributes,
+ accel_3d_sensitivity_addresses,
+ ARRAY_SIZE(accel_3d_sensitivity_addresses));
+ if (ret) {
+ dev_err(&pdev->dev, "failed to setup common attributes\n");
+ return ret;
+ }
+ indio_dev->channels = devm_kmemdup(&pdev->dev, channel_spec,
+ channel_size, GFP_KERNEL);
+
+ if (!indio_dev->channels) {
+ dev_err(&pdev->dev, "failed to duplicate channels\n");
+ return -ENOMEM;
+ }
+ ret = accel_3d_parse_report(pdev, hsdev,
+ (struct iio_chan_spec *)indio_dev->channels,
+ hsdev->usage, accel_state);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to setup attributes\n");
+ return ret;
+ }
+
+ indio_dev->info = &accel_3d_info;
+ indio_dev->name = name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ atomic_set(&accel_state->common_attributes.data_ready, 0);
+
+ ret = hid_sensor_setup_trigger(indio_dev, name,
+ &accel_state->common_attributes);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "trigger setup failed\n");
+ return ret;
+ }
+
+ accel_state->callbacks.send_event = accel_3d_proc_event;
+ accel_state->callbacks.capture_sample = accel_3d_capture_sample;
+ accel_state->callbacks.pdev = pdev;
+ ret = sensor_hub_register_callback(hsdev, hsdev->usage,
+ &accel_state->callbacks);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "callback reg failed\n");
+ goto error_remove_trigger;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "device register failed\n");
+ goto error_remove_callback;
+ }
+
+ return ret;
+
+error_remove_callback:
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
+error_remove_trigger:
+ hid_sensor_remove_trigger(indio_dev, &accel_state->common_attributes);
+ return ret;
+}
+
+/* Function to deinitialize the processing for usage id */
+static void hid_accel_3d_remove(struct platform_device *pdev)
+{
+ struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev);
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct accel_3d_state *accel_state = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ sensor_hub_remove_callback(hsdev, hsdev->usage);
+ hid_sensor_remove_trigger(indio_dev, &accel_state->common_attributes);
+}
+
+static const struct platform_device_id hid_accel_3d_ids[] = {
+ {
+ /* Format: HID-SENSOR-usage_id_in_hex_lowercase */
+ .name = "HID-SENSOR-200073",
+ },
+ { /* gravity sensor */
+ .name = "HID-SENSOR-20007b",
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, hid_accel_3d_ids);
+
+static struct platform_driver hid_accel_3d_platform_driver = {
+ .id_table = hid_accel_3d_ids,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .pm = &hid_sensor_pm_ops,
+ },
+ .probe = hid_accel_3d_probe,
+ .remove = hid_accel_3d_remove,
+};
+module_platform_driver(hid_accel_3d_platform_driver);
+
+MODULE_DESCRIPTION("HID Sensor Accel 3D");
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_HID");
diff --git a/drivers/iio/accel/kionix-kx022a-i2c.c b/drivers/iio/accel/kionix-kx022a-i2c.c
new file mode 100644
index 000000000..42388636c
--- /dev/null
+++ b/drivers/iio/accel/kionix-kx022a-i2c.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022 ROHM Semiconductors
+ *
+ * ROHM/KIONIX accelerometer driver
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "kionix-kx022a.h"
+
+static int kx022a_i2c_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ const struct kx022a_chip_info *chip_info;
+ struct regmap *regmap;
+
+ if (!i2c->irq) {
+ dev_err(dev, "No IRQ configured\n");
+ return -EINVAL;
+ }
+
+ chip_info = i2c_get_match_data(i2c);
+ if (!chip_info)
+ return -EINVAL;
+
+ regmap = devm_regmap_init_i2c(i2c, chip_info->regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "Failed to initialize Regmap\n");
+
+ return kx022a_probe_internal(dev, chip_info);
+}
+
+static const struct i2c_device_id kx022a_i2c_id[] = {
+ { .name = "kx022a", .driver_data = (kernel_ulong_t)&kx022a_chip_info },
+ { .name = "kx132-1211", .driver_data = (kernel_ulong_t)&kx132_chip_info },
+ { .name = "kx134-1211", .driver_data = (kernel_ulong_t)&kx134_chip_info },
+ { .name = "kx132acr-lbz", .driver_data = (kernel_ulong_t)&kx132acr_chip_info },
+ { .name = "kx134acr-lbz", .driver_data = (kernel_ulong_t)&kx134acr_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, kx022a_i2c_id);
+
+static const struct of_device_id kx022a_of_match[] = {
+ { .compatible = "kionix,kx022a", .data = &kx022a_chip_info },
+ { .compatible = "kionix,kx132-1211", .data = &kx132_chip_info },
+ { .compatible = "kionix,kx134-1211", .data = &kx134_chip_info },
+ { .compatible = "rohm,kx132acr-lbz", .data = &kx132acr_chip_info },
+ { .compatible = "rohm,kx134acr-lbz", .data = &kx134acr_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, kx022a_of_match);
+
+static struct i2c_driver kx022a_i2c_driver = {
+ .driver = {
+ .name = "kx022a-i2c",
+ .of_match_table = kx022a_of_match,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = kx022a_i2c_probe,
+ .id_table = kx022a_i2c_id,
+};
+module_i2c_driver(kx022a_i2c_driver);
+
+MODULE_DESCRIPTION("ROHM/Kionix KX022A accelerometer driver");
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_KX022A");
diff --git a/drivers/iio/accel/kionix-kx022a-spi.c b/drivers/iio/accel/kionix-kx022a-spi.c
new file mode 100644
index 000000000..e30d21083
--- /dev/null
+++ b/drivers/iio/accel/kionix-kx022a-spi.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022 ROHM Semiconductors
+ *
+ * ROHM/KIONIX accelerometer driver
+ */
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "kionix-kx022a.h"
+
+static int kx022a_spi_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ const struct kx022a_chip_info *chip_info;
+ struct regmap *regmap;
+
+ if (!spi->irq) {
+ dev_err(dev, "No IRQ configured\n");
+ return -EINVAL;
+ }
+
+ chip_info = spi_get_device_match_data(spi);
+ if (!chip_info)
+ return -EINVAL;
+
+ regmap = devm_regmap_init_spi(spi, chip_info->regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "Failed to initialize Regmap\n");
+
+ return kx022a_probe_internal(dev, chip_info);
+}
+
+static const struct spi_device_id kx022a_id[] = {
+ { .name = "kx022a", .driver_data = (kernel_ulong_t)&kx022a_chip_info },
+ { .name = "kx132-1211", .driver_data = (kernel_ulong_t)&kx132_chip_info },
+ { .name = "kx134-1211", .driver_data = (kernel_ulong_t)&kx134_chip_info },
+ { .name = "kx132acr-lbz", .driver_data = (kernel_ulong_t)&kx132acr_chip_info },
+ { .name = "kx134acr-lbz", .driver_data = (kernel_ulong_t)&kx134acr_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, kx022a_id);
+
+static const struct of_device_id kx022a_of_match[] = {
+ { .compatible = "kionix,kx022a", .data = &kx022a_chip_info },
+ { .compatible = "kionix,kx132-1211", .data = &kx132_chip_info },
+ { .compatible = "kionix,kx134-1211", .data = &kx134_chip_info },
+ { .compatible = "rohm,kx132acr-lbz", .data = &kx132acr_chip_info },
+ { .compatible = "rohm,kx134acr-lbz", .data = &kx134acr_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, kx022a_of_match);
+
+static struct spi_driver kx022a_spi_driver = {
+ .driver = {
+ .name = "kx022a-spi",
+ .of_match_table = kx022a_of_match,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = kx022a_spi_probe,
+ .id_table = kx022a_id,
+};
+module_spi_driver(kx022a_spi_driver);
+
+MODULE_DESCRIPTION("ROHM/Kionix kx022A accelerometer driver");
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_KX022A");
diff --git a/drivers/iio/accel/kionix-kx022a.c b/drivers/iio/accel/kionix-kx022a.c
new file mode 100644
index 000000000..9ff70a373
--- /dev/null
+++ b/drivers/iio/accel/kionix-kx022a.c
@@ -0,0 +1,1474 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022 ROHM Semiconductors
+ *
+ * ROHM/KIONIX accelerometer driver
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitmap.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/string_choices.h>
+#include <linux/sysfs.h>
+#include <linux/time64.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include <asm/byteorder.h>
+
+#include "kionix-kx022a.h"
+
+/*
+ * The KX022A has FIFO which can store 43 samples of HiRes data from 2
+ * channels. This equals to 43 (samples) * 3 (channels) * 2 (bytes/sample) to
+ * 258 bytes of sample data. The quirk to know is that the amount of bytes in
+ * the FIFO is advertised via 8 bit register (max value 255). The thing to note
+ * is that full 258 bytes of data is indicated using the max value 255.
+ */
+#define KX022A_FIFO_LENGTH 43
+#define KX022A_FIFO_FULL_VALUE 255
+#define KX022A_SOFT_RESET_WAIT_TIME_US (5 * USEC_PER_MSEC)
+#define KX022A_SOFT_RESET_TOTAL_WAIT_TIME_US (500 * USEC_PER_MSEC)
+
+/* 3 axis, 2 bytes of data for each of the axis */
+#define KX022A_FIFO_SAMPLES_SIZE_BYTES 6
+#define KX022A_FIFO_MAX_BYTES \
+ (KX022A_FIFO_LENGTH * KX022A_FIFO_SAMPLES_SIZE_BYTES)
+
+enum {
+ KX022A_STATE_SAMPLE,
+ KX022A_STATE_FIFO,
+};
+
+/* kx022a Regmap configs */
+static const struct regmap_range kx022a_volatile_ranges[] = {
+ {
+ .range_min = KX022A_REG_XHP_L,
+ .range_max = KX022A_REG_COTR,
+ }, {
+ .range_min = KX022A_REG_TSCP,
+ .range_max = KX022A_REG_INT_REL,
+ }, {
+ /* The reset bit will be cleared by sensor */
+ .range_min = KX022A_REG_CNTL2,
+ .range_max = KX022A_REG_CNTL2,
+ }, {
+ .range_min = KX022A_REG_BUF_STATUS_1,
+ .range_max = KX022A_REG_BUF_READ,
+ },
+};
+
+static const struct regmap_access_table kx022a_volatile_regs = {
+ .yes_ranges = &kx022a_volatile_ranges[0],
+ .n_yes_ranges = ARRAY_SIZE(kx022a_volatile_ranges),
+};
+
+static const struct regmap_range kx022a_precious_ranges[] = {
+ {
+ .range_min = KX022A_REG_INT_REL,
+ .range_max = KX022A_REG_INT_REL,
+ },
+};
+
+static const struct regmap_access_table kx022a_precious_regs = {
+ .yes_ranges = &kx022a_precious_ranges[0],
+ .n_yes_ranges = ARRAY_SIZE(kx022a_precious_ranges),
+};
+
+/*
+ * The HW does not set WHO_AM_I reg as read-only but we don't want to write it
+ * so we still include it in the read-only ranges.
+ */
+static const struct regmap_range kx022a_read_only_ranges[] = {
+ {
+ .range_min = KX022A_REG_XHP_L,
+ .range_max = KX022A_REG_INT_REL,
+ }, {
+ .range_min = KX022A_REG_BUF_STATUS_1,
+ .range_max = KX022A_REG_BUF_STATUS_2,
+ }, {
+ .range_min = KX022A_REG_BUF_READ,
+ .range_max = KX022A_REG_BUF_READ,
+ },
+};
+
+static const struct regmap_access_table kx022a_ro_regs = {
+ .no_ranges = &kx022a_read_only_ranges[0],
+ .n_no_ranges = ARRAY_SIZE(kx022a_read_only_ranges),
+};
+
+static const struct regmap_range kx022a_write_only_ranges[] = {
+ {
+ .range_min = KX022A_REG_BTS_WUF_TH,
+ .range_max = KX022A_REG_BTS_WUF_TH,
+ }, {
+ .range_min = KX022A_REG_MAN_WAKE,
+ .range_max = KX022A_REG_MAN_WAKE,
+ }, {
+ .range_min = KX022A_REG_SELF_TEST,
+ .range_max = KX022A_REG_SELF_TEST,
+ }, {
+ .range_min = KX022A_REG_BUF_CLEAR,
+ .range_max = KX022A_REG_BUF_CLEAR,
+ },
+};
+
+static const struct regmap_access_table kx022a_wo_regs = {
+ .no_ranges = &kx022a_write_only_ranges[0],
+ .n_no_ranges = ARRAY_SIZE(kx022a_write_only_ranges),
+};
+
+static const struct regmap_range kx022a_noinc_read_ranges[] = {
+ {
+ .range_min = KX022A_REG_BUF_READ,
+ .range_max = KX022A_REG_BUF_READ,
+ },
+};
+
+static const struct regmap_access_table kx022a_nir_regs = {
+ .yes_ranges = &kx022a_noinc_read_ranges[0],
+ .n_yes_ranges = ARRAY_SIZE(kx022a_noinc_read_ranges),
+};
+
+static const struct regmap_config kx022a_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_table = &kx022a_volatile_regs,
+ .rd_table = &kx022a_wo_regs,
+ .wr_table = &kx022a_ro_regs,
+ .rd_noinc_table = &kx022a_nir_regs,
+ .precious_table = &kx022a_precious_regs,
+ .max_register = KX022A_MAX_REGISTER,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+/* Regmap configs kx132 */
+static const struct regmap_range kx132_volatile_ranges[] = {
+ {
+ .range_min = KX132_REG_XADP_L,
+ .range_max = KX132_REG_COTR,
+ }, {
+ .range_min = KX132_REG_TSCP,
+ .range_max = KX132_REG_INT_REL,
+ }, {
+ /* The reset bit will be cleared by sensor */
+ .range_min = KX132_REG_CNTL2,
+ .range_max = KX132_REG_CNTL2,
+ }, {
+ .range_min = KX132_REG_CNTL5,
+ .range_max = KX132_REG_CNTL5,
+ }, {
+ .range_min = KX132_REG_BUF_STATUS_1,
+ .range_max = KX132_REG_BUF_READ,
+ },
+};
+
+static const struct regmap_access_table kx132_volatile_regs = {
+ .yes_ranges = &kx132_volatile_ranges[0],
+ .n_yes_ranges = ARRAY_SIZE(kx132_volatile_ranges),
+};
+
+static const struct regmap_range kx132_precious_ranges[] = {
+ {
+ .range_min = KX132_REG_INT_REL,
+ .range_max = KX132_REG_INT_REL,
+ },
+};
+
+static const struct regmap_access_table kx132_precious_regs = {
+ .yes_ranges = &kx132_precious_ranges[0],
+ .n_yes_ranges = ARRAY_SIZE(kx132_precious_ranges),
+};
+
+static const struct regmap_range kx132_read_only_ranges[] = {
+ {
+ .range_min = KX132_REG_XADP_L,
+ .range_max = KX132_REG_INT_REL,
+ }, {
+ .range_min = KX132_REG_BUF_STATUS_1,
+ .range_max = KX132_REG_BUF_STATUS_2,
+ }, {
+ .range_min = KX132_REG_BUF_READ,
+ .range_max = KX132_REG_BUF_READ,
+ }, {
+ /* Kionix reserved registers: should not be written */
+ .range_min = 0x28,
+ .range_max = 0x28,
+ }, {
+ .range_min = 0x35,
+ .range_max = 0x36,
+ }, {
+ .range_min = 0x3c,
+ .range_max = 0x48,
+ }, {
+ .range_min = 0x4e,
+ .range_max = 0x5c,
+ }, {
+ .range_min = 0x77,
+ .range_max = 0x7f,
+ },
+};
+
+static const struct regmap_access_table kx132_ro_regs = {
+ .no_ranges = &kx132_read_only_ranges[0],
+ .n_no_ranges = ARRAY_SIZE(kx132_read_only_ranges),
+};
+
+static const struct regmap_range kx132_write_only_ranges[] = {
+ {
+ .range_min = KX132_REG_SELF_TEST,
+ .range_max = KX132_REG_SELF_TEST,
+ }, {
+ .range_min = KX132_REG_BUF_CLEAR,
+ .range_max = KX132_REG_BUF_CLEAR,
+ },
+};
+
+static const struct regmap_access_table kx132_wo_regs = {
+ .no_ranges = &kx132_write_only_ranges[0],
+ .n_no_ranges = ARRAY_SIZE(kx132_write_only_ranges),
+};
+
+static const struct regmap_range kx132_noinc_read_ranges[] = {
+ {
+ .range_min = KX132_REG_BUF_READ,
+ .range_max = KX132_REG_BUF_READ,
+ },
+};
+
+static const struct regmap_access_table kx132_nir_regs = {
+ .yes_ranges = &kx132_noinc_read_ranges[0],
+ .n_yes_ranges = ARRAY_SIZE(kx132_noinc_read_ranges),
+};
+
+static const struct regmap_config kx132_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_table = &kx132_volatile_regs,
+ .rd_table = &kx132_wo_regs,
+ .wr_table = &kx132_ro_regs,
+ .rd_noinc_table = &kx132_nir_regs,
+ .precious_table = &kx132_precious_regs,
+ .max_register = KX132_MAX_REGISTER,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+struct kx022a_data {
+ struct regmap *regmap;
+ const struct kx022a_chip_info *chip_info;
+ struct iio_trigger *trig;
+ struct device *dev;
+ struct iio_mount_matrix orientation;
+ int64_t timestamp, old_timestamp;
+
+ int irq;
+ int inc_reg;
+ int ien_reg;
+
+ unsigned int state;
+ unsigned int odr_ns;
+
+ bool trigger_enabled;
+ /*
+ * Prevent toggling the sensor stby/active state (PC1 bit) in the
+ * middle of a configuration, or when the fifo is enabled. Also,
+ * protect the data stored/retrieved from this structure from
+ * concurrent accesses.
+ */
+ struct mutex mutex;
+ u8 watermark;
+
+ __le16 *fifo_buffer;
+
+ /* 3 x 16bit accel data + timestamp */
+ __le16 buffer[8] __aligned(IIO_DMA_MINALIGN);
+ struct {
+ __le16 channels[3];
+ aligned_s64 ts;
+ } scan;
+};
+
+static const struct iio_mount_matrix *
+kx022a_get_mount_matrix(const struct iio_dev *idev,
+ const struct iio_chan_spec *chan)
+{
+ struct kx022a_data *data = iio_priv(idev);
+
+ return &data->orientation;
+}
+
+enum {
+ AXIS_X,
+ AXIS_Y,
+ AXIS_Z,
+ AXIS_MAX
+};
+
+static const unsigned long kx022a_scan_masks[] = {
+ BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), 0
+};
+
+static const struct iio_chan_spec_ext_info kx022a_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, kx022a_get_mount_matrix),
+ { }
+};
+
+#define KX022A_ACCEL_CHAN(axis, reg, index) \
+{ \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .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_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .ext_info = kx022a_ext_info, \
+ .address = reg, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_LE, \
+ }, \
+}
+
+static const struct iio_chan_spec kx022a_channels[] = {
+ KX022A_ACCEL_CHAN(X, KX022A_REG_XOUT_L, 0),
+ KX022A_ACCEL_CHAN(Y, KX022A_REG_YOUT_L, 1),
+ KX022A_ACCEL_CHAN(Z, KX022A_REG_ZOUT_L, 2),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const struct iio_chan_spec kx132_channels[] = {
+ KX022A_ACCEL_CHAN(X, KX132_REG_XOUT_L, 0),
+ KX022A_ACCEL_CHAN(Y, KX132_REG_YOUT_L, 1),
+ KX022A_ACCEL_CHAN(Z, KX132_REG_ZOUT_L, 2),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+/*
+ * The sensor HW can support ODR up to 1600 Hz, which is beyond what most of the
+ * Linux CPUs can handle without dropping samples. Also, the low power mode is
+ * not available for higher sample rates. Thus, the driver only supports 200 Hz
+ * and slower ODRs. The slowest is 0.78 Hz.
+ */
+static const int kx022a_accel_samp_freq_table[][2] = {
+ { 0, 780000 },
+ { 1, 563000 },
+ { 3, 125000 },
+ { 6, 250000 },
+ { 12, 500000 },
+ { 25, 0 },
+ { 50, 0 },
+ { 100, 0 },
+ { 200, 0 },
+};
+
+static const unsigned int kx022a_odrs[] = {
+ 1282051282,
+ 639795266,
+ 320 * MEGA,
+ 160 * MEGA,
+ 80 * MEGA,
+ 40 * MEGA,
+ 20 * MEGA,
+ 10 * MEGA,
+ 5 * MEGA,
+};
+
+/*
+ * range is typically +-2G/4G/8G/16G, distributed over the amount of bits.
+ * The scale table can be calculated using
+ * (range / 2^bits) * g = (range / 2^bits) * 9.80665 m/s^2
+ * => KX022A uses 16 bit (HiRes mode - assume the low 8 bits are zeroed
+ * in low-power mode(?) )
+ * => +/-2G => 4 / 2^16 * 9,80665
+ * => +/-2G - 0.000598550415
+ * +/-4G - 0.00119710083
+ * +/-8G - 0.00239420166
+ * +/-16G - 0.00478840332
+ */
+static const int kx022a_scale_table[][2] = {
+ { 0, 598550 },
+ { 0, 1197101 },
+ { 0, 2394202 },
+ { 0, 4788403 },
+};
+
+/* KX134ACR-LBZ ranges are (+/-) 8, 16, 32, 64 G */
+static const int kx134acr_lbz_scale_table[][2] = {
+ { 0, 2394202 },
+ { 0, 4788403 },
+ { 0, 9576807 },
+ { 0, 19153613 },
+};
+
+static int kx022a_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct kx022a_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = (const int *)kx022a_accel_samp_freq_table;
+ *length = ARRAY_SIZE(kx022a_accel_samp_freq_table) *
+ ARRAY_SIZE(kx022a_accel_samp_freq_table[0]);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SCALE:
+ *vals = (const int *)data->chip_info->scale_table;
+ *length = data->chip_info->scale_table_size;
+ *type = IIO_VAL_INT_PLUS_NANO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+#define KX022A_DEFAULT_PERIOD_NS (20 * NSEC_PER_MSEC)
+
+static void kx022a_reg2freq(unsigned int val, int *val1, int *val2)
+{
+ *val1 = kx022a_accel_samp_freq_table[val & KX022A_MASK_ODR][0];
+ *val2 = kx022a_accel_samp_freq_table[val & KX022A_MASK_ODR][1];
+}
+
+static void kx022a_reg2scale(struct kx022a_data *data, unsigned int val,
+ unsigned int *val1, unsigned int *val2)
+{
+ val &= KX022A_MASK_GSEL;
+ val >>= KX022A_GSEL_SHIFT;
+
+ *val1 = data->chip_info->scale_table[val][0];
+ *val2 = data->chip_info->scale_table[val][1];
+}
+
+static int __kx022a_turn_on_off(struct kx022a_data *data, bool on)
+{
+ int ret;
+
+ if (on)
+ ret = regmap_set_bits(data->regmap, data->chip_info->cntl,
+ KX022A_MASK_PC1);
+ else
+ ret = regmap_clear_bits(data->regmap, data->chip_info->cntl,
+ KX022A_MASK_PC1);
+ if (ret)
+ dev_err(data->dev, "Turn %s fail %d\n", str_on_off(on), ret);
+
+ return ret;
+}
+
+static int kx022a_turn_off_lock(struct kx022a_data *data)
+{
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = __kx022a_turn_on_off(data, false);
+ if (ret)
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int kx022a_turn_on_unlock(struct kx022a_data *data)
+{
+ int ret;
+
+ ret = __kx022a_turn_on_off(data, true);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int kx022a_write_raw_get_fmt(struct iio_dev *idev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int __kx022a_write_raw(struct iio_dev *idev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct kx022a_data *data = iio_priv(idev);
+ int ret, n;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ n = ARRAY_SIZE(kx022a_accel_samp_freq_table);
+
+ while (n--)
+ if (val == kx022a_accel_samp_freq_table[n][0] &&
+ val2 == kx022a_accel_samp_freq_table[n][1])
+ break;
+ if (n < 0)
+ return -EINVAL;
+
+ ret = kx022a_turn_off_lock(data);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap,
+ data->chip_info->odcntl,
+ KX022A_MASK_ODR, n);
+ data->odr_ns = kx022a_odrs[n];
+ kx022a_turn_on_unlock(data);
+ return ret;
+ case IIO_CHAN_INFO_SCALE:
+ n = data->chip_info->scale_table_size / 2;
+
+ while (n-- > 0)
+ if (val == data->chip_info->scale_table[n][0] &&
+ val2 == data->chip_info->scale_table[n][1])
+ break;
+ if (n < 0)
+ return -EINVAL;
+
+ ret = kx022a_turn_off_lock(data);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, data->chip_info->cntl,
+ KX022A_MASK_GSEL,
+ n << KX022A_GSEL_SHIFT);
+ kx022a_turn_on_unlock(data);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int kx022a_write_raw(struct iio_dev *idev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int ret;
+
+ /*
+ * We should not allow changing scale or frequency when FIFO is running
+ * as it will mess the timestamp/scale for samples existing in the
+ * buffer. If this turns out to be an issue we can later change logic
+ * to internally flush the fifo before reconfiguring so the samples in
+ * fifo keep matching the freq/scale settings. (Such setup could cause
+ * issues if users trust the watermark to be reached within known
+ * time-limit).
+ */
+ if (!iio_device_claim_direct(idev))
+ return -EBUSY;
+
+ ret = __kx022a_write_raw(idev, chan, val, val2, mask);
+
+ iio_device_release_direct(idev);
+
+ return ret;
+}
+
+static int kx022a_fifo_set_wmi(struct kx022a_data *data)
+{
+ u8 threshold;
+
+ threshold = data->watermark;
+
+ return regmap_update_bits(data->regmap, data->chip_info->buf_cntl1,
+ KX022A_MASK_WM_TH, threshold);
+}
+
+static int kx022a_get_axis(struct kx022a_data *data,
+ struct iio_chan_spec const *chan,
+ int *val)
+{
+ int ret;
+
+ ret = regmap_bulk_read(data->regmap, chan->address, &data->buffer[0],
+ sizeof(__le16));
+ if (ret)
+ return ret;
+
+ *val = (s16)le16_to_cpu(data->buffer[0]);
+
+ return IIO_VAL_INT;
+}
+
+static int kx022a_read_raw(struct iio_dev *idev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct kx022a_data *data = iio_priv(idev);
+ unsigned int regval;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (!iio_device_claim_direct(idev))
+ return -EBUSY;
+
+ mutex_lock(&data->mutex);
+ ret = kx022a_get_axis(data, chan, val);
+ mutex_unlock(&data->mutex);
+
+ iio_device_release_direct(idev);
+
+ return ret;
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = regmap_read(data->regmap, data->chip_info->odcntl, &regval);
+ if (ret)
+ return ret;
+
+ if ((regval & KX022A_MASK_ODR) >=
+ ARRAY_SIZE(kx022a_accel_samp_freq_table)) {
+ dev_err(data->dev, "Invalid ODR\n");
+ return -EINVAL;
+ }
+
+ kx022a_reg2freq(regval, val, val2);
+
+ return IIO_VAL_INT_PLUS_MICRO;
+
+ case IIO_CHAN_INFO_SCALE:
+ ret = regmap_read(data->regmap, data->chip_info->cntl, &regval);
+ if (ret < 0)
+ return ret;
+
+ kx022a_reg2scale(data, regval, val, val2);
+
+ return IIO_VAL_INT_PLUS_NANO;
+ }
+
+ return -EINVAL;
+};
+
+static int kx022a_set_watermark(struct iio_dev *idev, unsigned int val)
+{
+ struct kx022a_data *data = iio_priv(idev);
+
+ val = min(data->chip_info->fifo_length, val);
+
+ mutex_lock(&data->mutex);
+ data->watermark = val;
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static ssize_t hwfifo_enabled_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *idev = dev_to_iio_dev(dev);
+ struct kx022a_data *data = iio_priv(idev);
+ bool state;
+
+ mutex_lock(&data->mutex);
+ state = data->state;
+ mutex_unlock(&data->mutex);
+
+ return sysfs_emit(buf, "%d\n", state);
+}
+
+static ssize_t hwfifo_watermark_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *idev = dev_to_iio_dev(dev);
+ struct kx022a_data *data = iio_priv(idev);
+ int wm;
+
+ mutex_lock(&data->mutex);
+ wm = data->watermark;
+ mutex_unlock(&data->mutex);
+
+ return sysfs_emit(buf, "%d\n", wm);
+}
+
+static IIO_DEVICE_ATTR_RO(hwfifo_enabled, 0);
+static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0);
+
+static const struct iio_dev_attr *kx022a_fifo_attributes[] = {
+ &iio_dev_attr_hwfifo_watermark,
+ &iio_dev_attr_hwfifo_enabled,
+ NULL
+};
+
+static int kx022a_drop_fifo_contents(struct kx022a_data *data)
+{
+ /*
+ * We must clear the old time-stamp to avoid computing the timestamps
+ * based on samples acquired when buffer was last enabled.
+ *
+ * We don't need to protect the timestamp as long as we are only
+ * called from fifo-disable where we can guarantee the sensor is not
+ * triggering interrupts and where the mutex is locked to prevent the
+ * user-space access.
+ */
+ data->timestamp = 0;
+
+ return regmap_write(data->regmap, data->chip_info->buf_clear, 0x0);
+}
+
+static int kx022a_get_fifo_bytes_available(struct kx022a_data *data)
+{
+ int ret, fifo_bytes;
+
+ ret = regmap_read(data->regmap, KX022A_REG_BUF_STATUS_1, &fifo_bytes);
+ if (ret) {
+ dev_err(data->dev, "Error reading buffer status\n");
+ return ret;
+ }
+
+ if (fifo_bytes == KX022A_FIFO_FULL_VALUE)
+ return KX022A_FIFO_MAX_BYTES;
+
+ return fifo_bytes;
+}
+
+static int kx132_get_fifo_bytes_available(struct kx022a_data *data)
+{
+ __le16 buf_status;
+ int ret, fifo_bytes;
+
+ ret = regmap_bulk_read(data->regmap, data->chip_info->buf_status1,
+ &buf_status, sizeof(buf_status));
+ if (ret) {
+ dev_err(data->dev, "Error reading buffer status\n");
+ return ret;
+ }
+
+ fifo_bytes = le16_to_cpu(buf_status);
+ fifo_bytes &= data->chip_info->buf_smp_lvl_mask;
+ fifo_bytes = min((unsigned int)fifo_bytes, data->chip_info->fifo_length *
+ KX022A_FIFO_SAMPLES_SIZE_BYTES);
+
+ return fifo_bytes;
+}
+
+static int __kx022a_fifo_flush(struct iio_dev *idev, unsigned int samples,
+ bool irq)
+{
+ struct kx022a_data *data = iio_priv(idev);
+ uint64_t sample_period;
+ int count, fifo_bytes;
+ bool renable = false;
+ int64_t tstamp;
+ int ret, i;
+
+ fifo_bytes = data->chip_info->get_fifo_bytes_available(data);
+
+ if (fifo_bytes % KX022A_FIFO_SAMPLES_SIZE_BYTES)
+ dev_warn(data->dev, "Bad FIFO alignment. Data may be corrupt\n");
+
+ count = fifo_bytes / KX022A_FIFO_SAMPLES_SIZE_BYTES;
+ if (!count)
+ return 0;
+
+ /*
+ * If we are being called from IRQ handler we know the stored timestamp
+ * is fairly accurate for the last stored sample. Otherwise, if we are
+ * called as a result of a read operation from userspace and hence
+ * before the watermark interrupt was triggered, take a timestamp
+ * now. We can fall anywhere in between two samples so the error in this
+ * case is at most one sample period.
+ */
+ if (!irq) {
+ /*
+ * We need to have the IRQ disabled or we risk of messing-up
+ * the timestamps. If we are ran from IRQ, then the
+ * IRQF_ONESHOT has us covered - but if we are ran by the
+ * user-space read we need to disable the IRQ to be on a safe
+ * side. We do this usng synchronous disable so that if the
+ * IRQ thread is being ran on other CPU we wait for it to be
+ * finished.
+ */
+ disable_irq(data->irq);
+ renable = true;
+
+ data->old_timestamp = data->timestamp;
+ data->timestamp = iio_get_time_ns(idev);
+ }
+
+ /*
+ * Approximate timestamps for each of the sample based on the sampling
+ * frequency, timestamp for last sample and number of samples.
+ *
+ * We'd better not use the current bandwidth settings to compute the
+ * sample period. The real sample rate varies with the device and
+ * small variation adds when we store a large number of samples.
+ *
+ * To avoid this issue we compute the actual sample period ourselves
+ * based on the timestamp delta between the last two flush operations.
+ */
+ if (data->old_timestamp) {
+ sample_period = data->timestamp - data->old_timestamp;
+ do_div(sample_period, count);
+ } else {
+ sample_period = data->odr_ns;
+ }
+ tstamp = data->timestamp - (count - 1) * sample_period;
+
+ if (samples && count > samples) {
+ /*
+ * Here we leave some old samples to the buffer. We need to
+ * adjust the timestamp to match the first sample in the buffer
+ * or we will miscalculate the sample_period at next round.
+ */
+ data->timestamp -= (count - samples) * sample_period;
+ count = samples;
+ }
+
+ fifo_bytes = count * KX022A_FIFO_SAMPLES_SIZE_BYTES;
+ ret = regmap_noinc_read(data->regmap, data->chip_info->buf_read,
+ data->fifo_buffer, fifo_bytes);
+ if (ret)
+ goto renable_out;
+
+ for (i = 0; i < count; i++) {
+ __le16 *sam = &data->fifo_buffer[i * 3];
+ __le16 *chs;
+ int bit;
+
+ chs = &data->scan.channels[0];
+ for_each_set_bit(bit, idev->active_scan_mask, AXIS_MAX)
+ chs[bit] = sam[bit];
+
+ iio_push_to_buffers_with_timestamp(idev, &data->scan, tstamp);
+
+ tstamp += sample_period;
+ }
+
+ ret = count;
+
+renable_out:
+ if (renable)
+ enable_irq(data->irq);
+
+ return ret;
+}
+
+static int kx022a_fifo_flush(struct iio_dev *idev, unsigned int samples)
+{
+ struct kx022a_data *data = iio_priv(idev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = __kx022a_fifo_flush(idev, samples, false);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static const struct iio_info kx022a_info = {
+ .read_raw = &kx022a_read_raw,
+ .write_raw = &kx022a_write_raw,
+ .write_raw_get_fmt = &kx022a_write_raw_get_fmt,
+ .read_avail = &kx022a_read_avail,
+
+ .validate_trigger = iio_validate_own_trigger,
+ .hwfifo_set_watermark = kx022a_set_watermark,
+ .hwfifo_flush_to_buffer = kx022a_fifo_flush,
+};
+
+static int kx022a_set_drdy_irq(struct kx022a_data *data, bool en)
+{
+ if (en)
+ return regmap_set_bits(data->regmap, data->chip_info->cntl,
+ KX022A_MASK_DRDY);
+
+ return regmap_clear_bits(data->regmap, data->chip_info->cntl,
+ KX022A_MASK_DRDY);
+}
+
+static int kx022a_prepare_irq_pin(struct kx022a_data *data)
+{
+ /* Enable IRQ1 pin. Set polarity to active low */
+ int mask = KX022A_MASK_IEN | KX022A_MASK_IPOL |
+ KX022A_MASK_ITYP;
+ int val = KX022A_MASK_IEN | KX022A_IPOL_LOW |
+ KX022A_ITYP_LEVEL;
+ int ret;
+
+ ret = regmap_update_bits(data->regmap, data->inc_reg, mask, val);
+ if (ret)
+ return ret;
+
+ /* We enable WMI to IRQ pin only at buffer_enable */
+ mask = KX022A_MASK_INS2_DRDY;
+
+ return regmap_set_bits(data->regmap, data->ien_reg, mask);
+}
+
+static int kx022a_fifo_disable(struct kx022a_data *data)
+{
+ int ret = 0;
+
+ guard(mutex)(&data->mutex);
+ ret = __kx022a_turn_on_off(data, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_clear_bits(data->regmap, data->ien_reg, KX022A_MASK_WMI);
+ if (ret)
+ return ret;
+
+ ret = regmap_clear_bits(data->regmap, data->chip_info->buf_cntl2,
+ KX022A_MASK_BUF_EN);
+ if (ret)
+ return ret;
+
+ data->state &= ~KX022A_STATE_FIFO;
+
+ kx022a_drop_fifo_contents(data);
+
+ kfree(data->fifo_buffer);
+
+ return __kx022a_turn_on_off(data, true);
+}
+
+static int kx022a_buffer_predisable(struct iio_dev *idev)
+{
+ struct kx022a_data *data = iio_priv(idev);
+
+ if (iio_device_get_current_mode(idev) == INDIO_BUFFER_TRIGGERED)
+ return 0;
+
+ return kx022a_fifo_disable(data);
+}
+
+static int kx022a_fifo_enable(struct kx022a_data *data)
+{
+ int ret;
+
+ data->fifo_buffer = kmalloc_array(data->chip_info->fifo_length,
+ KX022A_FIFO_SAMPLES_SIZE_BYTES,
+ GFP_KERNEL);
+ if (!data->fifo_buffer)
+ return -ENOMEM;
+
+ guard(mutex)(&data->mutex);
+ ret = __kx022a_turn_on_off(data, false);
+ if (ret)
+ goto err_free_out;
+
+ /* Update watermark to HW */
+ ret = kx022a_fifo_set_wmi(data);
+ if (ret)
+ goto err_turn_on_out;
+
+ /* Enable buffer */
+ ret = regmap_set_bits(data->regmap, data->chip_info->buf_cntl2,
+ KX022A_MASK_BUF_EN);
+ if (ret)
+ goto err_turn_on_out;
+
+ data->state |= KX022A_STATE_FIFO;
+ ret = regmap_set_bits(data->regmap, data->ien_reg,
+ KX022A_MASK_WMI);
+ if (ret)
+ goto err_buf_disable_out;
+
+ ret = __kx022a_turn_on_off(data, true);
+ if (ret)
+ goto err_wmi_clear_out;
+
+ return ret;
+
+err_wmi_clear_out:
+ regmap_clear_bits(data->regmap, data->ien_reg,
+ KX022A_MASK_WMI);
+err_buf_disable_out:
+ regmap_clear_bits(data->regmap, data->chip_info->buf_cntl2,
+ KX022A_MASK_BUF_EN);
+ data->state &= ~KX022A_STATE_FIFO;
+err_turn_on_out:
+ __kx022a_turn_on_off(data, true);
+err_free_out:
+ kfree(data->fifo_buffer);
+
+ return ret;
+}
+
+static int kx022a_buffer_postenable(struct iio_dev *idev)
+{
+ struct kx022a_data *data = iio_priv(idev);
+
+ /*
+ * If we use data-ready trigger, then the IRQ masks should be handled by
+ * trigger enable and the hardware buffer is not used but we just update
+ * results to the IIO fifo when data-ready triggers.
+ */
+ if (iio_device_get_current_mode(idev) == INDIO_BUFFER_TRIGGERED)
+ return 0;
+
+ return kx022a_fifo_enable(data);
+}
+
+static const struct iio_buffer_setup_ops kx022a_buffer_ops = {
+ .postenable = kx022a_buffer_postenable,
+ .predisable = kx022a_buffer_predisable,
+};
+
+static irqreturn_t kx022a_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *idev = pf->indio_dev;
+ struct kx022a_data *data = iio_priv(idev);
+ int ret;
+
+ ret = regmap_bulk_read(data->regmap, data->chip_info->xout_l, data->buffer,
+ KX022A_FIFO_SAMPLES_SIZE_BYTES);
+ if (ret < 0)
+ goto err_read;
+
+ iio_push_to_buffers_with_timestamp(idev, data->buffer, data->timestamp);
+err_read:
+ iio_trigger_notify_done(idev->trig);
+
+ return IRQ_HANDLED;
+}
+
+/* Get timestamps and wake the thread if we need to read data */
+static irqreturn_t kx022a_irq_handler(int irq, void *private)
+{
+ struct iio_dev *idev = private;
+ struct kx022a_data *data = iio_priv(idev);
+
+ data->old_timestamp = data->timestamp;
+ data->timestamp = iio_get_time_ns(idev);
+
+ if (data->state & KX022A_STATE_FIFO || data->trigger_enabled)
+ return IRQ_WAKE_THREAD;
+
+ return IRQ_NONE;
+}
+
+/*
+ * WMI and data-ready IRQs are acked when results are read. If we add
+ * TILT/WAKE or other IRQs - then we may need to implement the acking
+ * (which is racy).
+ */
+static irqreturn_t kx022a_irq_thread_handler(int irq, void *private)
+{
+ struct iio_dev *idev = private;
+ struct kx022a_data *data = iio_priv(idev);
+ irqreturn_t ret = IRQ_NONE;
+
+ guard(mutex)(&data->mutex);
+
+ if (data->trigger_enabled) {
+ iio_trigger_poll_nested(data->trig);
+ ret = IRQ_HANDLED;
+ }
+
+ if (data->state & KX022A_STATE_FIFO) {
+ int ok;
+
+ ok = __kx022a_fifo_flush(idev, data->chip_info->fifo_length, true);
+ if (ok > 0)
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static int kx022a_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct kx022a_data *data = iio_trigger_get_drvdata(trig);
+ int ret = 0;
+
+ guard(mutex)(&data->mutex);
+
+ if (data->trigger_enabled == state)
+ return 0;
+
+ if (data->state & KX022A_STATE_FIFO) {
+ dev_warn(data->dev, "Can't set trigger when FIFO enabled\n");
+ return -EBUSY;
+ }
+
+ ret = __kx022a_turn_on_off(data, false);
+ if (ret)
+ return ret;
+
+ data->trigger_enabled = state;
+ ret = kx022a_set_drdy_irq(data, state);
+ if (ret)
+ return ret;
+
+ return __kx022a_turn_on_off(data, true);
+}
+
+static const struct iio_trigger_ops kx022a_trigger_ops = {
+ .set_trigger_state = kx022a_trigger_set_state,
+};
+
+static int kx022a_chip_init(struct kx022a_data *data)
+{
+ int ret, val;
+
+ /* Reset the senor */
+ ret = regmap_write(data->regmap, data->chip_info->cntl2, KX022A_MASK_SRST);
+ if (ret)
+ return ret;
+
+ /*
+ * According to the power-on procedure documents, there is (at least)
+ * 2ms delay required after the software reset. This should be same for
+ * all, KX022ACR-Z, KX132-1211, KX132ACR-LBZ and KX134ACR-LBZ.
+ *
+ * https://fscdn.rohm.com/kionix/en/document/AN010_KX022ACR-Z_Power-on_Procedure_E.pdf
+ * https://fscdn.rohm.com/kionix/en/document/TN027-Power-On-Procedure.pdf
+ * https://fscdn.rohm.com/kionix/en/document/AN011_KX134ACR-LBZ_Power-on_Procedure_E.pdf
+ */
+ msleep(2);
+
+ ret = regmap_read_poll_timeout(data->regmap, data->chip_info->cntl2, val,
+ !(val & KX022A_MASK_SRST),
+ KX022A_SOFT_RESET_WAIT_TIME_US,
+ KX022A_SOFT_RESET_TOTAL_WAIT_TIME_US);
+ if (ret) {
+ dev_err(data->dev, "Sensor reset %s\n",
+ val & KX022A_MASK_SRST ? "timeout" : "fail#");
+ return ret;
+ }
+
+ ret = regmap_reinit_cache(data->regmap, data->chip_info->regmap_config);
+ if (ret) {
+ dev_err(data->dev, "Failed to reinit reg cache\n");
+ return ret;
+ }
+
+ /* set data res 16bit */
+ ret = regmap_set_bits(data->regmap, data->chip_info->buf_cntl2,
+ KX022A_MASK_BRES16);
+ if (ret) {
+ dev_err(data->dev, "Failed to set data resolution\n");
+ return ret;
+ }
+
+ return kx022a_prepare_irq_pin(data);
+}
+
+const struct kx022a_chip_info kx022a_chip_info = {
+ .name = "kx022-accel",
+ .regmap_config = &kx022a_regmap_config,
+ .channels = kx022a_channels,
+ .num_channels = ARRAY_SIZE(kx022a_channels),
+ .scale_table = kx022a_scale_table,
+ .scale_table_size = ARRAY_SIZE(kx022a_scale_table) *
+ ARRAY_SIZE(kx022a_scale_table[0]),
+ .fifo_length = KX022A_FIFO_LENGTH,
+ .who = KX022A_REG_WHO,
+ .id = KX022A_ID,
+ .cntl = KX022A_REG_CNTL,
+ .cntl2 = KX022A_REG_CNTL2,
+ .odcntl = KX022A_REG_ODCNTL,
+ .buf_cntl1 = KX022A_REG_BUF_CNTL1,
+ .buf_cntl2 = KX022A_REG_BUF_CNTL2,
+ .buf_clear = KX022A_REG_BUF_CLEAR,
+ .buf_status1 = KX022A_REG_BUF_STATUS_1,
+ .buf_read = KX022A_REG_BUF_READ,
+ .inc1 = KX022A_REG_INC1,
+ .inc4 = KX022A_REG_INC4,
+ .inc5 = KX022A_REG_INC5,
+ .inc6 = KX022A_REG_INC6,
+ .xout_l = KX022A_REG_XOUT_L,
+ .get_fifo_bytes_available = kx022a_get_fifo_bytes_available,
+};
+EXPORT_SYMBOL_NS_GPL(kx022a_chip_info, "IIO_KX022A");
+
+const struct kx022a_chip_info kx132_chip_info = {
+ .name = "kx132-1211",
+ .regmap_config = &kx132_regmap_config,
+ .channels = kx132_channels,
+ .num_channels = ARRAY_SIZE(kx132_channels),
+ .scale_table = kx022a_scale_table,
+ .scale_table_size = ARRAY_SIZE(kx022a_scale_table) *
+ ARRAY_SIZE(kx022a_scale_table[0]),
+ .fifo_length = KX132_FIFO_LENGTH,
+ .who = KX132_REG_WHO,
+ .id = KX132_ID,
+ .cntl = KX132_REG_CNTL,
+ .cntl2 = KX132_REG_CNTL2,
+ .odcntl = KX132_REG_ODCNTL,
+ .buf_cntl1 = KX132_REG_BUF_CNTL1,
+ .buf_cntl2 = KX132_REG_BUF_CNTL2,
+ .buf_clear = KX132_REG_BUF_CLEAR,
+ .buf_status1 = KX132_REG_BUF_STATUS_1,
+ .buf_smp_lvl_mask = KX132_MASK_BUF_SMP_LVL,
+ .buf_read = KX132_REG_BUF_READ,
+ .inc1 = KX132_REG_INC1,
+ .inc4 = KX132_REG_INC4,
+ .inc5 = KX132_REG_INC5,
+ .inc6 = KX132_REG_INC6,
+ .xout_l = KX132_REG_XOUT_L,
+ .get_fifo_bytes_available = kx132_get_fifo_bytes_available,
+};
+EXPORT_SYMBOL_NS_GPL(kx132_chip_info, "IIO_KX022A");
+
+const struct kx022a_chip_info kx134_chip_info = {
+ .name = "kx134-1211",
+ .regmap_config = &kx132_regmap_config,
+ .channels = kx132_channels,
+ .num_channels = ARRAY_SIZE(kx132_channels),
+ .scale_table = kx134acr_lbz_scale_table,
+ .scale_table_size = ARRAY_SIZE(kx134acr_lbz_scale_table) *
+ ARRAY_SIZE(kx134acr_lbz_scale_table[0]),
+ .fifo_length = KX132_FIFO_LENGTH,
+ .who = KX132_REG_WHO,
+ .id = KX134_1211_ID,
+ .cntl = KX132_REG_CNTL,
+ .cntl2 = KX132_REG_CNTL2,
+ .odcntl = KX132_REG_ODCNTL,
+ .buf_cntl1 = KX132_REG_BUF_CNTL1,
+ .buf_cntl2 = KX132_REG_BUF_CNTL2,
+ .buf_clear = KX132_REG_BUF_CLEAR,
+ .buf_status1 = KX132_REG_BUF_STATUS_1,
+ .buf_smp_lvl_mask = KX132_MASK_BUF_SMP_LVL,
+ .buf_read = KX132_REG_BUF_READ,
+ .inc1 = KX132_REG_INC1,
+ .inc4 = KX132_REG_INC4,
+ .inc5 = KX132_REG_INC5,
+ .inc6 = KX132_REG_INC6,
+ .xout_l = KX132_REG_XOUT_L,
+ .get_fifo_bytes_available = kx132_get_fifo_bytes_available,
+};
+EXPORT_SYMBOL_NS_GPL(kx134_chip_info, "IIO_KX022A");
+
+/*
+ * Despite the naming, KX132ACR-LBZ is not similar to KX132-1211 but it is
+ * exact subset of KX022A. KX132ACR-LBZ is meant to be used for industrial
+ * applications and the tap/double tap, free fall and tilt engines were
+ * removed. Rest of the registers and functionalities (excluding the ID
+ * register) are exact match to what is found in KX022.
+ */
+const struct kx022a_chip_info kx132acr_chip_info = {
+ .name = "kx132acr-lbz",
+ .regmap_config = &kx022a_regmap_config,
+ .channels = kx022a_channels,
+ .num_channels = ARRAY_SIZE(kx022a_channels),
+ .scale_table = kx022a_scale_table,
+ .scale_table_size = ARRAY_SIZE(kx022a_scale_table) *
+ ARRAY_SIZE(kx022a_scale_table[0]),
+ .fifo_length = KX022A_FIFO_LENGTH,
+ .who = KX022A_REG_WHO,
+ .id = KX132ACR_LBZ_ID,
+ .cntl = KX022A_REG_CNTL,
+ .cntl2 = KX022A_REG_CNTL2,
+ .odcntl = KX022A_REG_ODCNTL,
+ .buf_cntl1 = KX022A_REG_BUF_CNTL1,
+ .buf_cntl2 = KX022A_REG_BUF_CNTL2,
+ .buf_clear = KX022A_REG_BUF_CLEAR,
+ .buf_status1 = KX022A_REG_BUF_STATUS_1,
+ .buf_read = KX022A_REG_BUF_READ,
+ .inc1 = KX022A_REG_INC1,
+ .inc4 = KX022A_REG_INC4,
+ .inc5 = KX022A_REG_INC5,
+ .inc6 = KX022A_REG_INC6,
+ .xout_l = KX022A_REG_XOUT_L,
+ .get_fifo_bytes_available = kx022a_get_fifo_bytes_available,
+};
+EXPORT_SYMBOL_NS_GPL(kx132acr_chip_info, "IIO_KX022A");
+
+const struct kx022a_chip_info kx134acr_chip_info = {
+ .name = "kx134acr-lbz",
+ .regmap_config = &kx022a_regmap_config,
+ .channels = kx022a_channels,
+ .num_channels = ARRAY_SIZE(kx022a_channels),
+ .scale_table = kx134acr_lbz_scale_table,
+ .scale_table_size = ARRAY_SIZE(kx134acr_lbz_scale_table) *
+ ARRAY_SIZE(kx134acr_lbz_scale_table[0]),
+ .fifo_length = KX022A_FIFO_LENGTH,
+ .who = KX022A_REG_WHO,
+ .id = KX134ACR_LBZ_ID,
+ .cntl = KX022A_REG_CNTL,
+ .cntl2 = KX022A_REG_CNTL2,
+ .odcntl = KX022A_REG_ODCNTL,
+ .buf_cntl1 = KX022A_REG_BUF_CNTL1,
+ .buf_cntl2 = KX022A_REG_BUF_CNTL2,
+ .buf_clear = KX022A_REG_BUF_CLEAR,
+ .buf_status1 = KX022A_REG_BUF_STATUS_1,
+ .buf_read = KX022A_REG_BUF_READ,
+ .inc1 = KX022A_REG_INC1,
+ .inc4 = KX022A_REG_INC4,
+ .inc5 = KX022A_REG_INC5,
+ .inc6 = KX022A_REG_INC6,
+ .xout_l = KX022A_REG_XOUT_L,
+ .get_fifo_bytes_available = kx022a_get_fifo_bytes_available,
+};
+EXPORT_SYMBOL_NS_GPL(kx134acr_chip_info, "IIO_KX022A");
+
+int kx022a_probe_internal(struct device *dev, const struct kx022a_chip_info *chip_info)
+{
+ static const char * const regulator_names[] = {"io-vdd", "vdd"};
+ struct iio_trigger *indio_trig;
+ struct fwnode_handle *fwnode;
+ struct kx022a_data *data;
+ struct regmap *regmap;
+ unsigned int chip_id;
+ struct iio_dev *idev;
+ int ret, irq;
+ char *name;
+
+ regmap = dev_get_regmap(dev, NULL);
+ if (!regmap) {
+ dev_err(dev, "no regmap\n");
+ return -EINVAL;
+ }
+
+ fwnode = dev_fwnode(dev);
+ if (!fwnode)
+ return -ENODEV;
+
+ idev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!idev)
+ return -ENOMEM;
+
+ data = iio_priv(idev);
+ data->chip_info = chip_info;
+
+ /*
+ * VDD is the analog and digital domain voltage supply and
+ * IO_VDD is the digital I/O voltage supply.
+ */
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names),
+ regulator_names);
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to enable regulator\n");
+
+ ret = regmap_read(regmap, chip_info->who, &chip_id);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to access sensor\n");
+
+ if (chip_id != chip_info->id)
+ dev_warn(dev, "unknown device 0x%x\n", chip_id);
+
+ irq = fwnode_irq_get_byname(fwnode, "INT1");
+ if (irq > 0) {
+ data->inc_reg = chip_info->inc1;
+ data->ien_reg = chip_info->inc4;
+ } else {
+ irq = fwnode_irq_get_byname(fwnode, "INT2");
+ if (irq < 0)
+ return dev_err_probe(dev, irq, "No suitable IRQ\n");
+
+ data->inc_reg = chip_info->inc5;
+ data->ien_reg = chip_info->inc6;
+ }
+
+ data->regmap = regmap;
+ data->dev = dev;
+ data->irq = irq;
+ data->odr_ns = KX022A_DEFAULT_PERIOD_NS;
+ mutex_init(&data->mutex);
+
+ idev->channels = chip_info->channels;
+ idev->num_channels = chip_info->num_channels;
+ idev->name = chip_info->name;
+ idev->info = &kx022a_info;
+ idev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
+ idev->available_scan_masks = kx022a_scan_masks;
+
+ /* Read the mounting matrix, if present */
+ ret = iio_read_mount_matrix(dev, &data->orientation);
+ if (ret)
+ return ret;
+
+ /* The sensor must be turned off for configuration */
+ ret = kx022a_turn_off_lock(data);
+ if (ret)
+ return ret;
+
+ ret = kx022a_chip_init(data);
+ if (ret) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+
+ ret = kx022a_turn_on_unlock(data);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_triggered_buffer_setup_ext(dev, idev,
+ &iio_pollfunc_store_time,
+ kx022a_trigger_handler,
+ IIO_BUFFER_DIRECTION_IN,
+ &kx022a_buffer_ops,
+ kx022a_fifo_attributes);
+
+ if (ret)
+ return dev_err_probe(data->dev, ret,
+ "iio_triggered_buffer_setup_ext FAIL\n");
+ indio_trig = devm_iio_trigger_alloc(dev, "%sdata-rdy-dev%d", idev->name,
+ iio_device_id(idev));
+ if (!indio_trig)
+ return -ENOMEM;
+
+ data->trig = indio_trig;
+
+ indio_trig->ops = &kx022a_trigger_ops;
+ iio_trigger_set_drvdata(indio_trig, data);
+
+ /*
+ * No need to check for NULL. request_threaded_irq() defaults to
+ * dev_name() should the alloc fail.
+ */
+ name = devm_kasprintf(data->dev, GFP_KERNEL, "%s-kx022a",
+ dev_name(data->dev));
+
+ ret = devm_request_threaded_irq(data->dev, irq, kx022a_irq_handler,
+ &kx022a_irq_thread_handler,
+ IRQF_ONESHOT, name, idev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_trigger_register(dev, indio_trig);
+ if (ret)
+ return dev_err_probe(data->dev, ret,
+ "Trigger registration failed\n");
+
+ ret = devm_iio_device_register(data->dev, idev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Unable to register iio device\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(kx022a_probe_internal, "IIO_KX022A");
+
+MODULE_DESCRIPTION("ROHM/Kionix KX022A accelerometer driver");
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/kionix-kx022a.h b/drivers/iio/accel/kionix-kx022a.h
new file mode 100644
index 000000000..0ed54f584
--- /dev/null
+++ b/drivers/iio/accel/kionix-kx022a.h
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022 ROHM Semiconductors
+ *
+ * ROHM/KIONIX KX022A accelerometer driver
+ */
+
+#ifndef _KX022A_H_
+#define _KX022A_H_
+
+#include <linux/bits.h>
+#include <linux/regmap.h>
+
+#define KX022A_REG_WHO 0x0f
+#define KX022A_ID 0xc8
+#define KX132ACR_LBZ_ID 0xd8
+#define KX134ACR_LBZ_ID 0xcc
+
+#define KX022A_REG_CNTL2 0x19
+#define KX022A_MASK_SRST BIT(7)
+#define KX022A_REG_CNTL 0x18
+#define KX022A_MASK_PC1 BIT(7)
+#define KX022A_MASK_RES BIT(6)
+#define KX022A_MASK_DRDY BIT(5)
+#define KX022A_MASK_GSEL GENMASK(4, 3)
+#define KX022A_GSEL_SHIFT 3
+#define KX022A_GSEL_2 0x0
+#define KX022A_GSEL_4 BIT(3)
+#define KX022A_GSEL_8 BIT(4)
+#define KX022A_GSEL_16 GENMASK(4, 3)
+
+#define KX022A_REG_INS2 0x13
+#define KX022A_MASK_INS2_DRDY BIT(4)
+#define KX122_MASK_INS2_WMI BIT(5)
+
+#define KX022A_REG_XHP_L 0x0
+#define KX022A_REG_XOUT_L 0x06
+#define KX022A_REG_YOUT_L 0x08
+#define KX022A_REG_ZOUT_L 0x0a
+#define KX022A_REG_COTR 0x0c
+#define KX022A_REG_TSCP 0x10
+#define KX022A_REG_INT_REL 0x17
+
+#define KX022A_REG_ODCNTL 0x1b
+
+#define KX022A_REG_BTS_WUF_TH 0x31
+#define KX022A_REG_MAN_WAKE 0x2c
+
+#define KX022A_REG_BUF_CNTL1 0x3a
+#define KX022A_MASK_WM_TH GENMASK(6, 0)
+#define KX022A_REG_BUF_CNTL2 0x3b
+#define KX022A_MASK_BUF_EN BIT(7)
+#define KX022A_MASK_BRES16 BIT(6)
+#define KX022A_REG_BUF_STATUS_1 0x3c
+#define KX022A_REG_BUF_STATUS_2 0x3d
+#define KX022A_REG_BUF_CLEAR 0x3e
+#define KX022A_REG_BUF_READ 0x3f
+#define KX022A_MASK_ODR GENMASK(3, 0)
+#define KX022A_ODR_SHIFT 3
+#define KX022A_FIFO_MAX_WMI_TH 41
+
+#define KX022A_REG_INC1 0x1c
+#define KX022A_REG_INC5 0x20
+#define KX022A_REG_INC6 0x21
+#define KX022A_MASK_IEN BIT(5)
+#define KX022A_MASK_IPOL BIT(4)
+#define KX022A_IPOL_LOW 0
+#define KX022A_IPOL_HIGH KX022A_MASK_IPOL1
+#define KX022A_MASK_ITYP BIT(3)
+#define KX022A_ITYP_PULSE KX022A_MASK_ITYP
+#define KX022A_ITYP_LEVEL 0
+
+#define KX022A_REG_INC4 0x1f
+#define KX022A_MASK_WMI BIT(5)
+
+#define KX022A_REG_SELF_TEST 0x60
+#define KX022A_MAX_REGISTER 0x60
+
+#define KX132_REG_WHO 0x13
+#define KX132_ID 0x3d
+#define KX134_1211_ID 0x46
+
+#define KX132_FIFO_LENGTH 86
+
+#define KX132_REG_CNTL 0x1b
+#define KX132_REG_CNTL2 0x1c
+#define KX132_REG_CNTL5 0x1f
+#define KX132_MASK_RES BIT(6)
+#define KX132_GSEL_2 0x0
+#define KX132_GSEL_4 BIT(3)
+#define KX132_GSEL_8 BIT(4)
+#define KX132_GSEL_16 GENMASK(4, 3)
+
+#define KX132_REG_INS2 0x17
+#define KX132_MASK_INS2_WMI BIT(5)
+
+#define KX132_REG_XADP_L 0x02
+#define KX132_REG_XOUT_L 0x08
+#define KX132_REG_YOUT_L 0x0a
+#define KX132_REG_ZOUT_L 0x0c
+#define KX132_REG_COTR 0x12
+#define KX132_REG_TSCP 0x14
+#define KX132_REG_INT_REL 0x1a
+
+#define KX132_REG_ODCNTL 0x21
+
+#define KX132_REG_BTS_WUF_TH 0x4a
+
+#define KX132_REG_BUF_CNTL1 0x5e
+#define KX132_REG_BUF_CNTL2 0x5f
+#define KX132_REG_BUF_STATUS_1 0x60
+#define KX132_REG_BUF_STATUS_2 0x61
+#define KX132_MASK_BUF_SMP_LVL GENMASK(9, 0)
+#define KX132_REG_BUF_CLEAR 0x62
+#define KX132_REG_BUF_READ 0x63
+#define KX132_ODR_SHIFT 3
+#define KX132_FIFO_MAX_WMI_TH 86
+
+#define KX132_REG_INC1 0x22
+#define KX132_REG_INC5 0x26
+#define KX132_REG_INC6 0x27
+#define KX132_IPOL_LOW 0
+#define KX132_IPOL_HIGH KX022A_MASK_IPOL
+#define KX132_ITYP_PULSE KX022A_MASK_ITYP
+
+#define KX132_REG_INC4 0x25
+
+#define KX132_REG_SELF_TEST 0x5d
+#define KX132_MAX_REGISTER 0x76
+
+struct device;
+
+struct kx022a_data;
+
+/**
+ * struct kx022a_chip_info - Kionix accelerometer chip specific information
+ *
+ * @name: name of the device
+ * @regmap_config: pointer to register map configuration
+ * @scale_table: An array of tables of scaling factors for
+ * a supported acceleration measurement range.
+ * Each table containing a single scaling
+ * factor consisting of two integers. The first
+ * value in a table is the integer part, and
+ * the second value is the fractional part as
+ * parts per billion.
+ * @scale_table_size: Amount of values in tables.
+ * @channels: pointer to iio_chan_spec array
+ * @num_channels: number of iio_chan_spec channels
+ * @fifo_length: number of 16-bit samples in a full buffer
+ * @buf_smp_lvl_mask: buffer sample level mask
+ * @who: WHO_AM_I register
+ * @id: WHO_AM_I register value
+ * @cntl: control register 1
+ * @cntl2: control register 2
+ * @odcntl: output data control register
+ * @buf_cntl1: buffer control register 1
+ * @buf_cntl2: buffer control register 2
+ * @buf_clear: buffer clear register
+ * @buf_status1: buffer status register 1
+ * @buf_read: buffer read register
+ * @inc1: interrupt control register 1
+ * @inc4: interrupt control register 4
+ * @inc5: interrupt control register 5
+ * @inc6: interrupt control register 6
+ * @xout_l: x-axis output least significant byte
+ * @get_fifo_bytes_available: function pointer to get amount of acceleration
+ * data bytes currently stored in the sensor's FIFO
+ * buffer
+ */
+struct kx022a_chip_info {
+ const char *name;
+ const struct regmap_config *regmap_config;
+ const int (*scale_table)[2];
+ const int scale_table_size;
+ const struct iio_chan_spec *channels;
+ unsigned int num_channels;
+ unsigned int fifo_length;
+ u16 buf_smp_lvl_mask;
+ u8 who;
+ u8 id;
+ u8 cntl;
+ u8 cntl2;
+ u8 odcntl;
+ u8 buf_cntl1;
+ u8 buf_cntl2;
+ u8 buf_clear;
+ u8 buf_status1;
+ u8 buf_read;
+ u8 inc1;
+ u8 inc4;
+ u8 inc5;
+ u8 inc6;
+ u8 xout_l;
+ int (*get_fifo_bytes_available)(struct kx022a_data *);
+};
+
+int kx022a_probe_internal(struct device *dev, const struct kx022a_chip_info *chip_info);
+
+extern const struct kx022a_chip_info kx022a_chip_info;
+extern const struct kx022a_chip_info kx132_chip_info;
+extern const struct kx022a_chip_info kx134_chip_info;
+extern const struct kx022a_chip_info kx132acr_chip_info;
+extern const struct kx022a_chip_info kx134acr_chip_info;
+
+#endif
diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c
new file mode 100644
index 000000000..166fb7864
--- /dev/null
+++ b/drivers/iio/accel/kxcjk-1013.c
@@ -0,0 +1,1684 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KXCJK-1013 3-axis accelerometer driver
+ * Copyright (c) 2014, Intel Corporation.
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/events.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/accel/kxcjk_1013.h>
+
+#define KXTF9_REG_HP_XOUT_L 0x00
+#define KXTF9_REG_HP_XOUT_H 0x01
+#define KXTF9_REG_HP_YOUT_L 0x02
+#define KXTF9_REG_HP_YOUT_H 0x03
+#define KXTF9_REG_HP_ZOUT_L 0x04
+#define KXTF9_REG_HP_ZOUT_H 0x05
+
+#define KXCJK1013_REG_XOUT_L 0x06
+/*
+ * From low byte X axis register, all the other addresses of Y and Z can be
+ * obtained by just applying axis offset. The following axis defines are just
+ * provide clarity, but not used.
+ */
+#define KXCJK1013_REG_XOUT_H 0x07
+#define KXCJK1013_REG_YOUT_L 0x08
+#define KXCJK1013_REG_YOUT_H 0x09
+#define KXCJK1013_REG_ZOUT_L 0x0A
+#define KXCJK1013_REG_ZOUT_H 0x0B
+
+#define KXCJK1013_REG_DCST_RESP 0x0C
+#define KXCJK1013_REG_WHO_AM_I 0x0F
+#define KXTF9_REG_TILT_POS_CUR 0x10
+#define KXTF9_REG_TILT_POS_PREV 0x11
+#define KXTF9_REG_INT_SRC1 0x15
+#define KXTF9_REG_INT_SRC2 0x16
+#define KXCJK1013_REG_INT_SRC1 0x16
+#define KXCJK1013_REG_INT_SRC2 0x17
+#define KXCJK1013_REG_STATUS_REG 0x18
+#define KXCJK1013_REG_INT_REL 0x1A
+#define KXCJK1013_REG_CTRL1 0x1B
+#define KXTF9_REG_CTRL2 0x1C
+#define KXTF9_REG_CTRL3 0x1D
+#define KXCJK1013_REG_CTRL2 0x1D
+#define KXCJK1013_REG_INT_CTRL1 0x1E
+#define KXCJK1013_REG_INT_CTRL2 0x1F
+#define KXTF9_REG_INT_CTRL3 0x20
+#define KXCJK1013_REG_DATA_CTRL 0x21
+#define KXTF9_REG_TILT_TIMER 0x28
+#define KXCJK1013_REG_WAKE_TIMER 0x29
+#define KXTF9_REG_TDT_TIMER 0x2B
+#define KXTF9_REG_TDT_THRESH_H 0x2C
+#define KXTF9_REG_TDT_THRESH_L 0x2D
+#define KXTF9_REG_TDT_TAP_TIMER 0x2E
+#define KXTF9_REG_TDT_TOTAL_TIMER 0x2F
+#define KXTF9_REG_TDT_LATENCY_TIMER 0x30
+#define KXTF9_REG_TDT_WINDOW_TIMER 0x31
+#define KXCJK1013_REG_SELF_TEST 0x3A
+#define KXTF9_REG_WAKE_THRESH 0x5A
+#define KXTF9_REG_TILT_ANGLE 0x5C
+#define KXTF9_REG_HYST_SET 0x5F
+#define KXCJK1013_REG_WAKE_THRES 0x6A
+
+/* Everything up to 0x11 is equal to KXCJK1013/KXTF9 above */
+#define KX023_REG_INS1 0x12
+#define KX023_REG_INS2 0x13
+#define KX023_REG_INS3 0x14
+#define KX023_REG_STAT 0x15
+#define KX023_REG_INT_REL 0x17
+#define KX023_REG_CNTL1 0x18
+#define KX023_REG_CNTL2 0x19
+#define KX023_REG_CNTL3 0x1A
+#define KX023_REG_ODCNTL 0x1B
+#define KX023_REG_INC1 0x1C
+#define KX023_REG_INC2 0x1D
+#define KX023_REG_INC3 0x1E
+#define KX023_REG_INC4 0x1F
+#define KX023_REG_INC5 0x20
+#define KX023_REG_INC6 0x21
+#define KX023_REG_TILT_TIMER 0x22
+#define KX023_REG_WUFC 0x23
+#define KX023_REG_TDTRC 0x24
+#define KX023_REG_TDTC 0x25
+#define KX023_REG_TTH 0x26
+#define KX023_REG_TTL 0x27
+#define KX023_REG_FTD 0x28
+#define KX023_REG_STD 0x29
+#define KX023_REG_TLT 0x2A
+#define KX023_REG_TWS 0x2B
+#define KX023_REG_ATH 0x30
+#define KX023_REG_TILT_ANGLE_LL 0x32
+#define KX023_REG_TILT_ANGLE_HL 0x33
+#define KX023_REG_HYST_SET 0x34
+#define KX023_REG_LP_CNTL 0x35
+#define KX023_REG_BUF_CNTL1 0x3A
+#define KX023_REG_BUF_CNTL2 0x3B
+#define KX023_REG_BUF_STATUS_1 0x3C
+#define KX023_REG_BUF_STATUS_2 0x3D
+#define KX023_REG_BUF_CLEAR 0x3E
+#define KX023_REG_BUF_READ 0x3F
+#define KX023_REG_SELF_TEST 0x60
+
+#define KXCJK1013_REG_CTRL1_BIT_PC1 BIT(7)
+#define KXCJK1013_REG_CTRL1_BIT_RES BIT(6)
+#define KXCJK1013_REG_CTRL1_BIT_DRDY BIT(5)
+#define KXCJK1013_REG_CTRL1_BIT_GSEL1 BIT(4)
+#define KXCJK1013_REG_CTRL1_BIT_GSEL0 BIT(3)
+#define KXCJK1013_REG_CTRL1_BIT_WUFE BIT(1)
+
+#define KXCJK1013_REG_INT_CTRL1_BIT_IEU BIT(2) /* KXTF9 */
+#define KXCJK1013_REG_INT_CTRL1_BIT_IEL BIT(3)
+#define KXCJK1013_REG_INT_CTRL1_BIT_IEA BIT(4)
+#define KXCJK1013_REG_INT_CTRL1_BIT_IEN BIT(5)
+
+#define KXTF9_REG_TILT_BIT_LEFT_EDGE BIT(5)
+#define KXTF9_REG_TILT_BIT_RIGHT_EDGE BIT(4)
+#define KXTF9_REG_TILT_BIT_LOWER_EDGE BIT(3)
+#define KXTF9_REG_TILT_BIT_UPPER_EDGE BIT(2)
+#define KXTF9_REG_TILT_BIT_FACE_DOWN BIT(1)
+#define KXTF9_REG_TILT_BIT_FACE_UP BIT(0)
+
+#define KXCJK1013_DATA_MASK_12_BIT 0x0FFF
+#define KXCJK1013_MAX_STARTUP_TIME_US 100000
+
+#define KXCJK1013_SLEEP_DELAY_MS 2000
+
+#define KXCJK1013_REG_INT_SRC1_BIT_TPS BIT(0) /* KXTF9 */
+#define KXCJK1013_REG_INT_SRC1_BIT_WUFS BIT(1)
+#define KXCJK1013_REG_INT_SRC1_MASK_TDTS (BIT(2) | BIT(3)) /* KXTF9 */
+#define KXCJK1013_REG_INT_SRC1_TAP_NONE 0
+#define KXCJK1013_REG_INT_SRC1_TAP_SINGLE BIT(2)
+#define KXCJK1013_REG_INT_SRC1_TAP_DOUBLE BIT(3)
+#define KXCJK1013_REG_INT_SRC1_BIT_DRDY BIT(4)
+
+/* KXCJK: INT_SOURCE2: motion detect, KXTF9: INT_SRC_REG1: tap detect */
+#define KXCJK1013_REG_INT_SRC2_BIT_ZP BIT(0)
+#define KXCJK1013_REG_INT_SRC2_BIT_ZN BIT(1)
+#define KXCJK1013_REG_INT_SRC2_BIT_YP BIT(2)
+#define KXCJK1013_REG_INT_SRC2_BIT_YN BIT(3)
+#define KXCJK1013_REG_INT_SRC2_BIT_XP BIT(4)
+#define KXCJK1013_REG_INT_SRC2_BIT_XN BIT(5)
+
+/* KX023 interrupt routing to INT1. INT2 can be configured with INC6 */
+#define KX023_REG_INC4_BFI1 BIT(6)
+#define KX023_REG_INC4_WMI1 BIT(5)
+#define KX023_REG_INC4_DRDY1 BIT(4)
+#define KX023_REG_INC4_TDTI1 BIT(2)
+#define KX023_REG_INC4_WUFI1 BIT(1)
+#define KX023_REG_INC4_TPI1 BIT(0)
+
+#define KXCJK1013_DEFAULT_WAKE_THRES 1
+
+/* Refer to section 4 of the specification */
+struct kx_odr_start_up_time {
+ int odr_bits;
+ int usec;
+};
+
+/* KXCJK-1013 */
+static const struct kx_odr_start_up_time kxcjk1013_odr_start_up_times[] = {
+ { 0x08, 100000 },
+ { 0x09, 100000 },
+ { 0x0A, 100000 },
+ { 0x0B, 100000 },
+ { 0x00, 80000 },
+ { 0x01, 41000 },
+ { 0x02, 21000 },
+ { 0x03, 11000 },
+ { 0x04, 6400 },
+ { 0x05, 3900 },
+ { 0x06, 2700 },
+ { 0x07, 2100 },
+ { }
+};
+
+/* KXCTJ2-1009 */
+static const struct kx_odr_start_up_time kxtj21009_odr_start_up_times[] = {
+ { 0x08, 1240000 },
+ { 0x09, 621000 },
+ { 0x0A, 309000 },
+ { 0x0B, 151000 },
+ { 0x00, 80000 },
+ { 0x01, 41000 },
+ { 0x02, 21000 },
+ { 0x03, 11000 },
+ { 0x04, 6000 },
+ { 0x05, 4000 },
+ { 0x06, 3000 },
+ { 0x07, 2000 },
+ { }
+};
+
+/* KXTF9 */
+static const struct kx_odr_start_up_time kxtf9_odr_start_up_times[] = {
+ { 0x01, 81000 },
+ { 0x02, 41000 },
+ { 0x03, 21000 },
+ { 0x04, 11000 },
+ { 0x05, 5100 },
+ { 0x06, 2700 },
+ { }
+};
+
+/* KX023-1025 */
+static const struct kx_odr_start_up_time kx0231025_odr_start_up_times[] = {
+ /* First 4 are not in datasheet, taken from KXCTJ2-1009 */
+ { 0x08, 1240000 },
+ { 0x09, 621000 },
+ { 0x0A, 309000 },
+ { 0x0B, 151000 },
+ { 0x00, 81000 },
+ { 0x01, 40000 },
+ { 0x02, 22000 },
+ { 0x03, 12000 },
+ { 0x04, 7000 },
+ { 0x05, 4400 },
+ { 0x06, 3000 },
+ { 0x07, 3000 },
+ { }
+};
+
+enum kx_acpi_type {
+ ACPI_GENERIC,
+ ACPI_SMO8500,
+ ACPI_KIOX010A,
+};
+
+struct kx_chipset_regs {
+ u8 int_src1;
+ u8 int_src2;
+ u8 int_rel;
+ u8 ctrl1;
+ u8 wuf_ctrl;
+ u8 int_ctrl1;
+ u8 data_ctrl;
+ u8 wake_timer;
+ u8 wake_thres;
+};
+
+static const struct kx_chipset_regs kxcjk1013_regs = {
+ .int_src1 = KXCJK1013_REG_INT_SRC1,
+ .int_src2 = KXCJK1013_REG_INT_SRC2,
+ .int_rel = KXCJK1013_REG_INT_REL,
+ .ctrl1 = KXCJK1013_REG_CTRL1,
+ .wuf_ctrl = KXCJK1013_REG_CTRL2,
+ .int_ctrl1 = KXCJK1013_REG_INT_CTRL1,
+ .data_ctrl = KXCJK1013_REG_DATA_CTRL,
+ .wake_timer = KXCJK1013_REG_WAKE_TIMER,
+ .wake_thres = KXCJK1013_REG_WAKE_THRES,
+};
+
+static const struct kx_chipset_regs kxtf9_regs = {
+ /* .int_src1 was moved to INT_SRC2 on KXTF9 */
+ .int_src1 = KXTF9_REG_INT_SRC2,
+ /* .int_src2 is not available */
+ .int_rel = KXCJK1013_REG_INT_REL,
+ .ctrl1 = KXCJK1013_REG_CTRL1,
+ .wuf_ctrl = KXTF9_REG_CTRL3,
+ .int_ctrl1 = KXCJK1013_REG_INT_CTRL1,
+ .data_ctrl = KXCJK1013_REG_DATA_CTRL,
+ .wake_timer = KXCJK1013_REG_WAKE_TIMER,
+ .wake_thres = KXTF9_REG_WAKE_THRESH,
+};
+
+/* The registers have totally different names but the bits are compatible */
+static const struct kx_chipset_regs kx0231025_regs = {
+ .int_src1 = KX023_REG_INS2,
+ .int_src2 = KX023_REG_INS3,
+ .int_rel = KX023_REG_INT_REL,
+ .ctrl1 = KX023_REG_CNTL1,
+ .wuf_ctrl = KX023_REG_CNTL3,
+ .int_ctrl1 = KX023_REG_INC1,
+ .data_ctrl = KX023_REG_ODCNTL,
+ .wake_timer = KX023_REG_WUFC,
+ .wake_thres = KX023_REG_ATH,
+};
+
+struct kx_chipset_info {
+ const struct kx_chipset_regs *regs;
+ const struct kx_odr_start_up_time *times;
+ enum kx_acpi_type acpi_type;
+};
+
+static const struct kx_chipset_info kxcjk1013_info = {
+ .regs = &kxcjk1013_regs,
+ .times = pm_ptr(kxcjk1013_odr_start_up_times),
+};
+
+static const struct kx_chipset_info kxcj91008_info = {
+ .regs = &kxcjk1013_regs,
+ .times = pm_ptr(kxcjk1013_odr_start_up_times),
+};
+
+static const struct kx_chipset_info kxcj91008_kiox010a_info = {
+ .regs = &kxcjk1013_regs,
+ .times = pm_ptr(kxcjk1013_odr_start_up_times),
+ .acpi_type = ACPI_KIOX010A,
+};
+
+static const struct kx_chipset_info kxcj91008_kiox020a_info = {
+ .regs = &kxcjk1013_regs,
+ .times = pm_ptr(kxcjk1013_odr_start_up_times),
+ .acpi_type = ACPI_GENERIC,
+};
+
+static const struct kx_chipset_info kxcj91008_smo8500_info = {
+ .regs = &kxcjk1013_regs,
+ .times = pm_ptr(kxcjk1013_odr_start_up_times),
+ .acpi_type = ACPI_SMO8500,
+};
+
+static const struct kx_chipset_info kxtj21009_info = {
+ .regs = &kxcjk1013_regs,
+ .times = pm_ptr(kxtj21009_odr_start_up_times),
+};
+
+static const struct kx_chipset_info kxtf9_info = {
+ .regs = &kxtf9_regs,
+ .times = pm_ptr(kxtf9_odr_start_up_times),
+};
+
+static const struct kx_chipset_info kx0231025_info = {
+ .regs = &kx0231025_regs,
+ .times = pm_ptr(kx0231025_odr_start_up_times),
+};
+
+enum kxcjk1013_axis {
+ AXIS_X,
+ AXIS_Y,
+ AXIS_Z,
+ AXIS_MAX
+};
+
+struct kxcjk1013_data {
+ struct i2c_client *client;
+ struct iio_trigger *dready_trig;
+ struct iio_trigger *motion_trig;
+ struct iio_mount_matrix orientation;
+ struct mutex mutex;
+ /* Ensure timestamp naturally aligned */
+ struct {
+ s16 chans[AXIS_MAX];
+ aligned_s64 timestamp;
+ } scan;
+ u8 odr_bits;
+ u8 range;
+ int wake_thres;
+ int wake_dur;
+ bool active_high_intr;
+ bool dready_trigger_on;
+ int ev_enable_state;
+ bool motion_trigger_on;
+ int64_t timestamp;
+ const struct kx_chipset_info *info;
+};
+
+enum kxcjk1013_mode {
+ STANDBY,
+ OPERATION,
+};
+
+enum kxcjk1013_range {
+ KXCJK1013_RANGE_2G,
+ KXCJK1013_RANGE_4G,
+ KXCJK1013_RANGE_8G,
+};
+
+struct kx_odr_map {
+ int val;
+ int val2;
+ int odr_bits;
+ int wuf_bits;
+};
+
+static const struct kx_odr_map samp_freq_table[] = {
+ { 0, 781000, 0x08, 0x00 },
+ { 1, 563000, 0x09, 0x01 },
+ { 3, 125000, 0x0A, 0x02 },
+ { 6, 250000, 0x0B, 0x03 },
+ { 12, 500000, 0x00, 0x04 },
+ { 25, 0, 0x01, 0x05 },
+ { 50, 0, 0x02, 0x06 },
+ { 100, 0, 0x03, 0x06 },
+ { 200, 0, 0x04, 0x06 },
+ { 400, 0, 0x05, 0x06 },
+ { 800, 0, 0x06, 0x06 },
+ { 1600, 0, 0x07, 0x06 },
+};
+
+static const char *const kxcjk1013_samp_freq_avail =
+ "0.781000 1.563000 3.125000 6.250000 12.500000 25 50 100 200 400 800 1600";
+
+static const struct kx_odr_map kxtf9_samp_freq_table[] = {
+ { 25, 0, 0x01, 0x00 },
+ { 50, 0, 0x02, 0x01 },
+ { 100, 0, 0x03, 0x01 },
+ { 200, 0, 0x04, 0x01 },
+ { 400, 0, 0x05, 0x01 },
+ { 800, 0, 0x06, 0x01 },
+};
+
+static const char *const kxtf9_samp_freq_avail =
+ "25 50 100 200 400 800";
+
+static const struct {
+ u16 scale;
+ u8 gsel_0;
+ u8 gsel_1;
+} KXCJK1013_scale_table[] = { {9582, 0, 0},
+ {19163, 1, 0},
+ {38326, 0, 1} };
+
+#ifdef CONFIG_ACPI
+enum kiox010a_fn_index {
+ KIOX010A_SET_LAPTOP_MODE = 1,
+ KIOX010A_SET_TABLET_MODE = 2,
+};
+
+static int kiox010a_dsm(struct device *dev, int fn_index)
+{
+ acpi_handle handle = ACPI_HANDLE(dev);
+ guid_t kiox010a_dsm_guid;
+ union acpi_object *obj;
+
+ if (!handle)
+ return -ENODEV;
+
+ guid_parse("1f339696-d475-4e26-8cad-2e9f8e6d7a91", &kiox010a_dsm_guid);
+
+ obj = acpi_evaluate_dsm(handle, &kiox010a_dsm_guid, 1, fn_index, NULL);
+ if (!obj)
+ return -EIO;
+
+ ACPI_FREE(obj);
+ return 0;
+}
+
+#endif
+
+static int kxcjk1013_set_mode(struct kxcjk1013_data *data,
+ enum kxcjk1013_mode mode)
+{
+ const struct kx_chipset_regs *regs = data->info->regs;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, regs->ctrl1);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_ctrl1\n");
+ return ret;
+ }
+
+ if (mode == STANDBY)
+ ret &= ~KXCJK1013_REG_CTRL1_BIT_PC1;
+ else
+ ret |= KXCJK1013_REG_CTRL1_BIT_PC1;
+
+ ret = i2c_smbus_write_byte_data(data->client, regs->ctrl1, ret);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error writing reg_ctrl1\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int kxcjk1013_get_mode(struct kxcjk1013_data *data,
+ enum kxcjk1013_mode *mode)
+{
+ const struct kx_chipset_regs *regs = data->info->regs;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, regs->ctrl1);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_ctrl1\n");
+ return ret;
+ }
+
+ if (ret & KXCJK1013_REG_CTRL1_BIT_PC1)
+ *mode = OPERATION;
+ else
+ *mode = STANDBY;
+
+ return 0;
+}
+
+static int kxcjk1013_set_range(struct kxcjk1013_data *data, int range_index)
+{
+ const struct kx_chipset_regs *regs = data->info->regs;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, regs->ctrl1);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_ctrl1\n");
+ return ret;
+ }
+
+ ret &= ~(KXCJK1013_REG_CTRL1_BIT_GSEL0 |
+ KXCJK1013_REG_CTRL1_BIT_GSEL1);
+ ret |= (KXCJK1013_scale_table[range_index].gsel_0 << 3);
+ ret |= (KXCJK1013_scale_table[range_index].gsel_1 << 4);
+
+ ret = i2c_smbus_write_byte_data(data->client, regs->ctrl1, ret);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error writing reg_ctrl1\n");
+ return ret;
+ }
+
+ data->range = range_index;
+
+ return 0;
+}
+
+static int kxcjk1013_chip_init(struct kxcjk1013_data *data)
+{
+ const struct kx_chipset_regs *regs = data->info->regs;
+ int ret;
+
+#ifdef CONFIG_ACPI
+ if (data->info->acpi_type == ACPI_KIOX010A) {
+ /* Make sure the kbd and touchpad on 2-in-1s using 2 KXCJ91008-s work */
+ kiox010a_dsm(&data->client->dev, KIOX010A_SET_LAPTOP_MODE);
+ }
+#endif
+
+ ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_WHO_AM_I);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading who_am_i\n");
+ return ret;
+ }
+
+ dev_dbg(&data->client->dev, "KXCJK1013 Chip Id %x\n", ret);
+
+ ret = kxcjk1013_set_mode(data, STANDBY);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, regs->ctrl1);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_ctrl1\n");
+ return ret;
+ }
+
+ /* Set 12 bit mode */
+ ret |= KXCJK1013_REG_CTRL1_BIT_RES;
+
+ ret = i2c_smbus_write_byte_data(data->client, regs->ctrl1, ret);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_ctrl\n");
+ return ret;
+ }
+
+ /* Setting range to 4G */
+ ret = kxcjk1013_set_range(data, KXCJK1013_RANGE_4G);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, regs->data_ctrl);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_data_ctrl\n");
+ return ret;
+ }
+
+ data->odr_bits = ret;
+
+ /* Set up INT polarity */
+ ret = i2c_smbus_read_byte_data(data->client, regs->int_ctrl1);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_int_ctrl1\n");
+ return ret;
+ }
+
+ if (data->active_high_intr)
+ ret |= KXCJK1013_REG_INT_CTRL1_BIT_IEA;
+ else
+ ret &= ~KXCJK1013_REG_INT_CTRL1_BIT_IEA;
+
+ ret = i2c_smbus_write_byte_data(data->client, regs->int_ctrl1, ret);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error writing reg_int_ctrl1\n");
+ return ret;
+ }
+
+ /* On KX023, route all used interrupts to INT1 for now */
+ if (data->info == &kx0231025_info && data->client->irq > 0) {
+ ret = i2c_smbus_write_byte_data(data->client, KX023_REG_INC4,
+ KX023_REG_INC4_DRDY1 |
+ KX023_REG_INC4_WUFI1);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error writing reg_inc4\n");
+ return ret;
+ }
+ }
+
+ ret = kxcjk1013_set_mode(data, OPERATION);
+ if (ret < 0)
+ return ret;
+
+ data->wake_thres = KXCJK1013_DEFAULT_WAKE_THRES;
+
+ return 0;
+}
+
+static int kxcjk1013_get_startup_times(struct kxcjk1013_data *data)
+{
+ const struct kx_odr_start_up_time *times;
+
+ for (times = data->info->times; times->usec; times++) {
+ if (times->odr_bits == data->odr_bits)
+ return times->usec;
+ }
+
+ return KXCJK1013_MAX_STARTUP_TIME_US;
+}
+
+static int kxcjk1013_set_power_state(struct kxcjk1013_data *data, bool on)
+{
+#ifdef CONFIG_PM
+ int ret;
+
+ if (on)
+ ret = pm_runtime_resume_and_get(&data->client->dev);
+ else
+ ret = pm_runtime_put_autosuspend(&data->client->dev);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed: %s for %d\n", __func__, on);
+ return ret;
+ }
+#endif
+
+ return 0;
+}
+
+static int kxcjk1013_chip_update_thresholds(struct kxcjk1013_data *data)
+{
+ const struct kx_chipset_regs *regs = data->info->regs;
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(data->client, regs->wake_timer, data->wake_dur);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Error writing reg_wake_timer\n");
+ return ret;
+ }
+
+ ret = i2c_smbus_write_byte_data(data->client, regs->wake_thres, data->wake_thres);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error writing reg_wake_thres\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int kxcjk1013_setup_interrupt(struct kxcjk1013_data *data,
+ bool status, bool is_new_data)
+{
+ const struct kx_chipset_regs *regs = data->info->regs;
+ int ret;
+ enum kxcjk1013_mode store_mode;
+
+ ret = kxcjk1013_get_mode(data, &store_mode);
+ if (ret < 0)
+ return ret;
+
+ /* This is requirement by spec to change state to STANDBY */
+ ret = kxcjk1013_set_mode(data, STANDBY);
+ if (ret < 0)
+ return ret;
+
+ if (is_new_data == true) {
+ ret = kxcjk1013_chip_update_thresholds(data);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = i2c_smbus_read_byte_data(data->client, regs->int_ctrl1);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_int_ctrl1\n");
+ return ret;
+ }
+
+ if (status)
+ ret |= KXCJK1013_REG_INT_CTRL1_BIT_IEN;
+ else
+ ret &= ~KXCJK1013_REG_INT_CTRL1_BIT_IEN;
+
+ ret = i2c_smbus_write_byte_data(data->client, regs->int_ctrl1, ret);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error writing reg_int_ctrl1\n");
+ return ret;
+ }
+
+ ret = i2c_smbus_read_byte_data(data->client, regs->ctrl1);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_ctrl1\n");
+ return ret;
+ }
+
+ if (is_new_data) {
+ if (status)
+ ret |= KXCJK1013_REG_CTRL1_BIT_DRDY;
+ else
+ ret &= ~KXCJK1013_REG_CTRL1_BIT_DRDY;
+ } else {
+ if (status)
+ ret |= KXCJK1013_REG_CTRL1_BIT_WUFE;
+ else
+ ret &= ~KXCJK1013_REG_CTRL1_BIT_WUFE;
+ }
+
+ ret = i2c_smbus_write_byte_data(data->client, regs->ctrl1, ret);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error writing reg_ctrl1\n");
+ return ret;
+ }
+
+ if (store_mode == OPERATION) {
+ ret = kxcjk1013_set_mode(data, OPERATION);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct kx_odr_map *kxcjk1013_find_odr_value(
+ const struct kx_odr_map *map, size_t map_size, int val, int val2)
+{
+ int i;
+
+ for (i = 0; i < map_size; ++i) {
+ if (map[i].val == val && map[i].val2 == val2)
+ return &map[i];
+ }
+
+ return ERR_PTR(-EINVAL);
+}
+
+static int kxcjk1013_convert_odr_value(const struct kx_odr_map *map,
+ size_t map_size, int odr_bits,
+ int *val, int *val2)
+{
+ int i;
+
+ for (i = 0; i < map_size; ++i) {
+ if (map[i].odr_bits == odr_bits) {
+ *val = map[i].val;
+ *val2 = map[i].val2;
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int kxcjk1013_set_odr(struct kxcjk1013_data *data, int val, int val2)
+{
+ const struct kx_chipset_regs *regs = data->info->regs;
+ int ret;
+ enum kxcjk1013_mode store_mode;
+ const struct kx_odr_map *odr_setting;
+
+ ret = kxcjk1013_get_mode(data, &store_mode);
+ if (ret < 0)
+ return ret;
+
+ if (data->info == &kxtf9_info)
+ odr_setting = kxcjk1013_find_odr_value(kxtf9_samp_freq_table,
+ ARRAY_SIZE(kxtf9_samp_freq_table),
+ val, val2);
+ else
+ odr_setting = kxcjk1013_find_odr_value(samp_freq_table,
+ ARRAY_SIZE(samp_freq_table),
+ val, val2);
+
+ if (IS_ERR(odr_setting))
+ return PTR_ERR(odr_setting);
+
+ /* To change ODR, the chip must be set to STANDBY as per spec */
+ ret = kxcjk1013_set_mode(data, STANDBY);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_byte_data(data->client, regs->data_ctrl,
+ odr_setting->odr_bits);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error writing data_ctrl\n");
+ return ret;
+ }
+
+ data->odr_bits = odr_setting->odr_bits;
+
+ ret = i2c_smbus_write_byte_data(data->client, regs->wuf_ctrl,
+ odr_setting->wuf_bits);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error writing reg_ctrl2\n");
+ return ret;
+ }
+
+ if (store_mode == OPERATION) {
+ ret = kxcjk1013_set_mode(data, OPERATION);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int kxcjk1013_get_odr(struct kxcjk1013_data *data, int *val, int *val2)
+{
+ if (data->info == &kxtf9_info)
+ return kxcjk1013_convert_odr_value(kxtf9_samp_freq_table,
+ ARRAY_SIZE(kxtf9_samp_freq_table),
+ data->odr_bits, val, val2);
+ else
+ return kxcjk1013_convert_odr_value(samp_freq_table,
+ ARRAY_SIZE(samp_freq_table),
+ data->odr_bits, val, val2);
+}
+
+static int kxcjk1013_get_acc_reg(struct kxcjk1013_data *data, int axis)
+{
+ u8 reg = KXCJK1013_REG_XOUT_L + axis * 2;
+ int ret;
+
+ ret = i2c_smbus_read_word_data(data->client, reg);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "failed to read accel_%c registers\n", 'x' + axis);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int kxcjk1013_set_scale(struct kxcjk1013_data *data, int val)
+{
+ int ret, i;
+ enum kxcjk1013_mode store_mode;
+
+ for (i = 0; i < ARRAY_SIZE(KXCJK1013_scale_table); ++i) {
+ if (KXCJK1013_scale_table[i].scale == val) {
+ ret = kxcjk1013_get_mode(data, &store_mode);
+ if (ret < 0)
+ return ret;
+
+ ret = kxcjk1013_set_mode(data, STANDBY);
+ if (ret < 0)
+ return ret;
+
+ ret = kxcjk1013_set_range(data, i);
+ if (ret < 0)
+ return ret;
+
+ if (store_mode == OPERATION) {
+ ret = kxcjk1013_set_mode(data, OPERATION);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int kxcjk1013_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&data->mutex);
+ if (iio_buffer_enabled(indio_dev))
+ ret = -EBUSY;
+ else {
+ ret = kxcjk1013_set_power_state(data, true);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ ret = kxcjk1013_get_acc_reg(data, chan->scan_index);
+ if (ret < 0) {
+ kxcjk1013_set_power_state(data, false);
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ *val = sign_extend32(ret >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+ ret = kxcjk1013_set_power_state(data, false);
+ }
+ mutex_unlock(&data->mutex);
+
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = KXCJK1013_scale_table[data->range].scale;
+ return IIO_VAL_INT_PLUS_MICRO;
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ mutex_lock(&data->mutex);
+ ret = kxcjk1013_get_odr(data, val, val2);
+ mutex_unlock(&data->mutex);
+ return ret;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int kxcjk1013_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ mutex_lock(&data->mutex);
+ ret = kxcjk1013_set_odr(data, val, val2);
+ mutex_unlock(&data->mutex);
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ if (val)
+ return -EINVAL;
+
+ mutex_lock(&data->mutex);
+ ret = kxcjk1013_set_scale(data, val2);
+ mutex_unlock(&data->mutex);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int kxcjk1013_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 kxcjk1013_data *data = iio_priv(indio_dev);
+
+ *val2 = 0;
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ *val = data->wake_thres;
+ break;
+ case IIO_EV_INFO_PERIOD:
+ *val = data->wake_dur;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return IIO_VAL_INT;
+}
+
+static int kxcjk1013_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 kxcjk1013_data *data = iio_priv(indio_dev);
+
+ if (data->ev_enable_state)
+ return -EBUSY;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ data->wake_thres = val;
+ break;
+ case IIO_EV_INFO_PERIOD:
+ data->wake_dur = val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int kxcjk1013_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 kxcjk1013_data *data = iio_priv(indio_dev);
+
+ return data->ev_enable_state;
+}
+
+static int kxcjk1013_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (state && data->ev_enable_state)
+ return 0;
+
+ mutex_lock(&data->mutex);
+
+ if (!state && data->motion_trigger_on) {
+ data->ev_enable_state = 0;
+ mutex_unlock(&data->mutex);
+ return 0;
+ }
+
+ /*
+ * We will expect the enable and disable to do operation in
+ * reverse order. This will happen here anyway as our
+ * resume operation uses sync mode runtime pm calls, the
+ * suspend operation will be delayed by autosuspend delay
+ * So the disable operation will still happen in reverse of
+ * enable operation. When runtime pm is disabled the mode
+ * is always on so sequence doesn't matter
+ */
+ ret = kxcjk1013_set_power_state(data, state);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+
+ ret = kxcjk1013_setup_interrupt(data, state, false);
+ if (ret < 0) {
+ kxcjk1013_set_power_state(data, false);
+ data->ev_enable_state = 0;
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+
+ data->ev_enable_state = state;
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static int kxcjk1013_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+
+ return kxcjk1013_set_power_state(data, true);
+}
+
+static int kxcjk1013_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+
+ return kxcjk1013_set_power_state(data, false);
+}
+
+static ssize_t kxcjk1013_get_samp_freq_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ const char *str;
+
+ if (data->info == &kxtf9_info)
+ str = kxtf9_samp_freq_avail;
+ else
+ str = kxcjk1013_samp_freq_avail;
+
+ return sprintf(buf, "%s\n", str);
+}
+
+static IIO_DEVICE_ATTR(in_accel_sampling_frequency_available, S_IRUGO,
+ kxcjk1013_get_samp_freq_avail, NULL, 0);
+
+static IIO_CONST_ATTR(in_accel_scale_available, "0.009582 0.019163 0.038326");
+
+static struct attribute *kxcjk1013_attributes[] = {
+ &iio_dev_attr_in_accel_sampling_frequency_available.dev_attr.attr,
+ &iio_const_attr_in_accel_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group kxcjk1013_attrs_group = {
+ .attrs = kxcjk1013_attributes,
+};
+
+static const struct iio_event_spec kxcjk1013_event = {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_PERIOD)
+};
+
+static const struct iio_mount_matrix *
+kxcjk1013_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info kxcjk1013_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, kxcjk1013_get_mount_matrix),
+ { }
+};
+
+#define KXCJK1013_CHANNEL(_axis) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = AXIS_##_axis, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .shift = 4, \
+ .endianness = IIO_LE, \
+ }, \
+ .event_spec = &kxcjk1013_event, \
+ .ext_info = kxcjk1013_ext_info, \
+ .num_event_specs = 1 \
+}
+
+static const struct iio_chan_spec kxcjk1013_channels[] = {
+ KXCJK1013_CHANNEL(X),
+ KXCJK1013_CHANNEL(Y),
+ KXCJK1013_CHANNEL(Z),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const struct iio_buffer_setup_ops kxcjk1013_buffer_setup_ops = {
+ .preenable = kxcjk1013_buffer_preenable,
+ .postdisable = kxcjk1013_buffer_postdisable,
+};
+
+static const struct iio_info kxcjk1013_iio_info = {
+ .attrs = &kxcjk1013_attrs_group,
+ .read_raw = kxcjk1013_read_raw,
+ .write_raw = kxcjk1013_write_raw,
+ .read_event_value = kxcjk1013_read_event,
+ .write_event_value = kxcjk1013_write_event,
+ .write_event_config = kxcjk1013_write_event_config,
+ .read_event_config = kxcjk1013_read_event_config,
+};
+
+static const unsigned long kxcjk1013_scan_masks[] = {0x7, 0};
+
+static irqreturn_t kxcjk1013_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = i2c_smbus_read_i2c_block_data_or_emulated(data->client,
+ KXCJK1013_REG_XOUT_L,
+ AXIS_MAX * 2,
+ (u8 *)data->scan.chans);
+ mutex_unlock(&data->mutex);
+ if (ret < 0)
+ goto err;
+
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ data->timestamp);
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static void kxcjk1013_trig_reen(struct iio_trigger *trig)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ const struct kx_chipset_regs *regs = data->info->regs;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, regs->int_rel);
+ if (ret < 0)
+ dev_err(&data->client->dev, "Error reading reg_int_rel\n");
+}
+
+static int kxcjk1013_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+
+ if (!state && data->ev_enable_state && data->motion_trigger_on) {
+ data->motion_trigger_on = false;
+ mutex_unlock(&data->mutex);
+ return 0;
+ }
+
+ ret = kxcjk1013_set_power_state(data, state);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ ret = kxcjk1013_setup_interrupt(data, state, data->motion_trig != trig);
+ if (ret < 0) {
+ kxcjk1013_set_power_state(data, false);
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ if (data->motion_trig == trig)
+ data->motion_trigger_on = state;
+ else
+ data->dready_trigger_on = state;
+
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static const struct iio_trigger_ops kxcjk1013_trigger_ops = {
+ .set_trigger_state = kxcjk1013_data_rdy_trigger_set_state,
+ .reenable = kxcjk1013_trig_reen,
+};
+
+static void kxcjk1013_report_motion_event(struct iio_dev *indio_dev)
+{
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ const struct kx_chipset_regs *regs = data->info->regs;
+
+ int ret = i2c_smbus_read_byte_data(data->client, regs->int_src2);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_int_src2\n");
+ return;
+ }
+
+ if (ret & KXCJK1013_REG_INT_SRC2_BIT_XN)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_X,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_FALLING),
+ data->timestamp);
+
+ if (ret & KXCJK1013_REG_INT_SRC2_BIT_XP)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_X,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_RISING),
+ data->timestamp);
+
+ if (ret & KXCJK1013_REG_INT_SRC2_BIT_YN)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_Y,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_FALLING),
+ data->timestamp);
+
+ if (ret & KXCJK1013_REG_INT_SRC2_BIT_YP)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_Y,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_RISING),
+ data->timestamp);
+
+ if (ret & KXCJK1013_REG_INT_SRC2_BIT_ZN)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_Z,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_FALLING),
+ data->timestamp);
+
+ if (ret & KXCJK1013_REG_INT_SRC2_BIT_ZP)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_Z,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_RISING),
+ data->timestamp);
+}
+
+static irqreturn_t kxcjk1013_event_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ const struct kx_chipset_regs *regs = data->info->regs;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, regs->int_src1);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Error reading reg_int_src1\n");
+ goto ack_intr;
+ }
+
+ if (ret & KXCJK1013_REG_INT_SRC1_BIT_WUFS) {
+ if (data->info == &kxtf9_info)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_RISING),
+ data->timestamp);
+ else
+ kxcjk1013_report_motion_event(indio_dev);
+ }
+
+ack_intr:
+ if (data->dready_trigger_on)
+ return IRQ_HANDLED;
+
+ ret = i2c_smbus_read_byte_data(data->client, regs->int_rel);
+ if (ret < 0)
+ dev_err(&data->client->dev, "Error reading reg_int_rel\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t kxcjk1013_data_rdy_trig_poll(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+
+ data->timestamp = iio_get_time_ns(indio_dev);
+
+ if (data->dready_trigger_on)
+ iio_trigger_poll(data->dready_trig);
+ else if (data->motion_trigger_on)
+ iio_trigger_poll(data->motion_trig);
+
+ if (data->ev_enable_state)
+ return IRQ_WAKE_THREAD;
+ else
+ return IRQ_HANDLED;
+}
+
+static int kxcjk1013_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ static const char * const regulator_names[] = { "vdd", "vddio" };
+ struct kxcjk1013_data *data;
+ struct iio_dev *indio_dev;
+ struct kxcjk_1013_platform_data *pdata;
+ const void *ddata = NULL;
+ const char *name;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+
+ pdata = dev_get_platdata(&client->dev);
+ if (pdata) {
+ data->active_high_intr = pdata->active_high_intr;
+ data->orientation = pdata->orientation;
+ } else {
+ data->active_high_intr = true; /* default polarity */
+
+ if (!iio_read_acpi_mount_matrix(&client->dev, &data->orientation, "ROTM")) {
+ ret = iio_read_mount_matrix(&client->dev, &data->orientation);
+ if (ret)
+ return ret;
+ }
+
+ }
+
+ ret = devm_regulator_bulk_get_enable(&client->dev,
+ ARRAY_SIZE(regulator_names),
+ regulator_names);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to get regulators\n");
+
+ /*
+ * A typical delay of 10ms is required for powering up
+ * according to the data sheets of supported chips.
+ * Hence double that to play safe.
+ */
+ msleep(20);
+
+ if (id) {
+ name = id->name;
+ data->info = (const struct kx_chipset_info *)(id->driver_data);
+ } else {
+ name = iio_get_acpi_device_name_and_data(&client->dev, &ddata);
+ data->info = ddata;
+ if (data->info == &kxcj91008_kiox010a_info)
+ indio_dev->label = "accel-display";
+ else if (data->info == &kxcj91008_kiox020a_info)
+ indio_dev->label = "accel-base";
+ }
+ if (!name)
+ return -ENODEV;
+
+ ret = kxcjk1013_chip_init(data);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&data->mutex);
+
+ indio_dev->channels = kxcjk1013_channels;
+ indio_dev->num_channels = ARRAY_SIZE(kxcjk1013_channels);
+ indio_dev->available_scan_masks = kxcjk1013_scan_masks;
+ indio_dev->name = name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &kxcjk1013_iio_info;
+
+ if (client->irq > 0 && data->info->acpi_type != ACPI_SMO8500) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ kxcjk1013_data_rdy_trig_poll,
+ kxcjk1013_event_handler,
+ IRQF_TRIGGER_RISING,
+ "kxcjk1013_event",
+ indio_dev);
+ if (ret)
+ goto err_poweroff;
+
+ data->dready_trig = devm_iio_trigger_alloc(&client->dev,
+ "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->dready_trig) {
+ ret = -ENOMEM;
+ goto err_poweroff;
+ }
+
+ data->motion_trig = devm_iio_trigger_alloc(&client->dev,
+ "%s-any-motion-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->motion_trig) {
+ ret = -ENOMEM;
+ goto err_poweroff;
+ }
+
+ data->dready_trig->ops = &kxcjk1013_trigger_ops;
+ iio_trigger_set_drvdata(data->dready_trig, indio_dev);
+ ret = iio_trigger_register(data->dready_trig);
+ if (ret)
+ goto err_poweroff;
+
+ indio_dev->trig = iio_trigger_get(data->dready_trig);
+
+ data->motion_trig->ops = &kxcjk1013_trigger_ops;
+ iio_trigger_set_drvdata(data->motion_trig, indio_dev);
+ ret = iio_trigger_register(data->motion_trig);
+ if (ret) {
+ data->motion_trig = NULL;
+ goto err_trigger_unregister;
+ }
+ }
+
+ ret = iio_triggered_buffer_setup(indio_dev,
+ &iio_pollfunc_store_time,
+ kxcjk1013_trigger_handler,
+ &kxcjk1013_buffer_setup_ops);
+ if (ret < 0) {
+ dev_err(&client->dev, "iio triggered buffer setup failed\n");
+ goto err_trigger_unregister;
+ }
+
+ ret = pm_runtime_set_active(&client->dev);
+ if (ret)
+ goto err_buffer_cleanup;
+
+ pm_runtime_enable(&client->dev);
+ pm_runtime_set_autosuspend_delay(&client->dev,
+ KXCJK1013_SLEEP_DELAY_MS);
+ pm_runtime_use_autosuspend(&client->dev);
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "unable to register iio device\n");
+ goto err_pm_cleanup;
+ }
+
+ return 0;
+
+err_pm_cleanup:
+ pm_runtime_dont_use_autosuspend(&client->dev);
+ pm_runtime_disable(&client->dev);
+err_buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+err_trigger_unregister:
+ if (data->dready_trig)
+ iio_trigger_unregister(data->dready_trig);
+ if (data->motion_trig)
+ iio_trigger_unregister(data->motion_trig);
+err_poweroff:
+ kxcjk1013_set_mode(data, STANDBY);
+
+ return ret;
+}
+
+static void kxcjk1013_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ pm_runtime_disable(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+
+ iio_triggered_buffer_cleanup(indio_dev);
+ if (data->dready_trig) {
+ iio_trigger_unregister(data->dready_trig);
+ iio_trigger_unregister(data->motion_trig);
+ }
+
+ mutex_lock(&data->mutex);
+ kxcjk1013_set_mode(data, STANDBY);
+ mutex_unlock(&data->mutex);
+}
+
+static int kxcjk1013_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = kxcjk1013_set_mode(data, STANDBY);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int kxcjk1013_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ int ret = 0;
+
+ mutex_lock(&data->mutex);
+ ret = kxcjk1013_set_mode(data, OPERATION);
+ if (ret == 0)
+ ret = kxcjk1013_set_range(data, data->range);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int kxcjk1013_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = kxcjk1013_set_mode(data, STANDBY);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "powering off device failed\n");
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+static int kxcjk1013_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct kxcjk1013_data *data = iio_priv(indio_dev);
+ int ret;
+ int sleep_val;
+
+ ret = kxcjk1013_set_mode(data, OPERATION);
+ if (ret < 0)
+ return ret;
+
+ sleep_val = kxcjk1013_get_startup_times(data);
+ if (sleep_val < 20000)
+ usleep_range(sleep_val, 20000);
+ else
+ msleep_interruptible(sleep_val/1000);
+
+ return 0;
+}
+
+static const struct dev_pm_ops kxcjk1013_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(kxcjk1013_suspend, kxcjk1013_resume)
+ RUNTIME_PM_OPS(kxcjk1013_runtime_suspend, kxcjk1013_runtime_resume, NULL)
+};
+
+static const struct i2c_device_id kxcjk1013_id[] = {
+ { .name = "kxcjk1013", .driver_data = (kernel_ulong_t)&kxcjk1013_info },
+ { .name = "kxcj91008", .driver_data = (kernel_ulong_t)&kxcj91008_info },
+ { .name = "kxtj21009", .driver_data = (kernel_ulong_t)&kxtj21009_info },
+ { .name = "kxtf9", .driver_data = (kernel_ulong_t)&kxtf9_info },
+ { .name = "kx023-1025", .driver_data = (kernel_ulong_t)&kx0231025_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, kxcjk1013_id);
+
+static const struct of_device_id kxcjk1013_of_match[] = {
+ { .compatible = "kionix,kxcjk1013", &kxcjk1013_info },
+ { .compatible = "kionix,kxcj91008", &kxcj91008_info },
+ { .compatible = "kionix,kxtj21009", &kxtj21009_info },
+ { .compatible = "kionix,kxtf9", &kxtf9_info },
+ { .compatible = "kionix,kx023-1025", &kx0231025_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, kxcjk1013_of_match);
+
+static const struct acpi_device_id kx_acpi_match[] = {
+ { "KIOX0008", (kernel_ulong_t)&kxcj91008_info },
+ { "KIOX0009", (kernel_ulong_t)&kxtj21009_info },
+ { "KIOX000A", (kernel_ulong_t)&kxcj91008_info },
+ /* KXCJ91008 in the display of a yoga 2-in-1 */
+ { "KIOX010A", (kernel_ulong_t)&kxcj91008_kiox010a_info },
+ /* KXCJ91008 in the base of a yoga 2-in-1 */
+ { "KIOX020A", (kernel_ulong_t)&kxcj91008_kiox020a_info },
+ { "KXCJ1008", (kernel_ulong_t)&kxcj91008_info },
+ { "KXCJ1013", (kernel_ulong_t)&kxcjk1013_info },
+ { "KXCJ9000", (kernel_ulong_t)&kxcj91008_info },
+ { "KXJ2109", (kernel_ulong_t)&kxtj21009_info },
+ { "KXTJ1009", (kernel_ulong_t)&kxtj21009_info },
+ { "SMO8500", (kernel_ulong_t)&kxcj91008_smo8500_info },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, kx_acpi_match);
+
+static struct i2c_driver kxcjk1013_driver = {
+ .driver = {
+ .name = "kxcjk1013",
+ .acpi_match_table = kx_acpi_match,
+ .of_match_table = kxcjk1013_of_match,
+ .pm = pm_ptr(&kxcjk1013_pm_ops),
+ },
+ .probe = kxcjk1013_probe,
+ .remove = kxcjk1013_remove,
+ .id_table = kxcjk1013_id,
+};
+module_i2c_driver(kxcjk1013_driver);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("KXCJK1013 accelerometer driver");
diff --git a/drivers/iio/accel/kxsd9-i2c.c b/drivers/iio/accel/kxsd9-i2c.c
new file mode 100644
index 000000000..f19626c2f
--- /dev/null
+++ b/drivers/iio/accel/kxsd9-i2c.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+
+#include "kxsd9.h"
+
+static int kxsd9_i2c_probe(struct i2c_client *i2c)
+{
+ static const struct regmap_config config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x0e,
+ };
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_i2c(i2c, &config);
+ if (IS_ERR(regmap)) {
+ dev_err(&i2c->dev, "Failed to register i2c regmap: %pe\n",
+ regmap);
+ return PTR_ERR(regmap);
+ }
+
+ return kxsd9_common_probe(&i2c->dev,
+ regmap,
+ i2c->name);
+}
+
+static void kxsd9_i2c_remove(struct i2c_client *client)
+{
+ kxsd9_common_remove(&client->dev);
+}
+
+static const struct of_device_id kxsd9_of_match[] = {
+ { .compatible = "kionix,kxsd9", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, kxsd9_of_match);
+
+static const struct i2c_device_id kxsd9_i2c_id[] = {
+ { .name = "kxsd9" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, kxsd9_i2c_id);
+
+static struct i2c_driver kxsd9_i2c_driver = {
+ .driver = {
+ .name = "kxsd9",
+ .of_match_table = kxsd9_of_match,
+ .pm = pm_ptr(&kxsd9_dev_pm_ops),
+ },
+ .probe = kxsd9_i2c_probe,
+ .remove = kxsd9_i2c_remove,
+ .id_table = kxsd9_i2c_id,
+};
+module_i2c_driver(kxsd9_i2c_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("KXSD9 accelerometer I2C interface");
+MODULE_IMPORT_NS("IIO_KXSD9");
diff --git a/drivers/iio/accel/kxsd9-spi.c b/drivers/iio/accel/kxsd9-spi.c
new file mode 100644
index 000000000..71da958d4
--- /dev/null
+++ b/drivers/iio/accel/kxsd9-spi.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+
+#include "kxsd9.h"
+
+static int kxsd9_spi_probe(struct spi_device *spi)
+{
+ static const struct regmap_config config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x0e,
+ };
+ struct regmap *regmap;
+
+ spi->mode = SPI_MODE_0;
+ regmap = devm_regmap_init_spi(spi, &config);
+ if (IS_ERR(regmap)) {
+ dev_err(&spi->dev, "%s: regmap allocation failed: %ld\n",
+ __func__, PTR_ERR(regmap));
+ return PTR_ERR(regmap);
+ }
+
+ return kxsd9_common_probe(&spi->dev,
+ regmap,
+ spi_get_device_id(spi)->name);
+}
+
+static void kxsd9_spi_remove(struct spi_device *spi)
+{
+ kxsd9_common_remove(&spi->dev);
+}
+
+static const struct spi_device_id kxsd9_spi_id[] = {
+ { .name = "kxsd9" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, kxsd9_spi_id);
+
+static const struct of_device_id kxsd9_of_match[] = {
+ { .compatible = "kionix,kxsd9" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, kxsd9_of_match);
+
+static struct spi_driver kxsd9_spi_driver = {
+ .driver = {
+ .name = "kxsd9",
+ .pm = pm_ptr(&kxsd9_dev_pm_ops),
+ .of_match_table = kxsd9_of_match,
+ },
+ .probe = kxsd9_spi_probe,
+ .remove = kxsd9_spi_remove,
+ .id_table = kxsd9_spi_id,
+};
+module_spi_driver(kxsd9_spi_driver);
+
+MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
+MODULE_DESCRIPTION("Kionix KXSD9 SPI driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_KXSD9");
diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c
new file mode 100644
index 000000000..7ac885d94
--- /dev/null
+++ b/drivers/iio/accel/kxsd9.c
@@ -0,0 +1,511 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * kxsd9.c simple support for the Kionix KXSD9 3D
+ * accelerometer.
+ *
+ * Copyright (c) 2008-2009 Jonathan Cameron <jic23@kernel.org>
+ *
+ * The i2c interface is very similar, so shouldn't be a problem once
+ * I have a suitable wire made up.
+ *
+ * TODO: Support the motion detector
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/sysfs.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#include "kxsd9.h"
+
+#define KXSD9_REG_X 0x00
+#define KXSD9_REG_Y 0x02
+#define KXSD9_REG_Z 0x04
+#define KXSD9_REG_AUX 0x06
+#define KXSD9_REG_RESET 0x0a
+#define KXSD9_REG_CTRL_C 0x0c
+
+#define KXSD9_CTRL_C_FS_MASK 0x03
+#define KXSD9_CTRL_C_FS_8G 0x00
+#define KXSD9_CTRL_C_FS_6G 0x01
+#define KXSD9_CTRL_C_FS_4G 0x02
+#define KXSD9_CTRL_C_FS_2G 0x03
+#define KXSD9_CTRL_C_MOT_LAT BIT(3)
+#define KXSD9_CTRL_C_MOT_LEV BIT(4)
+#define KXSD9_CTRL_C_LP_MASK 0xe0
+#define KXSD9_CTRL_C_LP_NONE 0x00
+#define KXSD9_CTRL_C_LP_2000HZC BIT(5)
+#define KXSD9_CTRL_C_LP_2000HZB BIT(6)
+#define KXSD9_CTRL_C_LP_2000HZA (BIT(5)|BIT(6))
+#define KXSD9_CTRL_C_LP_1000HZ BIT(7)
+#define KXSD9_CTRL_C_LP_500HZ (BIT(7)|BIT(5))
+#define KXSD9_CTRL_C_LP_100HZ (BIT(7)|BIT(6))
+#define KXSD9_CTRL_C_LP_50HZ (BIT(7)|BIT(6)|BIT(5))
+
+#define KXSD9_REG_CTRL_B 0x0d
+
+#define KXSD9_CTRL_B_CLK_HLD BIT(7)
+#define KXSD9_CTRL_B_ENABLE BIT(6)
+#define KXSD9_CTRL_B_ST BIT(5) /* Self-test */
+
+#define KXSD9_REG_CTRL_A 0x0e
+
+/**
+ * struct kxsd9_state - device related storage
+ * @dev: pointer to the parent device
+ * @map: regmap to the device
+ * @orientation: mounting matrix, flipped axis etc
+ * @regs: regulators for this device, VDD and IOVDD
+ * @scale: the current scaling setting
+ */
+struct kxsd9_state {
+ struct device *dev;
+ struct regmap *map;
+ struct iio_mount_matrix orientation;
+ struct regulator_bulk_data regs[2];
+ u8 scale;
+};
+
+#define KXSD9_SCALE_2G "0.011978"
+#define KXSD9_SCALE_4G "0.023927"
+#define KXSD9_SCALE_6G "0.035934"
+#define KXSD9_SCALE_8G "0.047853"
+
+/* reverse order */
+static const int kxsd9_micro_scales[4] = { 47853, 35934, 23927, 11978 };
+
+#define KXSD9_ZERO_G_OFFSET -2048
+
+/*
+ * Regulator names
+ */
+static const char kxsd9_reg_vdd[] = "vdd";
+static const char kxsd9_reg_iovdd[] = "iovdd";
+
+static int kxsd9_write_scale(struct iio_dev *indio_dev, int micro)
+{
+ int ret, i;
+ struct kxsd9_state *st = iio_priv(indio_dev);
+ bool foundit = false;
+
+ for (i = 0; i < 4; i++)
+ if (micro == kxsd9_micro_scales[i]) {
+ foundit = true;
+ break;
+ }
+ if (!foundit)
+ return -EINVAL;
+
+ ret = regmap_update_bits(st->map,
+ KXSD9_REG_CTRL_C,
+ KXSD9_CTRL_C_FS_MASK,
+ i);
+ if (ret < 0)
+ goto error_ret;
+
+ /* Cached scale when the sensor is powered down */
+ st->scale = i;
+
+error_ret:
+ return ret;
+}
+
+static IIO_CONST_ATTR(accel_scale_available,
+ KXSD9_SCALE_2G " "
+ KXSD9_SCALE_4G " "
+ KXSD9_SCALE_6G " "
+ KXSD9_SCALE_8G);
+
+static struct attribute *kxsd9_attributes[] = {
+ &iio_const_attr_accel_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static int kxsd9_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ int ret = -EINVAL;
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ pm_runtime_get_sync(st->dev);
+
+ if (mask == IIO_CHAN_INFO_SCALE) {
+ /* Check no integer component */
+ if (val)
+ ret = -EINVAL;
+ else
+ ret = kxsd9_write_scale(indio_dev, val2);
+ }
+
+ pm_runtime_put_autosuspend(st->dev);
+
+ return ret;
+}
+
+static int kxsd9_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int ret = -EINVAL;
+ struct kxsd9_state *st = iio_priv(indio_dev);
+ unsigned int regval;
+ __be16 raw_val;
+ u16 nval;
+
+ pm_runtime_get_sync(st->dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_bulk_read(st->map, chan->address, &raw_val,
+ sizeof(raw_val));
+ if (ret)
+ goto error_ret;
+ nval = be16_to_cpu(raw_val);
+ /* Only 12 bits are valid */
+ nval >>= 4;
+ *val = nval;
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_OFFSET:
+ /* This has a bias of -2048 */
+ *val = KXSD9_ZERO_G_OFFSET;
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ ret = regmap_read(st->map,
+ KXSD9_REG_CTRL_C,
+ &regval);
+ if (ret < 0)
+ goto error_ret;
+ *val = 0;
+ *val2 = kxsd9_micro_scales[regval & KXSD9_CTRL_C_FS_MASK];
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ }
+
+error_ret:
+ pm_runtime_put_autosuspend(st->dev);
+
+ return ret;
+};
+
+static irqreturn_t kxsd9_trigger_handler(int irq, void *p)
+{
+ const struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct kxsd9_state *st = iio_priv(indio_dev);
+ /*
+ * Ensure correct positioning and alignment of timestamp.
+ * No need to zero initialize as all elements written.
+ */
+ struct {
+ __be16 chan[4];
+ aligned_s64 ts;
+ } hw_values;
+ int ret;
+
+ ret = regmap_bulk_read(st->map,
+ KXSD9_REG_X,
+ hw_values.chan,
+ sizeof(hw_values.chan));
+ if (ret) {
+ dev_err(st->dev, "error reading data: %d\n", ret);
+ goto out;
+ }
+
+ iio_push_to_buffers_with_ts(indio_dev, &hw_values, sizeof(hw_values),
+ iio_get_time_ns(indio_dev));
+out:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int kxsd9_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ pm_runtime_get_sync(st->dev);
+
+ return 0;
+}
+
+static int kxsd9_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ pm_runtime_put_autosuspend(st->dev);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops kxsd9_buffer_setup_ops = {
+ .preenable = kxsd9_buffer_preenable,
+ .postdisable = kxsd9_buffer_postdisable,
+};
+
+static const struct iio_mount_matrix *
+kxsd9_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ return &st->orientation;
+}
+
+static const struct iio_chan_spec_ext_info kxsd9_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, kxsd9_get_mount_matrix),
+ { }
+};
+
+#define KXSD9_ACCEL_CHAN(axis, index) \
+ { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET), \
+ .ext_info = kxsd9_ext_info, \
+ .address = KXSD9_REG_##axis, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .shift = 4, \
+ .endianness = IIO_BE, \
+ }, \
+ }
+
+static const struct iio_chan_spec kxsd9_channels[] = {
+ KXSD9_ACCEL_CHAN(X, 0),
+ KXSD9_ACCEL_CHAN(Y, 1),
+ KXSD9_ACCEL_CHAN(Z, 2),
+ {
+ .type = IIO_VOLTAGE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .indexed = 1,
+ .address = KXSD9_REG_AUX,
+ .scan_index = 3,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 12,
+ .storagebits = 16,
+ .shift = 4,
+ .endianness = IIO_BE,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static const struct attribute_group kxsd9_attribute_group = {
+ .attrs = kxsd9_attributes,
+};
+
+static int kxsd9_power_up(struct kxsd9_state *st)
+{
+ int ret;
+
+ /* Enable the regulators */
+ ret = regulator_bulk_enable(ARRAY_SIZE(st->regs), st->regs);
+ if (ret) {
+ dev_err(st->dev, "Cannot enable regulators\n");
+ return ret;
+ }
+
+ /* Power up */
+ ret = regmap_write(st->map,
+ KXSD9_REG_CTRL_B,
+ KXSD9_CTRL_B_ENABLE);
+ if (ret)
+ return ret;
+
+ /*
+ * Set 1000Hz LPF, 2g fullscale, motion wakeup threshold 1g,
+ * latched wakeup
+ */
+ ret = regmap_write(st->map,
+ KXSD9_REG_CTRL_C,
+ KXSD9_CTRL_C_LP_1000HZ |
+ KXSD9_CTRL_C_MOT_LEV |
+ KXSD9_CTRL_C_MOT_LAT |
+ st->scale);
+ if (ret)
+ return ret;
+
+ /*
+ * Power-up time depends on the LPF setting, but typ 15.9 ms, let's
+ * set 20 ms to allow for some slack.
+ */
+ msleep(20);
+
+ return 0;
+};
+
+static int kxsd9_power_down(struct kxsd9_state *st)
+{
+ int ret;
+
+ /*
+ * Set into low power mode - since there may be more users of the
+ * regulators this is the first step of the power saving: it will
+ * make sure we conserve power even if there are others users on the
+ * regulators.
+ */
+ ret = regmap_clear_bits(st->map, KXSD9_REG_CTRL_B, KXSD9_CTRL_B_ENABLE);
+ if (ret)
+ return ret;
+
+ /* Disable the regulators */
+ ret = regulator_bulk_disable(ARRAY_SIZE(st->regs), st->regs);
+ if (ret) {
+ dev_err(st->dev, "Cannot disable regulators\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct iio_info kxsd9_info = {
+ .read_raw = &kxsd9_read_raw,
+ .write_raw = &kxsd9_write_raw,
+ .attrs = &kxsd9_attribute_group,
+};
+
+/* Four channels apart from timestamp, scan mask = 0x0f */
+static const unsigned long kxsd9_scan_masks[] = { 0xf, 0 };
+
+int kxsd9_common_probe(struct device *dev,
+ struct regmap *map,
+ const char *name)
+{
+ struct iio_dev *indio_dev;
+ struct kxsd9_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->dev = dev;
+ st->map = map;
+
+ indio_dev->channels = kxsd9_channels;
+ indio_dev->num_channels = ARRAY_SIZE(kxsd9_channels);
+ indio_dev->name = name;
+ indio_dev->info = &kxsd9_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->available_scan_masks = kxsd9_scan_masks;
+
+ /* Read the mounting matrix, if present */
+ ret = iio_read_mount_matrix(dev, &st->orientation);
+ if (ret)
+ return ret;
+
+ /* Fetch and turn on regulators */
+ st->regs[0].supply = kxsd9_reg_vdd;
+ st->regs[1].supply = kxsd9_reg_iovdd;
+ ret = devm_regulator_bulk_get(dev,
+ ARRAY_SIZE(st->regs),
+ st->regs);
+ if (ret) {
+ dev_err(dev, "Cannot get regulators\n");
+ return ret;
+ }
+ /* Default scaling */
+ st->scale = KXSD9_CTRL_C_FS_2G;
+
+ kxsd9_power_up(st);
+
+ ret = iio_triggered_buffer_setup(indio_dev,
+ iio_pollfunc_store_time,
+ kxsd9_trigger_handler,
+ &kxsd9_buffer_setup_ops);
+ if (ret) {
+ dev_err(dev, "triggered buffer setup failed\n");
+ goto err_power_down;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto err_cleanup_buffer;
+
+ dev_set_drvdata(dev, indio_dev);
+
+ /* Enable runtime PM */
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ /*
+ * Set autosuspend to two orders of magnitude larger than the
+ * start-up time. 20ms start-up time means 2000ms autosuspend,
+ * i.e. 2 seconds.
+ */
+ pm_runtime_set_autosuspend_delay(dev, 2000);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_put(dev);
+
+ return 0;
+
+err_cleanup_buffer:
+ iio_triggered_buffer_cleanup(indio_dev);
+err_power_down:
+ kxsd9_power_down(st);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS(kxsd9_common_probe, "IIO_KXSD9");
+
+void kxsd9_common_remove(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ iio_triggered_buffer_cleanup(indio_dev);
+ iio_device_unregister(indio_dev);
+ pm_runtime_get_sync(dev);
+ pm_runtime_put_noidle(dev);
+ pm_runtime_disable(dev);
+ kxsd9_power_down(st);
+}
+EXPORT_SYMBOL_NS(kxsd9_common_remove, "IIO_KXSD9");
+
+static int kxsd9_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ return kxsd9_power_down(st);
+}
+
+static int kxsd9_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ return kxsd9_power_up(st);
+}
+
+EXPORT_NS_RUNTIME_DEV_PM_OPS(kxsd9_dev_pm_ops, kxsd9_runtime_suspend,
+ kxsd9_runtime_resume, NULL, IIO_KXSD9);
+
+MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
+MODULE_DESCRIPTION("Kionix KXSD9 driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/kxsd9.h b/drivers/iio/accel/kxsd9.h
new file mode 100644
index 000000000..c04dbfa4e
--- /dev/null
+++ b/drivers/iio/accel/kxsd9.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/device.h>
+#include <linux/kernel.h>
+
+#define KXSD9_STATE_RX_SIZE 2
+#define KXSD9_STATE_TX_SIZE 2
+
+int kxsd9_common_probe(struct device *dev,
+ struct regmap *map,
+ const char *name);
+void kxsd9_common_remove(struct device *dev);
+
+extern const struct dev_pm_ops kxsd9_dev_pm_ops;
diff --git a/drivers/iio/accel/mc3230.c b/drivers/iio/accel/mc3230.c
new file mode 100644
index 000000000..b45897210
--- /dev/null
+++ b/drivers/iio/accel/mc3230.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * mCube MC3230 3-Axis Accelerometer
+ *
+ * Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com>
+ *
+ * IIO driver for mCube MC3230; 7-bit I2C address: 0x4c.
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define MC3230_REG_XOUT 0x00
+#define MC3230_REG_YOUT 0x01
+#define MC3230_REG_ZOUT 0x02
+
+#define MC3230_REG_MODE 0x07
+#define MC3230_MODE_OPCON_MASK 0x03
+#define MC3230_MODE_OPCON_WAKE 0x01
+#define MC3230_MODE_OPCON_STANDBY 0x03
+
+#define MC3230_REG_CHIP_ID 0x18
+#define MC3230_REG_PRODUCT_CODE 0x3b
+
+/*
+ * The accelerometer has one measurement range:
+ *
+ * -1.5g - +1.5g (8-bit, signed)
+ *
+ */
+
+struct mc3230_chip_info {
+ const char *name;
+ const u8 chip_id;
+ const u8 product_code;
+ const int scale;
+};
+
+static const struct mc3230_chip_info mc3230_chip_info = {
+ .name = "mc3230",
+ .chip_id = 0x01,
+ .product_code = 0x19,
+ /* (1.5 + 1.5) * 9.81 / (2^8 - 1) = 0.115411765 */
+ .scale = 115411765,
+};
+
+static const struct mc3230_chip_info mc3510c_chip_info = {
+ .name = "mc3510c",
+ .chip_id = 0x23,
+ .product_code = 0x10,
+ /* Was obtained empirically */
+ .scale = 625000000,
+};
+
+#define MC3230_CHANNEL(reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .ext_info = mc3230_ext_info, \
+}
+
+struct mc3230_data {
+ const struct mc3230_chip_info *chip_info;
+ struct i2c_client *client;
+ struct iio_mount_matrix orientation;
+};
+
+static const struct iio_mount_matrix *
+mc3230_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct mc3230_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info mc3230_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, mc3230_get_mount_matrix),
+ { }
+};
+
+static const struct iio_chan_spec mc3230_channels[] = {
+ MC3230_CHANNEL(MC3230_REG_XOUT, X),
+ MC3230_CHANNEL(MC3230_REG_YOUT, Y),
+ MC3230_CHANNEL(MC3230_REG_ZOUT, Z),
+};
+
+static int mc3230_set_opcon(struct mc3230_data *data, int opcon)
+{
+ int ret;
+ struct i2c_client *client = data->client;
+
+ ret = i2c_smbus_read_byte_data(client, MC3230_REG_MODE);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to read mode reg: %d\n", ret);
+ return ret;
+ }
+
+ ret &= ~MC3230_MODE_OPCON_MASK;
+ ret |= opcon;
+
+ ret = i2c_smbus_write_byte_data(client, MC3230_REG_MODE, ret);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to write mode reg: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mc3230_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mc3230_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = i2c_smbus_read_byte_data(data->client, chan->address);
+ if (ret < 0)
+ return ret;
+ *val = sign_extend32(ret, 7);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = data->chip_info->scale;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info mc3230_info = {
+ .read_raw = mc3230_read_raw,
+};
+
+static int mc3230_probe(struct i2c_client *client)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct mc3230_data *data;
+ const struct mc3230_chip_info *chip_info;
+
+ chip_info = i2c_get_match_data(client);
+ if (chip_info == NULL) {
+ dev_err(&client->dev, "failed to get match data");
+ return -ENODATA;
+ }
+
+ /* First check chip-id and product-id */
+ ret = i2c_smbus_read_byte_data(client, MC3230_REG_CHIP_ID);
+ if (ret != chip_info->chip_id) {
+ dev_info(&client->dev,
+ "chip id check fail: 0x%x != 0x%x !\n",
+ ret, chip_info->chip_id);
+ }
+
+ ret = i2c_smbus_read_byte_data(client, MC3230_REG_PRODUCT_CODE);
+ if (ret != chip_info->product_code) {
+ dev_info(&client->dev,
+ "product code check fail: 0x%x != 0x%x !\n",
+ ret, chip_info->product_code);
+ }
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->chip_info = chip_info;
+ data->client = client;
+ i2c_set_clientdata(client, indio_dev);
+
+ indio_dev->info = &mc3230_info;
+ indio_dev->name = chip_info->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = mc3230_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mc3230_channels);
+
+ ret = mc3230_set_opcon(data, MC3230_MODE_OPCON_WAKE);
+ if (ret < 0)
+ return ret;
+
+ ret = iio_read_mount_matrix(&client->dev, &data->orientation);
+ if (ret)
+ return ret;
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "device_register failed\n");
+ mc3230_set_opcon(data, MC3230_MODE_OPCON_STANDBY);
+ }
+
+ return ret;
+}
+
+static void mc3230_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ iio_device_unregister(indio_dev);
+
+ mc3230_set_opcon(iio_priv(indio_dev), MC3230_MODE_OPCON_STANDBY);
+}
+
+static int mc3230_suspend(struct device *dev)
+{
+ struct mc3230_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return mc3230_set_opcon(data, MC3230_MODE_OPCON_STANDBY);
+}
+
+static int mc3230_resume(struct device *dev)
+{
+ struct mc3230_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return mc3230_set_opcon(data, MC3230_MODE_OPCON_WAKE);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(mc3230_pm_ops, mc3230_suspend, mc3230_resume);
+
+static const struct i2c_device_id mc3230_i2c_id[] = {
+ { .name = "mc3230", .driver_data = (kernel_ulong_t)&mc3230_chip_info },
+ { .name = "mc3510c", .driver_data = (kernel_ulong_t)&mc3510c_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mc3230_i2c_id);
+
+static const struct of_device_id mc3230_of_match[] = {
+ { .compatible = "mcube,mc3230", &mc3230_chip_info },
+ { .compatible = "mcube,mc3510c", &mc3510c_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mc3230_of_match);
+
+static struct i2c_driver mc3230_driver = {
+ .driver = {
+ .name = "mc3230",
+ .of_match_table = mc3230_of_match,
+ .pm = pm_sleep_ptr(&mc3230_pm_ops),
+ },
+ .probe = mc3230_probe,
+ .remove = mc3230_remove,
+ .id_table = mc3230_i2c_id,
+};
+
+module_i2c_driver(mc3230_driver);
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("mCube MC3230 3-Axis Accelerometer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/mma7455.h b/drivers/iio/accel/mma7455.h
new file mode 100644
index 000000000..1fcc4b64b
--- /dev/null
+++ b/drivers/iio/accel/mma7455.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * IIO accel driver for Freescale MMA7455L 3-axis 10-bit accelerometer
+ * Copyright 2015 Joachim Eastwood <manabian@gmail.com>
+ */
+
+#ifndef __MMA7455_H
+#define __MMA7455_H
+
+extern const struct regmap_config mma7455_core_regmap;
+
+int mma7455_core_probe(struct device *dev, struct regmap *regmap,
+ const char *name);
+void mma7455_core_remove(struct device *dev);
+
+#endif
diff --git a/drivers/iio/accel/mma7455_core.c b/drivers/iio/accel/mma7455_core.c
new file mode 100644
index 000000000..a2b5bdf14
--- /dev/null
+++ b/drivers/iio/accel/mma7455_core.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO accel core driver for Freescale MMA7455L 3-axis 10-bit accelerometer
+ * Copyright 2015 Joachim Eastwood <manabian@gmail.com>
+ *
+ * UNSUPPORTED hardware features:
+ * - 8-bit mode with different scales
+ * - INT1/INT2 interrupts
+ * - Offset calibration
+ * - Events
+ */
+
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include "mma7455.h"
+
+#define MMA7455_REG_XOUTL 0x00
+#define MMA7455_REG_XOUTH 0x01
+#define MMA7455_REG_YOUTL 0x02
+#define MMA7455_REG_YOUTH 0x03
+#define MMA7455_REG_ZOUTL 0x04
+#define MMA7455_REG_ZOUTH 0x05
+#define MMA7455_REG_STATUS 0x09
+#define MMA7455_STATUS_DRDY BIT(0)
+#define MMA7455_REG_WHOAMI 0x0f
+#define MMA7455_WHOAMI_ID 0x55
+#define MMA7455_REG_MCTL 0x16
+#define MMA7455_MCTL_MODE_STANDBY 0x00
+#define MMA7455_MCTL_MODE_MEASURE 0x01
+#define MMA7455_REG_CTL1 0x18
+#define MMA7455_CTL1_DFBW_MASK BIT(7)
+#define MMA7455_CTL1_DFBW_125HZ BIT(7)
+#define MMA7455_CTL1_DFBW_62_5HZ 0
+#define MMA7455_REG_TW 0x1e
+
+/*
+ * When MMA7455 is used in 10-bit it has a fullscale of -8g
+ * corresponding to raw value -512. The userspace interface
+ * uses m/s^2 and we declare micro units.
+ * So scale factor is given by:
+ * g * 8 * 1e6 / 512 = 153228.90625, with g = 9.80665
+ */
+#define MMA7455_10BIT_SCALE 153229
+
+struct mma7455_data {
+ struct regmap *regmap;
+ /*
+ * Used to reorganize data. Will ensure correct alignment of
+ * the timestamp if present
+ */
+ struct {
+ __le16 channels[3];
+ aligned_s64 ts;
+ } scan;
+};
+
+static int mma7455_drdy(struct mma7455_data *mma7455)
+{
+ struct device *dev = regmap_get_device(mma7455->regmap);
+ unsigned int reg;
+ int tries = 3;
+ int ret;
+
+ while (tries-- > 0) {
+ ret = regmap_read(mma7455->regmap, MMA7455_REG_STATUS, &reg);
+ if (ret)
+ return ret;
+
+ if (reg & MMA7455_STATUS_DRDY)
+ return 0;
+
+ msleep(20);
+ }
+
+ dev_warn(dev, "data not ready\n");
+
+ return -EIO;
+}
+
+static irqreturn_t mma7455_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct mma7455_data *mma7455 = iio_priv(indio_dev);
+ int ret;
+
+ ret = mma7455_drdy(mma7455);
+ if (ret)
+ goto done;
+
+ ret = regmap_bulk_read(mma7455->regmap, MMA7455_REG_XOUTL,
+ mma7455->scan.channels,
+ sizeof(mma7455->scan.channels));
+ if (ret)
+ goto done;
+
+ iio_push_to_buffers_with_ts(indio_dev, &mma7455->scan,
+ sizeof(mma7455->scan),
+ iio_get_time_ns(indio_dev));
+
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int mma7455_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mma7455_data *mma7455 = iio_priv(indio_dev);
+ unsigned int reg;
+ __le16 data;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (iio_buffer_enabled(indio_dev))
+ return -EBUSY;
+
+ ret = mma7455_drdy(mma7455);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(mma7455->regmap, chan->address, &data,
+ sizeof(data));
+ if (ret)
+ return ret;
+
+ *val = sign_extend32(le16_to_cpu(data),
+ chan->scan_type.realbits - 1);
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = MMA7455_10BIT_SCALE;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = regmap_read(mma7455->regmap, MMA7455_REG_CTL1, &reg);
+ if (ret)
+ return ret;
+
+ if (reg & MMA7455_CTL1_DFBW_MASK)
+ *val = 250;
+ else
+ *val = 125;
+
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int mma7455_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mma7455_data *mma7455 = iio_priv(indio_dev);
+ int i;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (val == 250 && val2 == 0)
+ i = MMA7455_CTL1_DFBW_125HZ;
+ else if (val == 125 && val2 == 0)
+ i = MMA7455_CTL1_DFBW_62_5HZ;
+ else
+ return -EINVAL;
+
+ return regmap_update_bits(mma7455->regmap, MMA7455_REG_CTL1,
+ MMA7455_CTL1_DFBW_MASK, i);
+
+ case IIO_CHAN_INFO_SCALE:
+ /* In 10-bit mode there is only one scale available */
+ if (val == 0 && val2 == MMA7455_10BIT_SCALE)
+ return 0;
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static IIO_CONST_ATTR(sampling_frequency_available, "125 250");
+
+static struct attribute *mma7455_attributes[] = {
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group mma7455_group = {
+ .attrs = mma7455_attributes,
+};
+
+static const struct iio_info mma7455_info = {
+ .attrs = &mma7455_group,
+ .read_raw = mma7455_read_raw,
+ .write_raw = mma7455_write_raw,
+};
+
+#define MMA7455_CHANNEL(axis, idx) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .address = MMA7455_REG_##axis##OUTL,\
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = idx, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 10, \
+ .storagebits = 16, \
+ .endianness = IIO_LE, \
+ }, \
+}
+
+static const struct iio_chan_spec mma7455_channels[] = {
+ MMA7455_CHANNEL(X, 0),
+ MMA7455_CHANNEL(Y, 1),
+ MMA7455_CHANNEL(Z, 2),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static const unsigned long mma7455_scan_masks[] = {0x7, 0};
+
+const struct regmap_config mma7455_core_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MMA7455_REG_TW,
+};
+EXPORT_SYMBOL_NS_GPL(mma7455_core_regmap, "IIO_MMA7455");
+
+int mma7455_core_probe(struct device *dev, struct regmap *regmap,
+ const char *name)
+{
+ struct mma7455_data *mma7455;
+ struct iio_dev *indio_dev;
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(regmap, MMA7455_REG_WHOAMI, &reg);
+ if (ret) {
+ dev_err(dev, "unable to read reg\n");
+ return ret;
+ }
+
+ if (reg != MMA7455_WHOAMI_ID) {
+ dev_err(dev, "device id mismatch\n");
+ return -ENODEV;
+ }
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*mma7455));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, indio_dev);
+ mma7455 = iio_priv(indio_dev);
+ mma7455->regmap = regmap;
+
+ indio_dev->info = &mma7455_info;
+ indio_dev->name = name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = mma7455_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mma7455_channels);
+ indio_dev->available_scan_masks = mma7455_scan_masks;
+
+ regmap_write(mma7455->regmap, MMA7455_REG_MCTL,
+ MMA7455_MCTL_MODE_MEASURE);
+
+ ret = iio_triggered_buffer_setup(indio_dev, NULL,
+ mma7455_trigger_handler, NULL);
+ if (ret) {
+ dev_err(dev, "unable to setup triggered buffer\n");
+ return ret;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(dev, "unable to register device\n");
+ iio_triggered_buffer_cleanup(indio_dev);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(mma7455_core_probe, "IIO_MMA7455");
+
+void mma7455_core_remove(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct mma7455_data *mma7455 = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+
+ regmap_write(mma7455->regmap, MMA7455_REG_MCTL,
+ MMA7455_MCTL_MODE_STANDBY);
+}
+EXPORT_SYMBOL_NS_GPL(mma7455_core_remove, "IIO_MMA7455");
+
+MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>");
+MODULE_DESCRIPTION("Freescale MMA7455L core accelerometer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/mma7455_i2c.c b/drivers/iio/accel/mma7455_i2c.c
new file mode 100644
index 000000000..9588fd133
--- /dev/null
+++ b/drivers/iio/accel/mma7455_i2c.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO accel I2C driver for Freescale MMA7455L 3-axis 10-bit accelerometer
+ * Copyright 2015 Joachim Eastwood <manabian@gmail.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "mma7455.h"
+
+static int mma7455_i2c_probe(struct i2c_client *i2c)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(i2c);
+ struct regmap *regmap;
+ const char *name = NULL;
+
+ regmap = devm_regmap_init_i2c(i2c, &mma7455_core_regmap);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ if (id)
+ name = id->name;
+
+ return mma7455_core_probe(&i2c->dev, regmap, name);
+}
+
+static void mma7455_i2c_remove(struct i2c_client *i2c)
+{
+ mma7455_core_remove(&i2c->dev);
+}
+
+static const struct i2c_device_id mma7455_i2c_ids[] = {
+ { .name = "mma7455" },
+ { .name = "mma7456" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mma7455_i2c_ids);
+
+static const struct of_device_id mma7455_of_match[] = {
+ { .compatible = "fsl,mma7455" },
+ { .compatible = "fsl,mma7456" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mma7455_of_match);
+
+static struct i2c_driver mma7455_i2c_driver = {
+ .probe = mma7455_i2c_probe,
+ .remove = mma7455_i2c_remove,
+ .id_table = mma7455_i2c_ids,
+ .driver = {
+ .name = "mma7455-i2c",
+ .of_match_table = mma7455_of_match,
+ },
+};
+module_i2c_driver(mma7455_i2c_driver);
+
+MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>");
+MODULE_DESCRIPTION("Freescale MMA7455L I2C accelerometer driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_MMA7455");
diff --git a/drivers/iio/accel/mma7455_spi.c b/drivers/iio/accel/mma7455_spi.c
new file mode 100644
index 000000000..872c47acf
--- /dev/null
+++ b/drivers/iio/accel/mma7455_spi.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO accel SPI driver for Freescale MMA7455L 3-axis 10-bit accelerometer
+ * Copyright 2015 Joachim Eastwood <manabian@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "mma7455.h"
+
+static int mma7455_spi_probe(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_spi(spi, &mma7455_core_regmap);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return mma7455_core_probe(&spi->dev, regmap, id->name);
+}
+
+static void mma7455_spi_remove(struct spi_device *spi)
+{
+ mma7455_core_remove(&spi->dev);
+}
+
+static const struct spi_device_id mma7455_spi_ids[] = {
+ { .name = "mma7455" },
+ { .name = "mma7456" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, mma7455_spi_ids);
+
+static struct spi_driver mma7455_spi_driver = {
+ .probe = mma7455_spi_probe,
+ .remove = mma7455_spi_remove,
+ .id_table = mma7455_spi_ids,
+ .driver = {
+ .name = "mma7455-spi",
+ },
+};
+module_spi_driver(mma7455_spi_driver);
+
+MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>");
+MODULE_DESCRIPTION("Freescale MMA7455L SPI accelerometer driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_MMA7455");
diff --git a/drivers/iio/accel/mma7660.c b/drivers/iio/accel/mma7660.c
new file mode 100644
index 000000000..38b4df76c
--- /dev/null
+++ b/drivers/iio/accel/mma7660.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Freescale MMA7660FC 3-Axis Accelerometer
+ *
+ * Copyright (c) 2016, Intel Corporation.
+ *
+ * IIO driver for Freescale MMA7660FC; 7-bit I2C address: 0x4c.
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define MMA7660_DRIVER_NAME "mma7660"
+
+#define MMA7660_REG_XOUT 0x00
+#define MMA7660_REG_YOUT 0x01
+#define MMA7660_REG_ZOUT 0x02
+#define MMA7660_REG_OUT_BIT_ALERT BIT(6)
+
+#define MMA7660_REG_MODE 0x07
+#define MMA7660_REG_MODE_BIT_MODE BIT(0)
+#define MMA7660_REG_MODE_BIT_TON BIT(2)
+
+#define MMA7660_I2C_READ_RETRIES 5
+
+/*
+ * The accelerometer has one measurement range:
+ *
+ * -1.5g - +1.5g (6-bit, signed)
+ *
+ * scale = (1.5 + 1.5) * 9.81 / (2^6 - 1) = 0.467142857
+ */
+
+#define MMA7660_SCALE_AVAIL "0.467142857"
+
+static const int mma7660_nscale = 467142857;
+
+enum mma7660_mode {
+ MMA7660_MODE_STANDBY,
+ MMA7660_MODE_ACTIVE
+};
+
+struct mma7660_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ enum mma7660_mode mode;
+ struct iio_mount_matrix orientation;
+};
+
+static const struct iio_mount_matrix *
+mma7660_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct mma7660_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info mma7660_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, mma7660_get_mount_matrix),
+ { }
+};
+
+static IIO_CONST_ATTR(in_accel_scale_available, MMA7660_SCALE_AVAIL);
+
+static struct attribute *mma7660_attributes[] = {
+ &iio_const_attr_in_accel_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group mma7660_attribute_group = {
+ .attrs = mma7660_attributes
+};
+
+#define MMA7660_CHANNEL(reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .ext_info = mma7660_ext_info, \
+}
+
+static const struct iio_chan_spec mma7660_channels[] = {
+ MMA7660_CHANNEL(MMA7660_REG_XOUT, X),
+ MMA7660_CHANNEL(MMA7660_REG_YOUT, Y),
+ MMA7660_CHANNEL(MMA7660_REG_ZOUT, Z),
+};
+
+static int mma7660_set_mode(struct mma7660_data *data,
+ enum mma7660_mode mode)
+{
+ int ret;
+ struct i2c_client *client = data->client;
+
+ if (mode == data->mode)
+ return 0;
+
+ ret = i2c_smbus_read_byte_data(client, MMA7660_REG_MODE);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to read sensor mode\n");
+ return ret;
+ }
+
+ if (mode == MMA7660_MODE_ACTIVE) {
+ ret &= ~MMA7660_REG_MODE_BIT_TON;
+ ret |= MMA7660_REG_MODE_BIT_MODE;
+ } else {
+ ret &= ~MMA7660_REG_MODE_BIT_TON;
+ ret &= ~MMA7660_REG_MODE_BIT_MODE;
+ }
+
+ ret = i2c_smbus_write_byte_data(client, MMA7660_REG_MODE, ret);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to change sensor mode\n");
+ return ret;
+ }
+
+ data->mode = mode;
+
+ return ret;
+}
+
+static int mma7660_read_accel(struct mma7660_data *data, u8 address)
+{
+ int ret, retries = MMA7660_I2C_READ_RETRIES;
+ struct i2c_client *client = data->client;
+
+ /*
+ * Read data. If the Alert bit is set, the register was read at
+ * the same time as the device was attempting to update the content.
+ * The solution is to read the register again. Do this only
+ * MMA7660_I2C_READ_RETRIES times to avoid spending too much time
+ * in the kernel.
+ */
+ do {
+ ret = i2c_smbus_read_byte_data(client, address);
+ if (ret < 0) {
+ dev_err(&client->dev, "register read failed\n");
+ return ret;
+ }
+ } while (retries-- > 0 && ret & MMA7660_REG_OUT_BIT_ALERT);
+
+ if (ret & MMA7660_REG_OUT_BIT_ALERT) {
+ dev_err(&client->dev, "all register read retries failed\n");
+ return -ETIMEDOUT;
+ }
+
+ return ret;
+}
+
+static int mma7660_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mma7660_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&data->lock);
+ ret = mma7660_read_accel(data, chan->address);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+ *val = sign_extend32(ret, 5);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = mma7660_nscale;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info mma7660_info = {
+ .read_raw = mma7660_read_raw,
+ .attrs = &mma7660_attribute_group,
+};
+
+static int mma7660_probe(struct i2c_client *client)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct mma7660_data *data;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ i2c_set_clientdata(client, indio_dev);
+ mutex_init(&data->lock);
+ data->mode = MMA7660_MODE_STANDBY;
+
+ ret = iio_read_mount_matrix(&client->dev, &data->orientation);
+ if (ret)
+ return ret;
+
+ indio_dev->info = &mma7660_info;
+ indio_dev->name = MMA7660_DRIVER_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = mma7660_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mma7660_channels);
+
+ ret = mma7660_set_mode(data, MMA7660_MODE_ACTIVE);
+ if (ret < 0)
+ return ret;
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "device_register failed\n");
+ mma7660_set_mode(data, MMA7660_MODE_STANDBY);
+ }
+
+ return ret;
+}
+
+static void mma7660_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ int ret;
+
+ iio_device_unregister(indio_dev);
+
+ ret = mma7660_set_mode(iio_priv(indio_dev), MMA7660_MODE_STANDBY);
+ if (ret)
+ dev_warn(&client->dev, "Failed to put device in stand-by mode (%pe), ignoring\n",
+ ERR_PTR(ret));
+}
+
+static int mma7660_suspend(struct device *dev)
+{
+ struct mma7660_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return mma7660_set_mode(data, MMA7660_MODE_STANDBY);
+}
+
+static int mma7660_resume(struct device *dev)
+{
+ struct mma7660_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return mma7660_set_mode(data, MMA7660_MODE_ACTIVE);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(mma7660_pm_ops, mma7660_suspend,
+ mma7660_resume);
+
+static const struct i2c_device_id mma7660_i2c_id[] = {
+ { .name = "mma7660" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mma7660_i2c_id);
+
+static const struct of_device_id mma7660_of_match[] = {
+ { .compatible = "fsl,mma7660" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mma7660_of_match);
+
+static const struct acpi_device_id mma7660_acpi_id[] = {
+ {"MMA7660", 0},
+ { }
+};
+
+MODULE_DEVICE_TABLE(acpi, mma7660_acpi_id);
+
+static struct i2c_driver mma7660_driver = {
+ .driver = {
+ .name = "mma7660",
+ .pm = pm_sleep_ptr(&mma7660_pm_ops),
+ .of_match_table = mma7660_of_match,
+ .acpi_match_table = mma7660_acpi_id,
+ },
+ .probe = mma7660_probe,
+ .remove = mma7660_remove,
+ .id_table = mma7660_i2c_id,
+};
+
+module_i2c_driver(mma7660_driver);
+
+MODULE_AUTHOR("Constantin Musca <constantin.musca@intel.com>");
+MODULE_DESCRIPTION("Freescale MMA7660FC 3-Axis Accelerometer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c
new file mode 100644
index 000000000..7d683686d
--- /dev/null
+++ b/drivers/iio/accel/mma8452.c
@@ -0,0 +1,1855 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mma8452.c - Support for following Freescale / NXP 3-axis accelerometers:
+ *
+ * device name digital output 7-bit I2C slave address (pin selectable)
+ * ---------------------------------------------------------------------
+ * MMA8451Q 14 bit 0x1c / 0x1d
+ * MMA8452Q 12 bit 0x1c / 0x1d
+ * MMA8453Q 10 bit 0x1c / 0x1d
+ * MMA8652FC 12 bit 0x1d
+ * MMA8653FC 10 bit 0x1d
+ * FXLS8471Q 14 bit 0x1e / 0x1d / 0x1c / 0x1f
+ *
+ * Copyright 2015 Martin Kepplinger <martink@posteo.de>
+ * Copyright 2014 Peter Meerwald <pmeerw@pmeerw.net>
+ *
+ *
+ * TODO: orientation events
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/types.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define MMA8452_STATUS 0x00
+#define MMA8452_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0))
+#define MMA8452_OUT_X 0x01 /* MSB first */
+#define MMA8452_OUT_Y 0x03
+#define MMA8452_OUT_Z 0x05
+#define MMA8452_INT_SRC 0x0c
+#define MMA8452_WHO_AM_I 0x0d
+#define MMA8452_DATA_CFG 0x0e
+#define MMA8452_DATA_CFG_FS_MASK GENMASK(1, 0)
+#define MMA8452_DATA_CFG_FS_2G 0
+#define MMA8452_DATA_CFG_FS_4G 1
+#define MMA8452_DATA_CFG_FS_8G 2
+#define MMA8452_DATA_CFG_HPF_MASK BIT(4)
+#define MMA8452_HP_FILTER_CUTOFF 0x0f
+#define MMA8452_HP_FILTER_CUTOFF_SEL_MASK GENMASK(1, 0)
+#define MMA8452_FF_MT_CFG 0x15
+#define MMA8452_FF_MT_CFG_OAE BIT(6)
+#define MMA8452_FF_MT_CFG_ELE BIT(7)
+#define MMA8452_FF_MT_SRC 0x16
+#define MMA8452_FF_MT_SRC_XHE BIT(1)
+#define MMA8452_FF_MT_SRC_YHE BIT(3)
+#define MMA8452_FF_MT_SRC_ZHE BIT(5)
+#define MMA8452_FF_MT_THS 0x17
+#define MMA8452_FF_MT_THS_MASK 0x7f
+#define MMA8452_FF_MT_COUNT 0x18
+#define MMA8452_FF_MT_CHAN_SHIFT 3
+#define MMA8452_TRANSIENT_CFG 0x1d
+#define MMA8452_TRANSIENT_CFG_CHAN(chan) BIT(chan + 1)
+#define MMA8452_TRANSIENT_CFG_HPF_BYP BIT(0)
+#define MMA8452_TRANSIENT_CFG_ELE BIT(4)
+#define MMA8452_TRANSIENT_SRC 0x1e
+#define MMA8452_TRANSIENT_SRC_XTRANSE BIT(1)
+#define MMA8452_TRANSIENT_SRC_YTRANSE BIT(3)
+#define MMA8452_TRANSIENT_SRC_ZTRANSE BIT(5)
+#define MMA8452_TRANSIENT_THS 0x1f
+#define MMA8452_TRANSIENT_THS_MASK GENMASK(6, 0)
+#define MMA8452_TRANSIENT_COUNT 0x20
+#define MMA8452_TRANSIENT_CHAN_SHIFT 1
+#define MMA8452_CTRL_REG1 0x2a
+#define MMA8452_CTRL_ACTIVE BIT(0)
+#define MMA8452_CTRL_DR_MASK GENMASK(5, 3)
+#define MMA8452_CTRL_DR_SHIFT 3
+#define MMA8452_CTRL_DR_DEFAULT 0x4 /* 50 Hz sample frequency */
+#define MMA8452_CTRL_REG2 0x2b
+#define MMA8452_CTRL_REG2_RST BIT(6)
+#define MMA8452_CTRL_REG2_MODS_SHIFT 3
+#define MMA8452_CTRL_REG2_MODS_MASK 0x1b
+#define MMA8452_CTRL_REG4 0x2d
+#define MMA8452_CTRL_REG5 0x2e
+#define MMA8452_OFF_X 0x2f
+#define MMA8452_OFF_Y 0x30
+#define MMA8452_OFF_Z 0x31
+
+#define MMA8452_MAX_REG 0x31
+
+#define MMA8452_INT_DRDY BIT(0)
+#define MMA8452_INT_FF_MT BIT(2)
+#define MMA8452_INT_TRANS BIT(5)
+
+#define MMA8451_DEVICE_ID 0x1a
+#define MMA8452_DEVICE_ID 0x2a
+#define MMA8453_DEVICE_ID 0x3a
+#define MMA8652_DEVICE_ID 0x4a
+#define MMA8653_DEVICE_ID 0x5a
+#define FXLS8471_DEVICE_ID 0x6a
+
+#define MMA8452_AUTO_SUSPEND_DELAY_MS 2000
+
+struct mma8452_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct iio_mount_matrix orientation;
+ u8 ctrl_reg1;
+ u8 data_cfg;
+ const struct mma_chip_info *chip_info;
+ int sleep_val;
+ struct regulator *vdd_reg;
+ struct regulator *vddio_reg;
+
+ /* Ensure correct alignment of time stamp when present */
+ struct {
+ __be16 channels[3];
+ aligned_s64 ts;
+ } buffer;
+};
+
+ /**
+ * struct mma8452_event_regs - chip specific data related to events
+ * @ev_cfg: event config register address
+ * @ev_cfg_ele: latch bit in event config register
+ * @ev_cfg_chan_shift: number of the bit to enable events in X
+ * direction; in event config register
+ * @ev_src: event source register address
+ * @ev_ths: event threshold register address
+ * @ev_ths_mask: mask for the threshold value
+ * @ev_count: event count (period) register address
+ *
+ * Since not all chips supported by the driver support comparing high pass
+ * filtered data for events (interrupts), different interrupt sources are
+ * used for different chips and the relevant registers are included here.
+ */
+struct mma8452_event_regs {
+ u8 ev_cfg;
+ u8 ev_cfg_ele;
+ u8 ev_cfg_chan_shift;
+ u8 ev_src;
+ u8 ev_ths;
+ u8 ev_ths_mask;
+ u8 ev_count;
+};
+
+static const struct mma8452_event_regs ff_mt_ev_regs = {
+ .ev_cfg = MMA8452_FF_MT_CFG,
+ .ev_cfg_ele = MMA8452_FF_MT_CFG_ELE,
+ .ev_cfg_chan_shift = MMA8452_FF_MT_CHAN_SHIFT,
+ .ev_src = MMA8452_FF_MT_SRC,
+ .ev_ths = MMA8452_FF_MT_THS,
+ .ev_ths_mask = MMA8452_FF_MT_THS_MASK,
+ .ev_count = MMA8452_FF_MT_COUNT
+};
+
+static const struct mma8452_event_regs trans_ev_regs = {
+ .ev_cfg = MMA8452_TRANSIENT_CFG,
+ .ev_cfg_ele = MMA8452_TRANSIENT_CFG_ELE,
+ .ev_cfg_chan_shift = MMA8452_TRANSIENT_CHAN_SHIFT,
+ .ev_src = MMA8452_TRANSIENT_SRC,
+ .ev_ths = MMA8452_TRANSIENT_THS,
+ .ev_ths_mask = MMA8452_TRANSIENT_THS_MASK,
+ .ev_count = MMA8452_TRANSIENT_COUNT,
+};
+
+/**
+ * struct mma_chip_info - chip specific data
+ * @name: part number of device reported via 'name' attr
+ * @chip_id: WHO_AM_I register's value
+ * @channels: struct iio_chan_spec matching the device's
+ * capabilities
+ * @num_channels: number of channels
+ * @mma_scales: scale factors for converting register values
+ * to m/s^2; 3 modes: 2g, 4g, 8g; 2 integers
+ * per mode: m/s^2 and micro m/s^2
+ * @all_events: all events supported by this chip
+ * @enabled_events: event flags enabled and handled by this driver
+ */
+struct mma_chip_info {
+ const char *name;
+ u8 chip_id;
+ const struct iio_chan_spec *channels;
+ int num_channels;
+ const int mma_scales[3][2];
+ int all_events;
+ int enabled_events;
+};
+
+enum {
+ idx_x,
+ idx_y,
+ idx_z,
+ idx_ts,
+};
+
+static int mma8452_drdy(struct mma8452_data *data)
+{
+ int tries = 150;
+
+ while (tries-- > 0) {
+ int ret = i2c_smbus_read_byte_data(data->client,
+ MMA8452_STATUS);
+ if (ret < 0)
+ return ret;
+ if ((ret & MMA8452_STATUS_DRDY) == MMA8452_STATUS_DRDY)
+ return 0;
+
+ if (data->sleep_val <= 20)
+ usleep_range(data->sleep_val * 250,
+ data->sleep_val * 500);
+ else
+ msleep(20);
+ }
+
+ dev_err(&data->client->dev, "data not ready\n");
+
+ return -EIO;
+}
+
+static int mma8452_set_runtime_pm_state(struct i2c_client *client, bool on)
+{
+#ifdef CONFIG_PM
+ int ret;
+
+ if (on)
+ ret = pm_runtime_resume_and_get(&client->dev);
+ else
+ ret = pm_runtime_put_autosuspend(&client->dev);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "failed to change power state to %d\n", on);
+
+ return ret;
+ }
+#endif
+
+ return 0;
+}
+
+static int mma8452_read(struct mma8452_data *data, __be16 buf[3])
+{
+ int ret = mma8452_drdy(data);
+
+ if (ret < 0)
+ return ret;
+
+ ret = mma8452_set_runtime_pm_state(data->client, true);
+ if (ret)
+ return ret;
+
+ ret = i2c_smbus_read_i2c_block_data(data->client, MMA8452_OUT_X,
+ 3 * sizeof(__be16), (u8 *)buf);
+ if (ret < 0)
+ return ret;
+
+ ret = mma8452_set_runtime_pm_state(data->client, false);
+
+ return ret;
+}
+
+static ssize_t mma8452_show_int_plus_micros(char *buf, const int (*vals)[2],
+ int n)
+{
+ size_t len = 0;
+
+ while (n-- > 0)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ",
+ vals[n][0], vals[n][1]);
+
+ /* replace trailing space by newline */
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static int mma8452_get_int_plus_micros_index(const int (*vals)[2], int n,
+ int val, int val2)
+{
+ while (n-- > 0)
+ if (val == vals[n][0] && val2 == vals[n][1])
+ return n;
+
+ return -EINVAL;
+}
+
+static unsigned int mma8452_get_odr_index(struct mma8452_data *data)
+{
+ return (data->ctrl_reg1 & MMA8452_CTRL_DR_MASK) >>
+ MMA8452_CTRL_DR_SHIFT;
+}
+
+static const int mma8452_samp_freq[8][2] = {
+ {800, 0}, {400, 0}, {200, 0}, {100, 0}, {50, 0}, {12, 500000},
+ {6, 250000}, {1, 560000}
+};
+
+/* Datasheet table: step time "Relationship with the ODR" (sample frequency) */
+static const unsigned int mma8452_time_step_us[4][8] = {
+ { 1250, 2500, 5000, 10000, 20000, 20000, 20000, 20000 }, /* normal */
+ { 1250, 2500, 5000, 10000, 20000, 80000, 80000, 80000 }, /* l p l n */
+ { 1250, 2500, 2500, 2500, 2500, 2500, 2500, 2500 }, /* high res*/
+ { 1250, 2500, 5000, 10000, 20000, 80000, 160000, 160000 } /* l p */
+};
+
+/* Datasheet table "High-Pass Filter Cutoff Options" */
+static const int mma8452_hp_filter_cutoff[4][8][4][2] = {
+ { /* normal */
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, /* 800 Hz sample */
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, /* 400 Hz sample */
+ { {8, 0}, {4, 0}, {2, 0}, {1, 0} }, /* 200 Hz sample */
+ { {4, 0}, {2, 0}, {1, 0}, {0, 500000} }, /* 100 Hz sample */
+ { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} }, /* 50 Hz sample */
+ { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} }, /* 12.5 Hz sample */
+ { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} }, /* 6.25 Hz sample */
+ { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} } /* 1.56 Hz sample */
+ },
+ { /* low noise low power */
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} },
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} },
+ { {8, 0}, {4, 0}, {2, 0}, {1, 0} },
+ { {4, 0}, {2, 0}, {1, 0}, {0, 500000} },
+ { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} },
+ { {0, 500000}, {0, 250000}, {0, 125000}, {0, 063000} },
+ { {0, 500000}, {0, 250000}, {0, 125000}, {0, 063000} },
+ { {0, 500000}, {0, 250000}, {0, 125000}, {0, 063000} }
+ },
+ { /* high resolution */
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} },
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} },
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} },
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} },
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} },
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} },
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} },
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} }
+ },
+ { /* low power */
+ { {16, 0}, {8, 0}, {4, 0}, {2, 0} },
+ { {8, 0}, {4, 0}, {2, 0}, {1, 0} },
+ { {4, 0}, {2, 0}, {1, 0}, {0, 500000} },
+ { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} },
+ { {1, 0}, {0, 500000}, {0, 250000}, {0, 125000} },
+ { {0, 250000}, {0, 125000}, {0, 063000}, {0, 031000} },
+ { {0, 250000}, {0, 125000}, {0, 063000}, {0, 031000} },
+ { {0, 250000}, {0, 125000}, {0, 063000}, {0, 031000} }
+ }
+};
+
+/* Datasheet table "MODS Oversampling modes averaging values at each ODR" */
+static const u16 mma8452_os_ratio[4][8] = {
+ /* 800 Hz, 400 Hz, ... , 1.56 Hz */
+ { 2, 4, 4, 4, 4, 16, 32, 128 }, /* normal */
+ { 2, 4, 4, 4, 4, 4, 8, 32 }, /* low power low noise */
+ { 2, 4, 8, 16, 32, 128, 256, 1024 }, /* high resolution */
+ { 2, 2, 2, 2, 2, 2, 4, 16 } /* low power */
+};
+
+static int mma8452_get_power_mode(struct mma8452_data *data)
+{
+ int reg;
+
+ reg = i2c_smbus_read_byte_data(data->client,
+ MMA8452_CTRL_REG2);
+ if (reg < 0)
+ return reg;
+
+ return ((reg & MMA8452_CTRL_REG2_MODS_MASK) >>
+ MMA8452_CTRL_REG2_MODS_SHIFT);
+}
+
+static ssize_t mma8452_show_samp_freq_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return mma8452_show_int_plus_micros(buf, mma8452_samp_freq,
+ ARRAY_SIZE(mma8452_samp_freq));
+}
+
+static ssize_t mma8452_show_scale_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct mma8452_data *data = iio_priv(indio_dev);
+
+ return mma8452_show_int_plus_micros(buf, data->chip_info->mma_scales,
+ ARRAY_SIZE(data->chip_info->mma_scales));
+}
+
+static ssize_t mma8452_show_hp_cutoff_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct mma8452_data *data = iio_priv(indio_dev);
+ int i, j;
+
+ i = mma8452_get_odr_index(data);
+ j = mma8452_get_power_mode(data);
+ if (j < 0)
+ return j;
+
+ return mma8452_show_int_plus_micros(buf, mma8452_hp_filter_cutoff[j][i],
+ ARRAY_SIZE(mma8452_hp_filter_cutoff[0][0]));
+}
+
+static ssize_t mma8452_show_os_ratio_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct mma8452_data *data = iio_priv(indio_dev);
+ int i = mma8452_get_odr_index(data);
+ int j;
+ u16 val = 0;
+ size_t len = 0;
+
+ for (j = 0; j < ARRAY_SIZE(mma8452_os_ratio); j++) {
+ if (val == mma8452_os_ratio[j][i])
+ continue;
+
+ val = mma8452_os_ratio[j][i];
+
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", val);
+ }
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(mma8452_show_samp_freq_avail);
+static IIO_DEVICE_ATTR(in_accel_scale_available, 0444,
+ mma8452_show_scale_avail, NULL, 0);
+static IIO_DEVICE_ATTR(in_accel_filter_high_pass_3db_frequency_available,
+ 0444, mma8452_show_hp_cutoff_avail, NULL, 0);
+static IIO_DEVICE_ATTR(in_accel_oversampling_ratio_available, 0444,
+ mma8452_show_os_ratio_avail, NULL, 0);
+
+static int mma8452_get_samp_freq_index(struct mma8452_data *data,
+ int val, int val2)
+{
+ return mma8452_get_int_plus_micros_index(mma8452_samp_freq,
+ ARRAY_SIZE(mma8452_samp_freq),
+ val, val2);
+}
+
+static int mma8452_get_scale_index(struct mma8452_data *data, int val, int val2)
+{
+ return mma8452_get_int_plus_micros_index(data->chip_info->mma_scales,
+ ARRAY_SIZE(data->chip_info->mma_scales), val, val2);
+}
+
+static int mma8452_get_hp_filter_index(struct mma8452_data *data,
+ int val, int val2)
+{
+ int i, j;
+
+ i = mma8452_get_odr_index(data);
+ j = mma8452_get_power_mode(data);
+ if (j < 0)
+ return j;
+
+ return mma8452_get_int_plus_micros_index(mma8452_hp_filter_cutoff[j][i],
+ ARRAY_SIZE(mma8452_hp_filter_cutoff[0][0]), val, val2);
+}
+
+static int mma8452_read_hp_filter(struct mma8452_data *data, int *hz, int *uHz)
+{
+ int j, i, ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, MMA8452_HP_FILTER_CUTOFF);
+ if (ret < 0)
+ return ret;
+
+ i = mma8452_get_odr_index(data);
+ j = mma8452_get_power_mode(data);
+ if (j < 0)
+ return j;
+
+ ret &= MMA8452_HP_FILTER_CUTOFF_SEL_MASK;
+ *hz = mma8452_hp_filter_cutoff[j][i][ret][0];
+ *uHz = mma8452_hp_filter_cutoff[j][i][ret][1];
+
+ return 0;
+}
+
+static int mma8452_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mma8452_data *data = iio_priv(indio_dev);
+ __be16 buffer[3];
+ int i, ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ mutex_lock(&data->lock);
+ ret = mma8452_read(data, buffer);
+ mutex_unlock(&data->lock);
+ iio_device_release_direct(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ *val = sign_extend32(be16_to_cpu(
+ buffer[chan->scan_index]) >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ i = data->data_cfg & MMA8452_DATA_CFG_FS_MASK;
+ *val = data->chip_info->mma_scales[i][0];
+ *val2 = data->chip_info->mma_scales[i][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ i = mma8452_get_odr_index(data);
+ *val = mma8452_samp_freq[i][0];
+ *val2 = mma8452_samp_freq[i][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ ret = i2c_smbus_read_byte_data(data->client,
+ MMA8452_OFF_X +
+ chan->scan_index);
+ if (ret < 0)
+ return ret;
+
+ *val = sign_extend32(ret, 7);
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+ if (data->data_cfg & MMA8452_DATA_CFG_HPF_MASK) {
+ ret = mma8452_read_hp_filter(data, val, val2);
+ if (ret < 0)
+ return ret;
+ } else {
+ *val = 0;
+ *val2 = 0;
+ }
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ ret = mma8452_get_power_mode(data);
+ if (ret < 0)
+ return ret;
+
+ i = mma8452_get_odr_index(data);
+
+ *val = mma8452_os_ratio[ret][i];
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int mma8452_calculate_sleep(struct mma8452_data *data)
+{
+ int ret, i = mma8452_get_odr_index(data);
+
+ if (mma8452_samp_freq[i][0] > 0)
+ ret = 1000 / mma8452_samp_freq[i][0];
+ else
+ ret = 1000;
+
+ return ret == 0 ? 1 : ret;
+}
+
+static int mma8452_standby(struct mma8452_data *data)
+{
+ return i2c_smbus_write_byte_data(data->client, MMA8452_CTRL_REG1,
+ data->ctrl_reg1 & ~MMA8452_CTRL_ACTIVE);
+}
+
+static int mma8452_active(struct mma8452_data *data)
+{
+ return i2c_smbus_write_byte_data(data->client, MMA8452_CTRL_REG1,
+ data->ctrl_reg1);
+}
+
+/* returns >0 if active, 0 if in standby and <0 on error */
+static int mma8452_is_active(struct mma8452_data *data)
+{
+ int reg;
+
+ reg = i2c_smbus_read_byte_data(data->client, MMA8452_CTRL_REG1);
+ if (reg < 0)
+ return reg;
+
+ return reg & MMA8452_CTRL_ACTIVE;
+}
+
+static int mma8452_change_config(struct mma8452_data *data, u8 reg, u8 val)
+{
+ int ret;
+ int is_active;
+
+ mutex_lock(&data->lock);
+
+ is_active = mma8452_is_active(data);
+ if (is_active < 0) {
+ ret = is_active;
+ goto fail;
+ }
+
+ /* config can only be changed when in standby */
+ if (is_active > 0) {
+ ret = mma8452_standby(data);
+ if (ret < 0)
+ goto fail;
+ }
+
+ ret = i2c_smbus_write_byte_data(data->client, reg, val);
+ if (ret < 0)
+ goto fail;
+
+ if (is_active > 0) {
+ ret = mma8452_active(data);
+ if (ret < 0)
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int mma8452_set_power_mode(struct mma8452_data *data, u8 mode)
+{
+ int reg;
+
+ reg = i2c_smbus_read_byte_data(data->client,
+ MMA8452_CTRL_REG2);
+ if (reg < 0)
+ return reg;
+
+ reg &= ~MMA8452_CTRL_REG2_MODS_MASK;
+ reg |= mode << MMA8452_CTRL_REG2_MODS_SHIFT;
+
+ return mma8452_change_config(data, MMA8452_CTRL_REG2, reg);
+}
+
+/* returns >0 if in freefall mode, 0 if not or <0 if an error occurred */
+static int mma8452_freefall_mode_enabled(struct mma8452_data *data)
+{
+ int val;
+
+ val = i2c_smbus_read_byte_data(data->client, MMA8452_FF_MT_CFG);
+ if (val < 0)
+ return val;
+
+ return !(val & MMA8452_FF_MT_CFG_OAE);
+}
+
+static int mma8452_set_freefall_mode(struct mma8452_data *data, bool state)
+{
+ int val;
+
+ if ((state && mma8452_freefall_mode_enabled(data)) ||
+ (!state && !(mma8452_freefall_mode_enabled(data))))
+ return 0;
+
+ val = i2c_smbus_read_byte_data(data->client, MMA8452_FF_MT_CFG);
+ if (val < 0)
+ return val;
+
+ if (state) {
+ val |= BIT(idx_x + MMA8452_FF_MT_CHAN_SHIFT);
+ val |= BIT(idx_y + MMA8452_FF_MT_CHAN_SHIFT);
+ val |= BIT(idx_z + MMA8452_FF_MT_CHAN_SHIFT);
+ val &= ~MMA8452_FF_MT_CFG_OAE;
+ } else {
+ val &= ~BIT(idx_x + MMA8452_FF_MT_CHAN_SHIFT);
+ val &= ~BIT(idx_y + MMA8452_FF_MT_CHAN_SHIFT);
+ val &= ~BIT(idx_z + MMA8452_FF_MT_CHAN_SHIFT);
+ val |= MMA8452_FF_MT_CFG_OAE;
+ }
+
+ return mma8452_change_config(data, MMA8452_FF_MT_CFG, val);
+}
+
+static int mma8452_set_hp_filter_frequency(struct mma8452_data *data,
+ int val, int val2)
+{
+ int i, reg;
+
+ i = mma8452_get_hp_filter_index(data, val, val2);
+ if (i < 0)
+ return i;
+
+ reg = i2c_smbus_read_byte_data(data->client,
+ MMA8452_HP_FILTER_CUTOFF);
+ if (reg < 0)
+ return reg;
+
+ reg &= ~MMA8452_HP_FILTER_CUTOFF_SEL_MASK;
+ reg |= i;
+
+ return mma8452_change_config(data, MMA8452_HP_FILTER_CUTOFF, reg);
+}
+
+static int __mma8452_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mma8452_data *data = iio_priv(indio_dev);
+ int i, j, ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ i = mma8452_get_samp_freq_index(data, val, val2);
+ if (i < 0)
+ return i;
+
+ data->ctrl_reg1 &= ~MMA8452_CTRL_DR_MASK;
+ data->ctrl_reg1 |= i << MMA8452_CTRL_DR_SHIFT;
+
+ data->sleep_val = mma8452_calculate_sleep(data);
+
+ return mma8452_change_config(data, MMA8452_CTRL_REG1,
+ data->ctrl_reg1);
+
+ case IIO_CHAN_INFO_SCALE:
+ i = mma8452_get_scale_index(data, val, val2);
+ if (i < 0)
+ return i;
+
+ data->data_cfg &= ~MMA8452_DATA_CFG_FS_MASK;
+ data->data_cfg |= i;
+
+ return mma8452_change_config(data, MMA8452_DATA_CFG,
+ data->data_cfg);
+
+ case IIO_CHAN_INFO_CALIBBIAS:
+ if (val < -128 || val > 127)
+ return -EINVAL;
+
+ return mma8452_change_config(data,
+ MMA8452_OFF_X + chan->scan_index,
+ val);
+
+ case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
+ if (val == 0 && val2 == 0) {
+ data->data_cfg &= ~MMA8452_DATA_CFG_HPF_MASK;
+ } else {
+ data->data_cfg |= MMA8452_DATA_CFG_HPF_MASK;
+ ret = mma8452_set_hp_filter_frequency(data, val, val2);
+ if (ret < 0)
+ return ret;
+ }
+
+ return mma8452_change_config(data, MMA8452_DATA_CFG,
+ data->data_cfg);
+
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ j = mma8452_get_odr_index(data);
+
+ for (i = 0; i < ARRAY_SIZE(mma8452_os_ratio); i++) {
+ if (mma8452_os_ratio[i][j] == val)
+ return mma8452_set_power_mode(data, i);
+ }
+
+ return -EINVAL;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma8452_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int ret;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ ret = __mma8452_write_raw(indio_dev, chan, val, val2, mask);
+ iio_device_release_direct(indio_dev);
+ return ret;
+}
+
+static int mma8452_get_event_regs(struct mma8452_data *data,
+ const struct iio_chan_spec *chan,
+ enum iio_event_direction dir,
+ const struct mma8452_event_regs **ev_reg)
+{
+ if (!chan)
+ return -EINVAL;
+
+ switch (chan->type) {
+ case IIO_ACCEL:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ if ((data->chip_info->all_events
+ & MMA8452_INT_TRANS) &&
+ (data->chip_info->enabled_events
+ & MMA8452_INT_TRANS))
+ *ev_reg = &trans_ev_regs;
+ else
+ *ev_reg = &ff_mt_ev_regs;
+ return 0;
+ case IIO_EV_DIR_FALLING:
+ *ev_reg = &ff_mt_ev_regs;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma8452_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct mma8452_data *data = iio_priv(indio_dev);
+ int ret, us, power_mode;
+ const struct mma8452_event_regs *ev_regs;
+
+ ret = mma8452_get_event_regs(data, chan, dir, &ev_regs);
+ if (ret)
+ return ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = i2c_smbus_read_byte_data(data->client, ev_regs->ev_ths);
+ if (ret < 0)
+ return ret;
+
+ *val = ret & ev_regs->ev_ths_mask;
+
+ return IIO_VAL_INT;
+
+ case IIO_EV_INFO_PERIOD:
+ ret = i2c_smbus_read_byte_data(data->client, ev_regs->ev_count);
+ if (ret < 0)
+ return ret;
+
+ power_mode = mma8452_get_power_mode(data);
+ if (power_mode < 0)
+ return power_mode;
+
+ us = ret * mma8452_time_step_us[power_mode][
+ mma8452_get_odr_index(data)];
+ *val = us / USEC_PER_SEC;
+ *val2 = us % USEC_PER_SEC;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+
+ case IIO_EV_INFO_HIGH_PASS_FILTER_3DB:
+ ret = i2c_smbus_read_byte_data(data->client,
+ MMA8452_TRANSIENT_CFG);
+ if (ret < 0)
+ return ret;
+
+ if (ret & MMA8452_TRANSIENT_CFG_HPF_BYP) {
+ *val = 0;
+ *val2 = 0;
+ } else {
+ ret = mma8452_read_hp_filter(data, val, val2);
+ if (ret < 0)
+ return ret;
+ }
+
+ return IIO_VAL_INT_PLUS_MICRO;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma8452_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct mma8452_data *data = iio_priv(indio_dev);
+ int ret, reg, steps;
+ const struct mma8452_event_regs *ev_regs;
+
+ ret = mma8452_get_event_regs(data, chan, dir, &ev_regs);
+ if (ret)
+ return ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ if (val < 0 || val > ev_regs->ev_ths_mask)
+ return -EINVAL;
+
+ return mma8452_change_config(data, ev_regs->ev_ths, val);
+
+ case IIO_EV_INFO_PERIOD:
+ ret = mma8452_get_power_mode(data);
+ if (ret < 0)
+ return ret;
+
+ steps = (val * USEC_PER_SEC + val2) /
+ mma8452_time_step_us[ret][
+ mma8452_get_odr_index(data)];
+
+ if (steps < 0 || steps > 0xff)
+ return -EINVAL;
+
+ return mma8452_change_config(data, ev_regs->ev_count, steps);
+
+ case IIO_EV_INFO_HIGH_PASS_FILTER_3DB:
+ reg = i2c_smbus_read_byte_data(data->client,
+ MMA8452_TRANSIENT_CFG);
+ if (reg < 0)
+ return reg;
+
+ if (val == 0 && val2 == 0) {
+ reg |= MMA8452_TRANSIENT_CFG_HPF_BYP;
+ } else {
+ reg &= ~MMA8452_TRANSIENT_CFG_HPF_BYP;
+ ret = mma8452_set_hp_filter_frequency(data, val, val2);
+ if (ret < 0)
+ return ret;
+ }
+
+ return mma8452_change_config(data, MMA8452_TRANSIENT_CFG, reg);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma8452_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 mma8452_data *data = iio_priv(indio_dev);
+ int ret;
+ const struct mma8452_event_regs *ev_regs;
+
+ ret = mma8452_get_event_regs(data, chan, dir, &ev_regs);
+ if (ret)
+ return ret;
+
+ switch (dir) {
+ case IIO_EV_DIR_FALLING:
+ return mma8452_freefall_mode_enabled(data);
+ case IIO_EV_DIR_RISING:
+ ret = i2c_smbus_read_byte_data(data->client, ev_regs->ev_cfg);
+ if (ret < 0)
+ return ret;
+
+ return !!(ret & BIT(chan->scan_index +
+ ev_regs->ev_cfg_chan_shift));
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma8452_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct mma8452_data *data = iio_priv(indio_dev);
+ int val, ret;
+ const struct mma8452_event_regs *ev_regs;
+
+ ret = mma8452_get_event_regs(data, chan, dir, &ev_regs);
+ if (ret)
+ return ret;
+
+ ret = mma8452_set_runtime_pm_state(data->client, state);
+ if (ret)
+ return ret;
+
+ switch (dir) {
+ case IIO_EV_DIR_FALLING:
+ return mma8452_set_freefall_mode(data, state);
+ case IIO_EV_DIR_RISING:
+ val = i2c_smbus_read_byte_data(data->client, ev_regs->ev_cfg);
+ if (val < 0)
+ return val;
+
+ if (state) {
+ if (mma8452_freefall_mode_enabled(data)) {
+ val &= ~BIT(idx_x + ev_regs->ev_cfg_chan_shift);
+ val &= ~BIT(idx_y + ev_regs->ev_cfg_chan_shift);
+ val &= ~BIT(idx_z + ev_regs->ev_cfg_chan_shift);
+ val |= MMA8452_FF_MT_CFG_OAE;
+ }
+ val |= BIT(chan->scan_index +
+ ev_regs->ev_cfg_chan_shift);
+ } else {
+ if (mma8452_freefall_mode_enabled(data))
+ return 0;
+
+ val &= ~BIT(chan->scan_index +
+ ev_regs->ev_cfg_chan_shift);
+ }
+
+ val |= ev_regs->ev_cfg_ele;
+
+ return mma8452_change_config(data, ev_regs->ev_cfg, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static void mma8452_transient_interrupt(struct iio_dev *indio_dev)
+{
+ struct mma8452_data *data = iio_priv(indio_dev);
+ s64 ts = iio_get_time_ns(indio_dev);
+ int src;
+
+ src = i2c_smbus_read_byte_data(data->client, MMA8452_TRANSIENT_SRC);
+ if (src < 0)
+ return;
+
+ if (src & MMA8452_TRANSIENT_SRC_XTRANSE)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ ts);
+
+ if (src & MMA8452_TRANSIENT_SRC_YTRANSE)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_Y,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ ts);
+
+ if (src & MMA8452_TRANSIENT_SRC_ZTRANSE)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ ts);
+}
+
+static irqreturn_t mma8452_interrupt(int irq, void *p)
+{
+ struct iio_dev *indio_dev = p;
+ struct mma8452_data *data = iio_priv(indio_dev);
+ irqreturn_t ret = IRQ_NONE;
+ int src;
+
+ src = i2c_smbus_read_byte_data(data->client, MMA8452_INT_SRC);
+ if (src < 0)
+ return IRQ_NONE;
+
+ if (!(src & (data->chip_info->enabled_events | MMA8452_INT_DRDY)))
+ return IRQ_NONE;
+
+ if (src & MMA8452_INT_DRDY) {
+ iio_trigger_poll_nested(indio_dev->trig);
+ ret = IRQ_HANDLED;
+ }
+
+ if (src & MMA8452_INT_FF_MT) {
+ if (mma8452_freefall_mode_enabled(data)) {
+ s64 ts = iio_get_time_ns(indio_dev);
+
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_FALLING),
+ ts);
+ }
+ ret = IRQ_HANDLED;
+ }
+
+ if (src & MMA8452_INT_TRANS) {
+ mma8452_transient_interrupt(indio_dev);
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static irqreturn_t mma8452_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct mma8452_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = mma8452_read(data, data->buffer.channels);
+ if (ret < 0)
+ goto done;
+
+ iio_push_to_buffers_with_ts(indio_dev, &data->buffer,
+ sizeof(data->buffer),
+ iio_get_time_ns(indio_dev));
+
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int mma8452_reg_access_dbg(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval,
+ unsigned int *readval)
+{
+ int ret;
+ struct mma8452_data *data = iio_priv(indio_dev);
+
+ if (reg > MMA8452_MAX_REG)
+ return -EINVAL;
+
+ if (!readval)
+ return mma8452_change_config(data, reg, writeval);
+
+ ret = i2c_smbus_read_byte_data(data->client, reg);
+ if (ret < 0)
+ return ret;
+
+ *readval = ret;
+
+ return 0;
+}
+
+static const struct iio_event_spec mma8452_freefall_event[] = {
+ {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD) |
+ BIT(IIO_EV_INFO_HIGH_PASS_FILTER_3DB)
+ },
+};
+
+static const struct iio_event_spec mma8652_freefall_event[] = {
+ {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD)
+ },
+};
+
+static const struct iio_event_spec mma8452_transient_event[] = {
+ {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD) |
+ BIT(IIO_EV_INFO_HIGH_PASS_FILTER_3DB)
+ },
+};
+
+static const struct iio_event_spec mma8452_motion_event[] = {
+ {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD)
+ },
+};
+
+/*
+ * Threshold is configured in fixed 8G/127 steps regardless of
+ * currently selected scale for measurement.
+ */
+static IIO_CONST_ATTR_NAMED(accel_transient_scale, in_accel_scale, "0.617742");
+
+static struct attribute *mma8452_event_attributes[] = {
+ &iio_const_attr_accel_transient_scale.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group mma8452_event_attribute_group = {
+ .attrs = mma8452_event_attributes,
+};
+
+static const struct iio_mount_matrix *
+mma8452_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct mma8452_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info mma8452_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, mma8452_get_mount_matrix),
+ { }
+};
+
+#define MMA8452_FREEFALL_CHANNEL(modifier) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = modifier, \
+ .scan_index = -1, \
+ .event_spec = mma8452_freefall_event, \
+ .num_event_specs = ARRAY_SIZE(mma8452_freefall_event), \
+}
+
+#define MMA8652_FREEFALL_CHANNEL(modifier) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = modifier, \
+ .scan_index = -1, \
+ .event_spec = mma8652_freefall_event, \
+ .num_event_specs = ARRAY_SIZE(mma8652_freefall_event), \
+}
+
+#define MMA8452_CHANNEL(axis, idx, bits) { \
+ .type = IIO_ACCEL, \
+ .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_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .scan_index = idx, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = (bits), \
+ .storagebits = 16, \
+ .shift = 16 - (bits), \
+ .endianness = IIO_BE, \
+ }, \
+ .event_spec = mma8452_transient_event, \
+ .num_event_specs = ARRAY_SIZE(mma8452_transient_event), \
+ .ext_info = mma8452_ext_info, \
+}
+
+#define MMA8652_CHANNEL(axis, idx, bits) { \
+ .type = IIO_ACCEL, \
+ .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_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .scan_index = idx, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = (bits), \
+ .storagebits = 16, \
+ .shift = 16 - (bits), \
+ .endianness = IIO_BE, \
+ }, \
+ .event_spec = mma8452_motion_event, \
+ .num_event_specs = ARRAY_SIZE(mma8452_motion_event), \
+ .ext_info = mma8452_ext_info, \
+}
+
+static const struct iio_chan_spec mma8451_channels[] = {
+ MMA8452_CHANNEL(X, idx_x, 14),
+ MMA8452_CHANNEL(Y, idx_y, 14),
+ MMA8452_CHANNEL(Z, idx_z, 14),
+ IIO_CHAN_SOFT_TIMESTAMP(idx_ts),
+ MMA8452_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z),
+};
+
+static const struct iio_chan_spec mma8452_channels[] = {
+ MMA8452_CHANNEL(X, idx_x, 12),
+ MMA8452_CHANNEL(Y, idx_y, 12),
+ MMA8452_CHANNEL(Z, idx_z, 12),
+ IIO_CHAN_SOFT_TIMESTAMP(idx_ts),
+ MMA8452_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z),
+};
+
+static const struct iio_chan_spec mma8453_channels[] = {
+ MMA8452_CHANNEL(X, idx_x, 10),
+ MMA8452_CHANNEL(Y, idx_y, 10),
+ MMA8452_CHANNEL(Z, idx_z, 10),
+ IIO_CHAN_SOFT_TIMESTAMP(idx_ts),
+ MMA8452_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z),
+};
+
+static const struct iio_chan_spec mma8652_channels[] = {
+ MMA8652_CHANNEL(X, idx_x, 12),
+ MMA8652_CHANNEL(Y, idx_y, 12),
+ MMA8652_CHANNEL(Z, idx_z, 12),
+ IIO_CHAN_SOFT_TIMESTAMP(idx_ts),
+ MMA8652_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z),
+};
+
+static const struct iio_chan_spec mma8653_channels[] = {
+ MMA8652_CHANNEL(X, idx_x, 10),
+ MMA8652_CHANNEL(Y, idx_y, 10),
+ MMA8652_CHANNEL(Z, idx_z, 10),
+ IIO_CHAN_SOFT_TIMESTAMP(idx_ts),
+ MMA8652_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z),
+};
+
+enum {
+ mma8451,
+ mma8452,
+ mma8453,
+ mma8652,
+ mma8653,
+ fxls8471,
+};
+
+static const struct mma_chip_info mma_chip_info_table[] = {
+ [mma8451] = {
+ .name = "mma8451",
+ .chip_id = MMA8451_DEVICE_ID,
+ .channels = mma8451_channels,
+ .num_channels = ARRAY_SIZE(mma8451_channels),
+ /*
+ * Hardware has fullscale of -2G, -4G, -8G corresponding to
+ * raw value -8192 for 14 bit, -2048 for 12 bit or -512 for 10
+ * bit.
+ * The userspace interface uses m/s^2 and we declare micro units
+ * So scale factor for 12 bit here is given by:
+ * g * N * 1000000 / 2048 for N = 2, 4, 8 and g=9.80665
+ */
+ .mma_scales = { {0, 2394}, {0, 4788}, {0, 9577} },
+ /*
+ * Although we enable the interrupt sources once and for
+ * all here the event detection itself is not enabled until
+ * userspace asks for it by mma8452_write_event_config()
+ */
+ .all_events = MMA8452_INT_DRDY |
+ MMA8452_INT_TRANS |
+ MMA8452_INT_FF_MT,
+ .enabled_events = MMA8452_INT_TRANS |
+ MMA8452_INT_FF_MT,
+ },
+ [mma8452] = {
+ .name = "mma8452",
+ .chip_id = MMA8452_DEVICE_ID,
+ .channels = mma8452_channels,
+ .num_channels = ARRAY_SIZE(mma8452_channels),
+ .mma_scales = { {0, 9577}, {0, 19154}, {0, 38307} },
+ /*
+ * Although we enable the interrupt sources once and for
+ * all here the event detection itself is not enabled until
+ * userspace asks for it by mma8452_write_event_config()
+ */
+ .all_events = MMA8452_INT_DRDY |
+ MMA8452_INT_TRANS |
+ MMA8452_INT_FF_MT,
+ .enabled_events = MMA8452_INT_TRANS |
+ MMA8452_INT_FF_MT,
+ },
+ [mma8453] = {
+ .name = "mma8453",
+ .chip_id = MMA8453_DEVICE_ID,
+ .channels = mma8453_channels,
+ .num_channels = ARRAY_SIZE(mma8453_channels),
+ .mma_scales = { {0, 38307}, {0, 76614}, {0, 153228} },
+ /*
+ * Although we enable the interrupt sources once and for
+ * all here the event detection itself is not enabled until
+ * userspace asks for it by mma8452_write_event_config()
+ */
+ .all_events = MMA8452_INT_DRDY |
+ MMA8452_INT_TRANS |
+ MMA8452_INT_FF_MT,
+ .enabled_events = MMA8452_INT_TRANS |
+ MMA8452_INT_FF_MT,
+ },
+ [mma8652] = {
+ .name = "mma8652",
+ .chip_id = MMA8652_DEVICE_ID,
+ .channels = mma8652_channels,
+ .num_channels = ARRAY_SIZE(mma8652_channels),
+ .mma_scales = { {0, 9577}, {0, 19154}, {0, 38307} },
+ .all_events = MMA8452_INT_DRDY |
+ MMA8452_INT_FF_MT,
+ .enabled_events = MMA8452_INT_FF_MT,
+ },
+ [mma8653] = {
+ .name = "mma8653",
+ .chip_id = MMA8653_DEVICE_ID,
+ .channels = mma8653_channels,
+ .num_channels = ARRAY_SIZE(mma8653_channels),
+ .mma_scales = { {0, 38307}, {0, 76614}, {0, 153228} },
+ /*
+ * Although we enable the interrupt sources once and for
+ * all here the event detection itself is not enabled until
+ * userspace asks for it by mma8452_write_event_config()
+ */
+ .all_events = MMA8452_INT_DRDY |
+ MMA8452_INT_FF_MT,
+ .enabled_events = MMA8452_INT_FF_MT,
+ },
+ [fxls8471] = {
+ .name = "fxls8471",
+ .chip_id = FXLS8471_DEVICE_ID,
+ .channels = mma8451_channels,
+ .num_channels = ARRAY_SIZE(mma8451_channels),
+ .mma_scales = { {0, 2394}, {0, 4788}, {0, 9577} },
+ /*
+ * Although we enable the interrupt sources once and for
+ * all here the event detection itself is not enabled until
+ * userspace asks for it by mma8452_write_event_config()
+ */
+ .all_events = MMA8452_INT_DRDY |
+ MMA8452_INT_TRANS |
+ MMA8452_INT_FF_MT,
+ .enabled_events = MMA8452_INT_TRANS |
+ MMA8452_INT_FF_MT,
+ },
+};
+
+static struct attribute *mma8452_attributes[] = {
+ &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+ &iio_dev_attr_in_accel_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_accel_filter_high_pass_3db_frequency_available.dev_attr.attr,
+ &iio_dev_attr_in_accel_oversampling_ratio_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group mma8452_group = {
+ .attrs = mma8452_attributes,
+};
+
+static const struct iio_info mma8452_info = {
+ .attrs = &mma8452_group,
+ .read_raw = &mma8452_read_raw,
+ .write_raw = &mma8452_write_raw,
+ .event_attrs = &mma8452_event_attribute_group,
+ .read_event_value = &mma8452_read_event_value,
+ .write_event_value = &mma8452_write_event_value,
+ .read_event_config = &mma8452_read_event_config,
+ .write_event_config = &mma8452_write_event_config,
+ .debugfs_reg_access = &mma8452_reg_access_dbg,
+};
+
+static const unsigned long mma8452_scan_masks[] = {0x7, 0};
+
+static int mma8452_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct mma8452_data *data = iio_priv(indio_dev);
+ int reg, ret;
+
+ ret = mma8452_set_runtime_pm_state(data->client, state);
+ if (ret)
+ return ret;
+
+ reg = i2c_smbus_read_byte_data(data->client, MMA8452_CTRL_REG4);
+ if (reg < 0)
+ return reg;
+
+ if (state)
+ reg |= MMA8452_INT_DRDY;
+ else
+ reg &= ~MMA8452_INT_DRDY;
+
+ return mma8452_change_config(data, MMA8452_CTRL_REG4, reg);
+}
+
+static const struct iio_trigger_ops mma8452_trigger_ops = {
+ .set_trigger_state = mma8452_data_rdy_trigger_set_state,
+ .validate_device = iio_trigger_validate_own_device,
+};
+
+static int mma8452_trigger_setup(struct iio_dev *indio_dev)
+{
+ struct mma8452_data *data = iio_priv(indio_dev);
+ struct iio_trigger *trig;
+ int ret;
+
+ trig = devm_iio_trigger_alloc(&data->client->dev, "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!trig)
+ return -ENOMEM;
+
+ trig->ops = &mma8452_trigger_ops;
+ iio_trigger_set_drvdata(trig, indio_dev);
+
+ ret = iio_trigger_register(trig);
+ if (ret)
+ return ret;
+
+ indio_dev->trig = iio_trigger_get(trig);
+
+ return 0;
+}
+
+static void mma8452_trigger_cleanup(struct iio_dev *indio_dev)
+{
+ if (indio_dev->trig)
+ iio_trigger_unregister(indio_dev->trig);
+}
+
+static int mma8452_reset(struct i2c_client *client)
+{
+ int i;
+ int ret;
+
+ /*
+ * Find on fxls8471, after config reset bit, it reset immediately,
+ * and will not give ACK, so here do not check the return value.
+ * The following code will read the reset register, and check whether
+ * this reset works.
+ */
+ i2c_smbus_write_byte_data(client,
+ MMA8452_CTRL_REG2,
+ MMA8452_CTRL_REG2_RST);
+
+ for (i = 0; i < 10; i++) {
+ usleep_range(100, 200);
+ ret = i2c_smbus_read_byte_data(client, MMA8452_CTRL_REG2);
+ if (ret == -EIO)
+ continue; /* I2C comm reset */
+ if (ret < 0)
+ return ret;
+ if (!(ret & MMA8452_CTRL_REG2_RST))
+ return 0;
+ }
+
+ return -ETIMEDOUT;
+}
+
+static const struct of_device_id mma8452_dt_ids[] = {
+ { .compatible = "fsl,fxls8471", .data = &mma_chip_info_table[fxls8471] },
+ { .compatible = "fsl,mma8451", .data = &mma_chip_info_table[mma8451] },
+ { .compatible = "fsl,mma8452", .data = &mma_chip_info_table[mma8452] },
+ { .compatible = "fsl,mma8453", .data = &mma_chip_info_table[mma8453] },
+ { .compatible = "fsl,mma8652", .data = &mma_chip_info_table[mma8652] },
+ { .compatible = "fsl,mma8653", .data = &mma_chip_info_table[mma8653] },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mma8452_dt_ids);
+
+static int mma8452_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct mma8452_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ mutex_init(&data->lock);
+
+ data->chip_info = i2c_get_match_data(client);
+ if (!data->chip_info)
+ return dev_err_probe(&client->dev, -ENODEV,
+ "unknown device model\n");
+
+ ret = iio_read_mount_matrix(&client->dev, &data->orientation);
+ if (ret)
+ return ret;
+
+ data->vdd_reg = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(data->vdd_reg))
+ return dev_err_probe(&client->dev, PTR_ERR(data->vdd_reg),
+ "failed to get VDD regulator!\n");
+
+ data->vddio_reg = devm_regulator_get(&client->dev, "vddio");
+ if (IS_ERR(data->vddio_reg))
+ return dev_err_probe(&client->dev, PTR_ERR(data->vddio_reg),
+ "failed to get VDDIO regulator!\n");
+
+ ret = regulator_enable(data->vdd_reg);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable VDD regulator!\n");
+
+ ret = regulator_enable(data->vddio_reg);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to enable VDDIO regulator!\n");
+ goto disable_regulator_vdd;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, MMA8452_WHO_AM_I);
+ if (ret < 0)
+ goto disable_regulators;
+
+ switch (ret) {
+ case MMA8451_DEVICE_ID:
+ case MMA8452_DEVICE_ID:
+ case MMA8453_DEVICE_ID:
+ case MMA8652_DEVICE_ID:
+ case MMA8653_DEVICE_ID:
+ case FXLS8471_DEVICE_ID:
+ if (ret == data->chip_info->chip_id)
+ break;
+ fallthrough;
+ default:
+ ret = -ENODEV;
+ goto disable_regulators;
+ }
+
+ dev_info(&client->dev, "registering %s accelerometer; ID 0x%x\n",
+ data->chip_info->name, data->chip_info->chip_id);
+
+ i2c_set_clientdata(client, indio_dev);
+ indio_dev->info = &mma8452_info;
+ indio_dev->name = data->chip_info->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = data->chip_info->channels;
+ indio_dev->num_channels = data->chip_info->num_channels;
+ indio_dev->available_scan_masks = mma8452_scan_masks;
+
+ ret = mma8452_reset(client);
+ if (ret < 0)
+ goto disable_regulators;
+
+ data->data_cfg = MMA8452_DATA_CFG_FS_2G;
+ ret = i2c_smbus_write_byte_data(client, MMA8452_DATA_CFG,
+ data->data_cfg);
+ if (ret < 0)
+ goto disable_regulators;
+
+ /*
+ * By default set transient threshold to max to avoid events if
+ * enabling without configuring threshold.
+ */
+ ret = i2c_smbus_write_byte_data(client, MMA8452_TRANSIENT_THS,
+ MMA8452_TRANSIENT_THS_MASK);
+ if (ret < 0)
+ goto disable_regulators;
+
+ if (client->irq) {
+ int irq2;
+
+ irq2 = fwnode_irq_get_byname(dev_fwnode(&client->dev), "INT2");
+
+ if (irq2 == client->irq) {
+ dev_dbg(&client->dev, "using interrupt line INT2\n");
+ } else {
+ ret = i2c_smbus_write_byte_data(client,
+ MMA8452_CTRL_REG5,
+ data->chip_info->all_events);
+ if (ret < 0)
+ goto disable_regulators;
+
+ dev_dbg(&client->dev, "using interrupt line INT1\n");
+ }
+
+ ret = i2c_smbus_write_byte_data(client,
+ MMA8452_CTRL_REG4,
+ data->chip_info->enabled_events);
+ if (ret < 0)
+ goto disable_regulators;
+
+ ret = mma8452_trigger_setup(indio_dev);
+ if (ret < 0)
+ goto disable_regulators;
+ }
+
+ data->ctrl_reg1 = MMA8452_CTRL_ACTIVE |
+ (MMA8452_CTRL_DR_DEFAULT << MMA8452_CTRL_DR_SHIFT);
+
+ data->sleep_val = mma8452_calculate_sleep(data);
+
+ ret = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG1,
+ data->ctrl_reg1);
+ if (ret < 0)
+ goto trigger_cleanup;
+
+ ret = iio_triggered_buffer_setup(indio_dev, NULL,
+ mma8452_trigger_handler, NULL);
+ if (ret < 0)
+ goto trigger_cleanup;
+
+ if (client->irq) {
+ ret = request_threaded_irq(client->irq, NULL, mma8452_interrupt,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ client->name, indio_dev);
+ if (ret)
+ goto buffer_cleanup;
+ }
+
+ ret = pm_runtime_set_active(&client->dev);
+ if (ret < 0)
+ goto free_irq;
+
+ pm_runtime_enable(&client->dev);
+ pm_runtime_set_autosuspend_delay(&client->dev,
+ MMA8452_AUTO_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(&client->dev);
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0)
+ goto free_irq;
+
+ ret = mma8452_set_freefall_mode(data, false);
+ if (ret < 0)
+ goto unregister_device;
+
+ return 0;
+
+unregister_device:
+ iio_device_unregister(indio_dev);
+
+free_irq:
+ if (client->irq)
+ free_irq(client->irq, indio_dev);
+
+buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+
+trigger_cleanup:
+ mma8452_trigger_cleanup(indio_dev);
+
+disable_regulators:
+ regulator_disable(data->vddio_reg);
+
+disable_regulator_vdd:
+ regulator_disable(data->vdd_reg);
+
+ return ret;
+}
+
+static void mma8452_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct mma8452_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ pm_runtime_disable(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+
+ if (client->irq)
+ free_irq(client->irq, indio_dev);
+
+ iio_triggered_buffer_cleanup(indio_dev);
+ mma8452_trigger_cleanup(indio_dev);
+ mma8452_standby(iio_priv(indio_dev));
+
+ regulator_disable(data->vddio_reg);
+ regulator_disable(data->vdd_reg);
+}
+
+#ifdef CONFIG_PM
+static int mma8452_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mma8452_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = mma8452_standby(data);
+ mutex_unlock(&data->lock);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "powering off device failed\n");
+ return -EAGAIN;
+ }
+
+ ret = regulator_disable(data->vddio_reg);
+ if (ret) {
+ dev_err(dev, "failed to disable VDDIO regulator\n");
+ return ret;
+ }
+
+ ret = regulator_disable(data->vdd_reg);
+ if (ret) {
+ dev_err(dev, "failed to disable VDD regulator\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mma8452_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mma8452_data *data = iio_priv(indio_dev);
+ int ret, sleep_val;
+
+ ret = regulator_enable(data->vdd_reg);
+ if (ret) {
+ dev_err(dev, "failed to enable VDD regulator\n");
+ return ret;
+ }
+
+ ret = regulator_enable(data->vddio_reg);
+ if (ret) {
+ dev_err(dev, "failed to enable VDDIO regulator\n");
+ regulator_disable(data->vdd_reg);
+ return ret;
+ }
+
+ ret = mma8452_active(data);
+ if (ret < 0)
+ goto runtime_resume_failed;
+
+ ret = mma8452_get_odr_index(data);
+ sleep_val = 1000 / mma8452_samp_freq[ret][0];
+ if (sleep_val < 20)
+ usleep_range(sleep_val * 1000, 20000);
+ else
+ msleep_interruptible(sleep_val);
+
+ return 0;
+
+runtime_resume_failed:
+ regulator_disable(data->vddio_reg);
+ regulator_disable(data->vdd_reg);
+
+ return ret;
+}
+#endif
+
+static const struct dev_pm_ops mma8452_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(mma8452_runtime_suspend,
+ mma8452_runtime_resume, NULL)
+};
+
+static const struct i2c_device_id mma8452_id[] = {
+ { .name = "fxls8471", .driver_data = (kernel_ulong_t)&mma_chip_info_table[fxls8471] },
+ { .name = "mma8451", .driver_data = (kernel_ulong_t)&mma_chip_info_table[mma8451] },
+ { .name = "mma8452", .driver_data = (kernel_ulong_t)&mma_chip_info_table[mma8452] },
+ { .name = "mma8453", .driver_data = (kernel_ulong_t)&mma_chip_info_table[mma8453] },
+ { .name = "mma8652", .driver_data = (kernel_ulong_t)&mma_chip_info_table[mma8652] },
+ { .name = "mma8653", .driver_data = (kernel_ulong_t)&mma_chip_info_table[mma8653] },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mma8452_id);
+
+static struct i2c_driver mma8452_driver = {
+ .driver = {
+ .name = "mma8452",
+ .of_match_table = mma8452_dt_ids,
+ .pm = &mma8452_pm_ops,
+ },
+ .probe = mma8452_probe,
+ .remove = mma8452_remove,
+ .id_table = mma8452_id,
+};
+module_i2c_driver(mma8452_driver);
+
+MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
+MODULE_DESCRIPTION("Freescale / NXP MMA8452 accelerometer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/mma9551.c b/drivers/iio/accel/mma9551.c
new file mode 100644
index 000000000..7e01427fd
--- /dev/null
+++ b/drivers/iio/accel/mma9551.c
@@ -0,0 +1,605 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Freescale MMA9551L Intelligent Motion-Sensing Platform driver
+ * Copyright (c) 2014, Intel Corporation.
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/pm_runtime.h>
+#include "mma9551_core.h"
+
+#define MMA9551_GPIO_COUNT 4
+
+/* Tilt application (inclination in IIO terms). */
+#define MMA9551_TILT_XZ_ANG_REG 0x00
+#define MMA9551_TILT_YZ_ANG_REG 0x01
+#define MMA9551_TILT_XY_ANG_REG 0x02
+#define MMA9551_TILT_ANGFLG BIT(7)
+#define MMA9551_TILT_QUAD_REG 0x03
+#define MMA9551_TILT_XY_QUAD_SHIFT 0
+#define MMA9551_TILT_YZ_QUAD_SHIFT 2
+#define MMA9551_TILT_XZ_QUAD_SHIFT 4
+#define MMA9551_TILT_CFG_REG 0x01
+#define MMA9551_TILT_ANG_THRESH_MASK GENMASK(3, 0)
+
+#define MMA9551_DEFAULT_SAMPLE_RATE 122 /* Hz */
+
+/* Tilt events are mapped to the first three GPIO pins. */
+enum mma9551_tilt_axis {
+ mma9551_x = 0,
+ mma9551_y,
+ mma9551_z,
+};
+
+struct mma9551_data {
+ struct i2c_client *client;
+ struct mutex mutex;
+ bool event_enabled[3];
+ int irqs[MMA9551_GPIO_COUNT];
+};
+
+static int mma9551_read_incli_chan(struct i2c_client *client,
+ const struct iio_chan_spec *chan,
+ int *val)
+{
+ u8 quad_shift, angle, quadrant;
+ u16 reg_addr;
+ int ret;
+
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ reg_addr = MMA9551_TILT_YZ_ANG_REG;
+ quad_shift = MMA9551_TILT_YZ_QUAD_SHIFT;
+ break;
+ case IIO_MOD_Y:
+ reg_addr = MMA9551_TILT_XZ_ANG_REG;
+ quad_shift = MMA9551_TILT_XZ_QUAD_SHIFT;
+ break;
+ case IIO_MOD_Z:
+ reg_addr = MMA9551_TILT_XY_ANG_REG;
+ quad_shift = MMA9551_TILT_XY_QUAD_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = mma9551_set_power_state(client, true);
+ if (ret < 0)
+ return ret;
+
+ ret = mma9551_read_status_byte(client, MMA9551_APPID_TILT,
+ reg_addr, &angle);
+ if (ret < 0)
+ goto out_poweroff;
+
+ ret = mma9551_read_status_byte(client, MMA9551_APPID_TILT,
+ MMA9551_TILT_QUAD_REG, &quadrant);
+ if (ret < 0)
+ goto out_poweroff;
+
+ angle &= ~MMA9551_TILT_ANGFLG;
+ quadrant = (quadrant >> quad_shift) & 0x03;
+
+ if (quadrant == 1 || quadrant == 3)
+ *val = 90 * (quadrant + 1) - angle;
+ else
+ *val = angle + 90 * quadrant;
+
+ ret = IIO_VAL_INT;
+
+out_poweroff:
+ mma9551_set_power_state(client, false);
+ return ret;
+}
+
+static int mma9551_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mma9551_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (chan->type) {
+ case IIO_INCLI:
+ mutex_lock(&data->mutex);
+ ret = mma9551_read_incli_chan(data->client, chan, val);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ mutex_lock(&data->mutex);
+ ret = mma9551_read_accel_chan(data->client,
+ chan, val, val2);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ return mma9551_read_accel_scale(val, val2);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma9551_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 mma9551_data *data = iio_priv(indio_dev);
+
+ switch (chan->type) {
+ case IIO_INCLI:
+ /* IIO counts axes from 1, because IIO_NO_MOD is 0. */
+ return data->event_enabled[chan->channel2 - 1];
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma9551_config_incli_event(struct iio_dev *indio_dev,
+ enum iio_modifier axis,
+ bool state)
+{
+ struct mma9551_data *data = iio_priv(indio_dev);
+ enum mma9551_tilt_axis mma_axis;
+ int ret;
+
+ /* IIO counts axes from 1, because IIO_NO_MOD is 0. */
+ mma_axis = axis - 1;
+
+ if (data->event_enabled[mma_axis] == state)
+ return 0;
+
+ if (!state) {
+ ret = mma9551_gpio_config(data->client,
+ (enum mma9551_gpio_pin)mma_axis,
+ MMA9551_APPID_NONE, 0, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = mma9551_set_power_state(data->client, false);
+ if (ret < 0)
+ return ret;
+ } else {
+ int bitnum;
+
+ /* Bit 7 of each angle register holds the angle flag. */
+ switch (axis) {
+ case IIO_MOD_X:
+ bitnum = 7 + 8 * MMA9551_TILT_YZ_ANG_REG;
+ break;
+ case IIO_MOD_Y:
+ bitnum = 7 + 8 * MMA9551_TILT_XZ_ANG_REG;
+ break;
+ case IIO_MOD_Z:
+ bitnum = 7 + 8 * MMA9551_TILT_XY_ANG_REG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+
+ ret = mma9551_set_power_state(data->client, true);
+ if (ret < 0)
+ return ret;
+
+ ret = mma9551_gpio_config(data->client,
+ (enum mma9551_gpio_pin)mma_axis,
+ MMA9551_APPID_TILT, bitnum, 0);
+ if (ret < 0) {
+ mma9551_set_power_state(data->client, false);
+ return ret;
+ }
+ }
+
+ data->event_enabled[mma_axis] = state;
+
+ return ret;
+}
+
+static int mma9551_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct mma9551_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (chan->type) {
+ case IIO_INCLI:
+ mutex_lock(&data->mutex);
+ ret = mma9551_config_incli_event(indio_dev,
+ chan->channel2, state);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma9551_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct mma9551_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (chan->type) {
+ case IIO_INCLI:
+ if (val2 != 0 || val < 1 || val > 10)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = mma9551_update_config_bits(data->client,
+ MMA9551_APPID_TILT,
+ MMA9551_TILT_CFG_REG,
+ MMA9551_TILT_ANG_THRESH_MASK,
+ val);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma9551_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct mma9551_data *data = iio_priv(indio_dev);
+ int ret;
+ u8 tmp;
+
+ switch (chan->type) {
+ case IIO_INCLI:
+ mutex_lock(&data->mutex);
+ ret = mma9551_read_config_byte(data->client,
+ MMA9551_APPID_TILT,
+ MMA9551_TILT_CFG_REG, &tmp);
+ mutex_unlock(&data->mutex);
+ if (ret < 0)
+ return ret;
+ *val = tmp & MMA9551_TILT_ANG_THRESH_MASK;
+ *val2 = 0;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_event_spec mma9551_incli_event = {
+ .type = IIO_EV_TYPE_ROC,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
+};
+
+#define MMA9551_INCLI_CHANNEL(axis) { \
+ .type = IIO_INCLI, \
+ .modified = 1, \
+ .channel2 = axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
+ .event_spec = &mma9551_incli_event, \
+ .num_event_specs = 1, \
+}
+
+static const struct iio_chan_spec mma9551_channels[] = {
+ MMA9551_ACCEL_CHANNEL(IIO_MOD_X),
+ MMA9551_ACCEL_CHANNEL(IIO_MOD_Y),
+ MMA9551_ACCEL_CHANNEL(IIO_MOD_Z),
+
+ MMA9551_INCLI_CHANNEL(IIO_MOD_X),
+ MMA9551_INCLI_CHANNEL(IIO_MOD_Y),
+ MMA9551_INCLI_CHANNEL(IIO_MOD_Z),
+};
+
+static const struct iio_info mma9551_info = {
+ .read_raw = mma9551_read_raw,
+ .read_event_config = mma9551_read_event_config,
+ .write_event_config = mma9551_write_event_config,
+ .read_event_value = mma9551_read_event_value,
+ .write_event_value = mma9551_write_event_value,
+};
+
+static irqreturn_t mma9551_event_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct mma9551_data *data = iio_priv(indio_dev);
+ int i, ret, mma_axis = -1;
+ u16 reg;
+ u8 val;
+
+ mutex_lock(&data->mutex);
+
+ for (i = 0; i < 3; i++)
+ if (irq == data->irqs[i]) {
+ mma_axis = i;
+ break;
+ }
+
+ if (mma_axis == -1) {
+ /* IRQ was triggered on 4th line, which we don't use. */
+ dev_warn(&data->client->dev,
+ "irq triggered on unused line %d\n", data->irqs[3]);
+ goto out;
+ }
+
+ switch (mma_axis) {
+ case mma9551_x:
+ reg = MMA9551_TILT_YZ_ANG_REG;
+ break;
+ case mma9551_y:
+ reg = MMA9551_TILT_XZ_ANG_REG;
+ break;
+ case mma9551_z:
+ reg = MMA9551_TILT_XY_ANG_REG;
+ break;
+ }
+
+ /*
+ * Read the angle even though we don't use it, otherwise we
+ * won't get any further interrupts.
+ */
+ ret = mma9551_read_status_byte(data->client, MMA9551_APPID_TILT,
+ reg, &val);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "error %d reading tilt register in IRQ\n", ret);
+ goto out;
+ }
+
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_INCLI, 0, (mma_axis + 1),
+ IIO_EV_TYPE_ROC, IIO_EV_DIR_RISING),
+ iio_get_time_ns(indio_dev));
+
+out:
+ mutex_unlock(&data->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static int mma9551_init(struct mma9551_data *data)
+{
+ int ret;
+
+ ret = mma9551_read_version(data->client);
+ if (ret)
+ return ret;
+
+ return mma9551_set_device_state(data->client, true);
+}
+
+static int mma9551_gpio_probe(struct iio_dev *indio_dev)
+{
+ struct gpio_desc *gpio;
+ int i, ret;
+ struct mma9551_data *data = iio_priv(indio_dev);
+ struct device *dev = &data->client->dev;
+
+ for (i = 0; i < MMA9551_GPIO_COUNT; i++) {
+ gpio = devm_gpiod_get_index(dev, NULL, i, GPIOD_IN);
+ if (IS_ERR(gpio)) {
+ dev_err(dev, "acpi gpio get index failed\n");
+ return PTR_ERR(gpio);
+ }
+
+ ret = gpiod_to_irq(gpio);
+ if (ret < 0)
+ return ret;
+
+ data->irqs[i] = ret;
+ ret = devm_request_threaded_irq(dev, data->irqs[i],
+ NULL, mma9551_event_handler,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "mma9551_event", indio_dev);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "gpio resource, no:%d irq:%d\n",
+ desc_to_gpio(gpio), data->irqs[i]);
+ }
+
+ return 0;
+}
+
+static int mma9551_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct mma9551_data *data;
+ struct iio_dev *indio_dev;
+ const char *name = NULL;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+
+ if (id)
+ name = id->name;
+ else
+ name = iio_get_acpi_device_name(&client->dev);
+
+ ret = mma9551_init(data);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&data->mutex);
+
+ indio_dev->channels = mma9551_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mma9551_channels);
+ indio_dev->name = name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &mma9551_info;
+
+ ret = mma9551_gpio_probe(indio_dev);
+ if (ret < 0)
+ goto out_poweroff;
+
+ ret = pm_runtime_set_active(&client->dev);
+ if (ret < 0)
+ goto out_poweroff;
+
+ pm_runtime_enable(&client->dev);
+ pm_runtime_set_autosuspend_delay(&client->dev,
+ MMA9551_AUTO_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(&client->dev);
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "unable to register iio device\n");
+ goto err_pm_cleanup;
+ }
+
+ return 0;
+
+err_pm_cleanup:
+ pm_runtime_dont_use_autosuspend(&client->dev);
+ pm_runtime_disable(&client->dev);
+out_poweroff:
+ mma9551_set_device_state(client, false);
+
+ return ret;
+}
+
+static void mma9551_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct mma9551_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ pm_runtime_disable(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+
+ mutex_lock(&data->mutex);
+ mma9551_set_device_state(data->client, false);
+ mutex_unlock(&data->mutex);
+}
+
+static int mma9551_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mma9551_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = mma9551_set_device_state(data->client, false);
+ mutex_unlock(&data->mutex);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "powering off device failed\n");
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static int mma9551_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mma9551_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = mma9551_set_device_state(data->client, true);
+ if (ret < 0)
+ return ret;
+
+ mma9551_sleep(MMA9551_DEFAULT_SAMPLE_RATE);
+
+ return 0;
+}
+
+static int mma9551_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mma9551_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = mma9551_set_device_state(data->client, false);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int mma9551_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mma9551_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = mma9551_set_device_state(data->client, true);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static const struct dev_pm_ops mma9551_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(mma9551_suspend, mma9551_resume)
+ RUNTIME_PM_OPS(mma9551_runtime_suspend, mma9551_runtime_resume, NULL)
+};
+
+static const struct acpi_device_id mma9551_acpi_match[] = {
+ {"MMA9551", 0},
+ { }
+};
+
+MODULE_DEVICE_TABLE(acpi, mma9551_acpi_match);
+
+static const struct i2c_device_id mma9551_id[] = {
+ { .name = "mma9551" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, mma9551_id);
+
+static struct i2c_driver mma9551_driver = {
+ .driver = {
+ .name = "mma9551",
+ .acpi_match_table = mma9551_acpi_match,
+ .pm = pm_ptr(&mma9551_pm_ops),
+ },
+ .probe = mma9551_probe,
+ .remove = mma9551_remove,
+ .id_table = mma9551_id,
+};
+
+module_i2c_driver(mma9551_driver);
+
+MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>");
+MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MMA9551L motion-sensing platform driver");
+MODULE_IMPORT_NS("IIO_MMA9551");
diff --git a/drivers/iio/accel/mma9551_core.c b/drivers/iio/accel/mma9551_core.c
new file mode 100644
index 000000000..2ccb1fb19
--- /dev/null
+++ b/drivers/iio/accel/mma9551_core.c
@@ -0,0 +1,803 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Common code for Freescale MMA955x Intelligent Sensor Platform drivers
+ * Copyright (c) 2014, Intel Corporation.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/pm_runtime.h>
+#include "mma9551_core.h"
+
+/* Command masks for mailbox write command */
+#define MMA9551_CMD_READ_VERSION_INFO 0x00
+#define MMA9551_CMD_READ_CONFIG 0x10
+#define MMA9551_CMD_WRITE_CONFIG 0x20
+#define MMA9551_CMD_READ_STATUS 0x30
+
+/* Mailbox read command */
+#define MMA9551_RESPONSE_COCO BIT(7)
+
+/* Error-Status codes returned in mailbox read command */
+#define MMA9551_MCI_ERROR_NONE 0x00
+#define MMA9551_MCI_ERROR_PARAM 0x04
+#define MMA9551_MCI_INVALID_COUNT 0x19
+#define MMA9551_MCI_ERROR_COMMAND 0x1C
+#define MMA9551_MCI_ERROR_INVALID_LENGTH 0x21
+#define MMA9551_MCI_ERROR_FIFO_BUSY 0x22
+#define MMA9551_MCI_ERROR_FIFO_ALLOCATED 0x23
+#define MMA9551_MCI_ERROR_FIFO_OVERSIZE 0x24
+
+/* GPIO Application */
+#define MMA9551_GPIO_POL_MSB 0x08
+#define MMA9551_GPIO_POL_LSB 0x09
+
+/* Sleep/Wake application */
+#define MMA9551_SLEEP_CFG 0x06
+#define MMA9551_SLEEP_CFG_SNCEN BIT(0)
+#define MMA9551_SLEEP_CFG_FLEEN BIT(1)
+#define MMA9551_SLEEP_CFG_SCHEN BIT(2)
+
+/* AFE application */
+#define MMA9551_AFE_X_ACCEL_REG 0x00
+#define MMA9551_AFE_Y_ACCEL_REG 0x02
+#define MMA9551_AFE_Z_ACCEL_REG 0x04
+
+/* Reset/Suspend/Clear application */
+#define MMA9551_RSC_RESET 0x00
+#define MMA9551_RSC_OFFSET(mask) (3 - (ffs(mask) - 1) / 8)
+#define MMA9551_RSC_VAL(mask) (mask >> (((ffs(mask) - 1) / 8) * 8))
+
+/*
+ * A response is composed of:
+ * - control registers: MB0-3
+ * - data registers: MB4-31
+ *
+ * A request is composed of:
+ * - mbox to write to (always 0)
+ * - control registers: MB1-4
+ * - data registers: MB5-31
+ */
+#define MMA9551_MAILBOX_CTRL_REGS 4
+#define MMA9551_MAX_MAILBOX_DATA_REGS 28
+#define MMA9551_MAILBOX_REGS 32
+
+#define MMA9551_I2C_READ_RETRIES 5
+#define MMA9551_I2C_READ_DELAY 50 /* us */
+
+struct mma9551_mbox_request {
+ u8 start_mbox; /* Always 0. */
+ u8 app_id;
+ /*
+ * See Section 5.3.1 of the MMA955xL Software Reference Manual.
+ *
+ * Bit 7: reserved, always 0
+ * Bits 6-4: command
+ * Bits 3-0: upper bits of register offset
+ */
+ u8 cmd_off;
+ u8 lower_off;
+ u8 nbytes;
+ u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS - 1];
+} __packed;
+
+struct mma9551_mbox_response {
+ u8 app_id;
+ /*
+ * See Section 5.3.3 of the MMA955xL Software Reference Manual.
+ *
+ * Bit 7: COCO
+ * Bits 6-0: Error code.
+ */
+ u8 coco_err;
+ u8 nbytes;
+ u8 req_bytes;
+ u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS];
+} __packed;
+
+struct mma9551_version_info {
+ __be32 device_id;
+ u8 rom_version[2];
+ u8 fw_version[2];
+ u8 hw_version[2];
+ u8 fw_build[2];
+};
+
+static int mma9551_transfer(struct i2c_client *client,
+ u8 app_id, u8 command, u16 offset,
+ u8 *inbytes, int num_inbytes,
+ u8 *outbytes, int num_outbytes)
+{
+ struct mma9551_mbox_request req;
+ struct mma9551_mbox_response rsp;
+ struct i2c_msg in, out;
+ u8 req_len, err_code;
+ int ret, retries;
+
+ if (offset >= 1 << 12) {
+ dev_err(&client->dev, "register offset too large\n");
+ return -EINVAL;
+ }
+
+ req_len = 1 + MMA9551_MAILBOX_CTRL_REGS + num_inbytes;
+ req.start_mbox = 0;
+ req.app_id = app_id;
+ req.cmd_off = command | (offset >> 8);
+ req.lower_off = offset;
+
+ if (command == MMA9551_CMD_WRITE_CONFIG)
+ req.nbytes = num_inbytes;
+ else
+ req.nbytes = num_outbytes;
+ if (num_inbytes)
+ memcpy(req.buf, inbytes, num_inbytes);
+
+ out.addr = client->addr;
+ out.flags = 0;
+ out.len = req_len;
+ out.buf = (u8 *)&req;
+
+ ret = i2c_transfer(client->adapter, &out, 1);
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c write failed\n");
+ return ret;
+ }
+
+ retries = MMA9551_I2C_READ_RETRIES;
+ do {
+ udelay(MMA9551_I2C_READ_DELAY);
+
+ in.addr = client->addr;
+ in.flags = I2C_M_RD;
+ in.len = sizeof(rsp);
+ in.buf = (u8 *)&rsp;
+
+ ret = i2c_transfer(client->adapter, &in, 1);
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c read failed\n");
+ return ret;
+ }
+
+ if (rsp.coco_err & MMA9551_RESPONSE_COCO)
+ break;
+ } while (--retries > 0);
+
+ if (retries == 0) {
+ dev_err(&client->dev,
+ "timed out while waiting for command response\n");
+ return -ETIMEDOUT;
+ }
+
+ if (rsp.app_id != app_id) {
+ dev_err(&client->dev,
+ "app_id mismatch in response got %02x expected %02x\n",
+ rsp.app_id, app_id);
+ return -EINVAL;
+ }
+
+ err_code = rsp.coco_err & ~MMA9551_RESPONSE_COCO;
+ if (err_code != MMA9551_MCI_ERROR_NONE) {
+ dev_err(&client->dev, "read returned error %x\n", err_code);
+ return -EINVAL;
+ }
+
+ if (rsp.nbytes != rsp.req_bytes) {
+ dev_err(&client->dev,
+ "output length mismatch got %d expected %d\n",
+ rsp.nbytes, rsp.req_bytes);
+ return -EINVAL;
+ }
+
+ if (num_outbytes)
+ memcpy(outbytes, rsp.buf, num_outbytes);
+
+ return 0;
+}
+
+/**
+ * mma9551_read_config_byte() - read 1 configuration byte
+ * @client: I2C client
+ * @app_id: Application ID
+ * @reg: Application register
+ * @val: Pointer to store value read
+ *
+ * Read one configuration byte from the device using MMA955xL command format.
+ * Commands to the MMA955xL platform consist of a write followed
+ * by one or more reads.
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_read_config_byte(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 *val)
+{
+ return mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG,
+ reg, NULL, 0, val, 1);
+}
+EXPORT_SYMBOL_NS(mma9551_read_config_byte, "IIO_MMA9551");
+
+/**
+ * mma9551_write_config_byte() - write 1 configuration byte
+ * @client: I2C client
+ * @app_id: Application ID
+ * @reg: Application register
+ * @val: Value to write
+ *
+ * Write one configuration byte from the device using MMA955xL command format.
+ * Commands to the MMA955xL platform consist of a write followed by one or
+ * more reads.
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_write_config_byte(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 val)
+{
+ return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg,
+ &val, 1, NULL, 0);
+}
+EXPORT_SYMBOL_NS(mma9551_write_config_byte, "IIO_MMA9551");
+
+/**
+ * mma9551_read_status_byte() - read 1 status byte
+ * @client: I2C client
+ * @app_id: Application ID
+ * @reg: Application register
+ * @val: Pointer to store value read
+ *
+ * Read one status byte from the device using MMA955xL command format.
+ * Commands to the MMA955xL platform consist of a write followed by one or
+ * more reads.
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_read_status_byte(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 *val)
+{
+ return mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS,
+ reg, NULL, 0, val, 1);
+}
+EXPORT_SYMBOL_NS(mma9551_read_status_byte, "IIO_MMA9551");
+
+/**
+ * mma9551_read_config_word() - read 1 config word
+ * @client: I2C client
+ * @app_id: Application ID
+ * @reg: Application register
+ * @val: Pointer to store value read
+ *
+ * Read one configuration word from the device using MMA955xL command format.
+ * Commands to the MMA955xL platform consist of a write followed by one or
+ * more reads.
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_read_config_word(struct i2c_client *client, u8 app_id,
+ u16 reg, u16 *val)
+{
+ int ret;
+ __be16 v;
+
+ ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG,
+ reg, NULL, 0, (u8 *)&v, 2);
+ if (ret < 0)
+ return ret;
+
+ *val = be16_to_cpu(v);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(mma9551_read_config_word, "IIO_MMA9551");
+
+/**
+ * mma9551_write_config_word() - write 1 config word
+ * @client: I2C client
+ * @app_id: Application ID
+ * @reg: Application register
+ * @val: Value to write
+ *
+ * Write one configuration word from the device using MMA955xL command format.
+ * Commands to the MMA955xL platform consist of a write followed by one or
+ * more reads.
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_write_config_word(struct i2c_client *client, u8 app_id,
+ u16 reg, u16 val)
+{
+ __be16 v = cpu_to_be16(val);
+
+ return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg,
+ (u8 *)&v, 2, NULL, 0);
+}
+EXPORT_SYMBOL_NS(mma9551_write_config_word, "IIO_MMA9551");
+
+/**
+ * mma9551_read_status_word() - read 1 status word
+ * @client: I2C client
+ * @app_id: Application ID
+ * @reg: Application register
+ * @val: Pointer to store value read
+ *
+ * Read one status word from the device using MMA955xL command format.
+ * Commands to the MMA955xL platform consist of a write followed by one or
+ * more reads.
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_read_status_word(struct i2c_client *client, u8 app_id,
+ u16 reg, u16 *val)
+{
+ int ret;
+ __be16 v;
+
+ ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS,
+ reg, NULL, 0, (u8 *)&v, 2);
+ if (ret < 0)
+ return ret;
+
+ *val = be16_to_cpu(v);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(mma9551_read_status_word, "IIO_MMA9551");
+
+/**
+ * mma9551_read_config_words() - read multiple config words
+ * @client: I2C client
+ * @app_id: Application ID
+ * @reg: Application register
+ * @len: Length of array to read (in words)
+ * @buf: Array of words to read
+ *
+ * Read multiple configuration registers (word-sized registers).
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_read_config_words(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 len, u16 *buf)
+{
+ int ret, i;
+ __be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS / 2];
+
+ if (len > ARRAY_SIZE(be_buf)) {
+ dev_err(&client->dev, "Invalid buffer size %d\n", len);
+ return -EINVAL;
+ }
+
+ ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG,
+ reg, NULL, 0, (u8 *)be_buf, len * sizeof(u16));
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < len; i++)
+ buf[i] = be16_to_cpu(be_buf[i]);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(mma9551_read_config_words, "IIO_MMA9551");
+
+/**
+ * mma9551_read_status_words() - read multiple status words
+ * @client: I2C client
+ * @app_id: Application ID
+ * @reg: Application register
+ * @len: Length of array to read (in words)
+ * @buf: Array of words to read
+ *
+ * Read multiple status registers (word-sized registers).
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_read_status_words(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 len, u16 *buf)
+{
+ int ret, i;
+ __be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS / 2];
+
+ if (len > ARRAY_SIZE(be_buf)) {
+ dev_err(&client->dev, "Invalid buffer size %d\n", len);
+ return -EINVAL;
+ }
+
+ ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS,
+ reg, NULL, 0, (u8 *)be_buf, len * sizeof(u16));
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < len; i++)
+ buf[i] = be16_to_cpu(be_buf[i]);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(mma9551_read_status_words, "IIO_MMA9551");
+
+/**
+ * mma9551_write_config_words() - write multiple config words
+ * @client: I2C client
+ * @app_id: Application ID
+ * @reg: Application register
+ * @len: Length of array to write (in words)
+ * @buf: Array of words to write
+ *
+ * Write multiple configuration registers (word-sized registers).
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_write_config_words(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 len, u16 *buf)
+{
+ int i;
+ __be16 be_buf[(MMA9551_MAX_MAILBOX_DATA_REGS - 1) / 2];
+
+ if (len > ARRAY_SIZE(be_buf)) {
+ dev_err(&client->dev, "Invalid buffer size %d\n", len);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < len; i++)
+ be_buf[i] = cpu_to_be16(buf[i]);
+
+ return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG,
+ reg, (u8 *)be_buf, len * sizeof(u16), NULL, 0);
+}
+EXPORT_SYMBOL_NS(mma9551_write_config_words, "IIO_MMA9551");
+
+/**
+ * mma9551_update_config_bits() - update bits in register
+ * @client: I2C client
+ * @app_id: Application ID
+ * @reg: Application register
+ * @mask: Mask for the bits to update
+ * @val: Value of the bits to update
+ *
+ * Update bits in the given register using a bit mask.
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_update_config_bits(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 mask, u8 val)
+{
+ int ret;
+ u8 tmp, orig;
+
+ ret = mma9551_read_config_byte(client, app_id, reg, &orig);
+ if (ret < 0)
+ return ret;
+
+ tmp = orig & ~mask;
+ tmp |= val & mask;
+
+ if (tmp == orig)
+ return 0;
+
+ return mma9551_write_config_byte(client, app_id, reg, tmp);
+}
+EXPORT_SYMBOL_NS(mma9551_update_config_bits, "IIO_MMA9551");
+
+/**
+ * mma9551_gpio_config() - configure gpio
+ * @client: I2C client
+ * @pin: GPIO pin to configure
+ * @app_id: Application ID
+ * @bitnum: Bit number of status register being assigned to the GPIO pin.
+ * @polarity: The polarity parameter is described in section 6.2.2, page 66,
+ * of the Software Reference Manual. Basically, polarity=0 means
+ * the interrupt line has the same value as the selected bit,
+ * while polarity=1 means the line is inverted.
+ *
+ * Assign a bit from an application’s status register to a specific GPIO pin.
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_gpio_config(struct i2c_client *client, enum mma9551_gpio_pin pin,
+ u8 app_id, u8 bitnum, int polarity)
+{
+ u8 reg, pol_mask, pol_val;
+ int ret;
+
+ if (pin > mma9551_gpio_max) {
+ dev_err(&client->dev, "bad GPIO pin\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Pin 6 is configured by regs 0x00 and 0x01, pin 7 by 0x02 and
+ * 0x03, and so on.
+ */
+ reg = pin * 2;
+
+ ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO,
+ reg, app_id);
+ if (ret < 0) {
+ dev_err(&client->dev, "error setting GPIO app_id\n");
+ return ret;
+ }
+
+ ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO,
+ reg + 1, bitnum);
+ if (ret < 0) {
+ dev_err(&client->dev, "error setting GPIO bit number\n");
+ return ret;
+ }
+
+ switch (pin) {
+ case mma9551_gpio6:
+ reg = MMA9551_GPIO_POL_LSB;
+ pol_mask = 1 << 6;
+ break;
+ case mma9551_gpio7:
+ reg = MMA9551_GPIO_POL_LSB;
+ pol_mask = 1 << 7;
+ break;
+ case mma9551_gpio8:
+ reg = MMA9551_GPIO_POL_MSB;
+ pol_mask = 1 << 0;
+ break;
+ case mma9551_gpio9:
+ reg = MMA9551_GPIO_POL_MSB;
+ pol_mask = 1 << 1;
+ break;
+ }
+ pol_val = polarity ? pol_mask : 0;
+
+ ret = mma9551_update_config_bits(client, MMA9551_APPID_GPIO, reg,
+ pol_mask, pol_val);
+ if (ret < 0)
+ dev_err(&client->dev, "error setting GPIO polarity\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_NS(mma9551_gpio_config, "IIO_MMA9551");
+
+/**
+ * mma9551_read_version() - read device version information
+ * @client: I2C client
+ *
+ * Read version information and print device id and firmware version.
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_read_version(struct i2c_client *client)
+{
+ struct mma9551_version_info info;
+ int ret;
+
+ ret = mma9551_transfer(client, MMA9551_APPID_VERSION, 0x00, 0x00,
+ NULL, 0, (u8 *)&info, sizeof(info));
+ if (ret < 0)
+ return ret;
+
+ dev_info(&client->dev, "device ID 0x%x, firmware version %02x.%02x\n",
+ be32_to_cpu(info.device_id), info.fw_version[0],
+ info.fw_version[1]);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(mma9551_read_version, "IIO_MMA9551");
+
+/**
+ * mma9551_set_device_state() - sets HW power mode
+ * @client: I2C client
+ * @enable: Use true to power on device, false to cause the device
+ * to enter sleep.
+ *
+ * Set power on/off for device using the Sleep/Wake Application.
+ * When enable is true, power on chip and enable doze mode.
+ * When enable is false, enter sleep mode (device remains in the
+ * lowest-power mode).
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_set_device_state(struct i2c_client *client, bool enable)
+{
+ return mma9551_update_config_bits(client, MMA9551_APPID_SLEEP_WAKE,
+ MMA9551_SLEEP_CFG,
+ MMA9551_SLEEP_CFG_SNCEN |
+ MMA9551_SLEEP_CFG_FLEEN |
+ MMA9551_SLEEP_CFG_SCHEN,
+ enable ? MMA9551_SLEEP_CFG_SCHEN |
+ MMA9551_SLEEP_CFG_FLEEN :
+ MMA9551_SLEEP_CFG_SNCEN);
+}
+EXPORT_SYMBOL_NS(mma9551_set_device_state, "IIO_MMA9551");
+
+/**
+ * mma9551_set_power_state() - sets runtime PM state
+ * @client: I2C client
+ * @on: Use true to power on device, false to power off
+ *
+ * Resume or suspend the device using Runtime PM.
+ * The device will suspend after the autosuspend delay.
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_set_power_state(struct i2c_client *client, bool on)
+{
+#ifdef CONFIG_PM
+ int ret;
+
+ if (on)
+ ret = pm_runtime_resume_and_get(&client->dev);
+ else
+ ret = pm_runtime_put_autosuspend(&client->dev);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "failed to change power state to %d\n", on);
+
+ return ret;
+ }
+#endif
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(mma9551_set_power_state, "IIO_MMA9551");
+
+/**
+ * mma9551_sleep() - sleep
+ * @freq: Application frequency
+ *
+ * Firmware applications run at a certain frequency on the
+ * device. Sleep for one application cycle to make sure the
+ * application had time to run once and initialize set values.
+ */
+void mma9551_sleep(int freq)
+{
+ int sleep_val = 1000 / freq;
+
+ if (sleep_val < 20)
+ usleep_range(sleep_val * 1000, 20000);
+ else
+ msleep_interruptible(sleep_val);
+}
+EXPORT_SYMBOL_NS(mma9551_sleep, "IIO_MMA9551");
+
+/**
+ * mma9551_read_accel_chan() - read accelerometer channel
+ * @client: I2C client
+ * @chan: IIO channel
+ * @val: Pointer to the accelerometer value read
+ * @val2: Unused
+ *
+ * Read accelerometer value for the specified channel.
+ *
+ * Locking note: This function must be called with the device lock held.
+ * Locking is not handled inside the function. Callers should ensure they
+ * serialize access to the HW.
+ *
+ * Returns: IIO_VAL_INT on success, negative value on failure.
+ */
+int mma9551_read_accel_chan(struct i2c_client *client,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2)
+{
+ u16 reg_addr;
+ s16 raw_accel;
+ int ret;
+
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ reg_addr = MMA9551_AFE_X_ACCEL_REG;
+ break;
+ case IIO_MOD_Y:
+ reg_addr = MMA9551_AFE_Y_ACCEL_REG;
+ break;
+ case IIO_MOD_Z:
+ reg_addr = MMA9551_AFE_Z_ACCEL_REG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = mma9551_set_power_state(client, true);
+ if (ret < 0)
+ return ret;
+
+ ret = mma9551_read_status_word(client, MMA9551_APPID_AFE,
+ reg_addr, &raw_accel);
+ if (ret < 0)
+ goto out_poweroff;
+
+ *val = raw_accel;
+
+ ret = IIO_VAL_INT;
+
+out_poweroff:
+ mma9551_set_power_state(client, false);
+ return ret;
+}
+EXPORT_SYMBOL_NS(mma9551_read_accel_chan, "IIO_MMA9551");
+
+/**
+ * mma9551_read_accel_scale() - read accelerometer scale
+ * @val: Pointer to the accelerometer scale (int value)
+ * @val2: Pointer to the accelerometer scale (micro value)
+ *
+ * Read accelerometer scale.
+ *
+ * Returns: IIO_VAL_INT_PLUS_MICRO.
+ */
+int mma9551_read_accel_scale(int *val, int *val2)
+{
+ *val = 0;
+ *val2 = 2440;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+EXPORT_SYMBOL_NS(mma9551_read_accel_scale, "IIO_MMA9551");
+
+/**
+ * mma9551_app_reset() - reset application
+ * @client: I2C client
+ * @app_mask: Application to reset
+ *
+ * Reset the given application (using the Reset/Suspend/Clear
+ * Control Application)
+ *
+ * Returns: 0 on success, negative value on failure.
+ */
+int mma9551_app_reset(struct i2c_client *client, u32 app_mask)
+{
+ return mma9551_write_config_byte(client, MMA9551_APPID_RSC,
+ MMA9551_RSC_RESET +
+ MMA9551_RSC_OFFSET(app_mask),
+ MMA9551_RSC_VAL(app_mask));
+}
+EXPORT_SYMBOL_NS(mma9551_app_reset, "IIO_MMA9551");
+
+MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>");
+MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MMA955xL sensors core");
diff --git a/drivers/iio/accel/mma9551_core.h b/drivers/iio/accel/mma9551_core.h
new file mode 100644
index 000000000..543454a31
--- /dev/null
+++ b/drivers/iio/accel/mma9551_core.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Common code for Freescale MMA955x Intelligent Sensor Platform drivers
+ * Copyright (c) 2014, Intel Corporation.
+ */
+
+#ifndef _MMA9551_CORE_H_
+#define _MMA9551_CORE_H_
+
+/* Applications IDs */
+#define MMA9551_APPID_VERSION 0x00
+#define MMA9551_APPID_GPIO 0x03
+#define MMA9551_APPID_AFE 0x06
+#define MMA9551_APPID_TILT 0x0B
+#define MMA9551_APPID_SLEEP_WAKE 0x12
+#define MMA9551_APPID_PEDOMETER 0x15
+#define MMA9551_APPID_RSC 0x17
+#define MMA9551_APPID_NONE 0xff
+
+/* Reset/Suspend/Clear application app masks */
+#define MMA9551_RSC_PED BIT(21)
+
+#define MMA9551_AUTO_SUSPEND_DELAY_MS 2000
+
+enum mma9551_gpio_pin {
+ mma9551_gpio6 = 0,
+ mma9551_gpio7,
+ mma9551_gpio8,
+ mma9551_gpio9,
+ mma9551_gpio_max = mma9551_gpio9,
+};
+
+#define MMA9551_ACCEL_CHANNEL(axis) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+int mma9551_read_config_byte(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 *val);
+int mma9551_write_config_byte(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 val);
+int mma9551_read_status_byte(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 *val);
+int mma9551_read_config_word(struct i2c_client *client, u8 app_id,
+ u16 reg, u16 *val);
+int mma9551_write_config_word(struct i2c_client *client, u8 app_id,
+ u16 reg, u16 val);
+int mma9551_read_status_word(struct i2c_client *client, u8 app_id,
+ u16 reg, u16 *val);
+int mma9551_read_config_words(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 len, u16 *buf);
+int mma9551_read_status_words(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 len, u16 *buf);
+int mma9551_write_config_words(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 len, u16 *buf);
+int mma9551_update_config_bits(struct i2c_client *client, u8 app_id,
+ u16 reg, u8 mask, u8 val);
+int mma9551_gpio_config(struct i2c_client *client, enum mma9551_gpio_pin pin,
+ u8 app_id, u8 bitnum, int polarity);
+int mma9551_read_version(struct i2c_client *client);
+int mma9551_set_device_state(struct i2c_client *client, bool enable);
+int mma9551_set_power_state(struct i2c_client *client, bool on);
+void mma9551_sleep(int freq);
+int mma9551_read_accel_chan(struct i2c_client *client,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2);
+int mma9551_read_accel_scale(int *val, int *val2);
+int mma9551_app_reset(struct i2c_client *client, u32 app_mask);
+
+#endif /* _MMA9551_CORE_H_ */
diff --git a/drivers/iio/accel/mma9553.c b/drivers/iio/accel/mma9553.c
new file mode 100644
index 000000000..8e7aaac89
--- /dev/null
+++ b/drivers/iio/accel/mma9553.c
@@ -0,0 +1,1240 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Freescale MMA9553L Intelligent Pedometer driver
+ * Copyright (c) 2014, Intel Corporation.
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/pm_runtime.h>
+#include "mma9551_core.h"
+
+/* Pedometer configuration registers (R/W) */
+#define MMA9553_REG_CONF_SLEEPMIN 0x00
+#define MMA9553_REG_CONF_SLEEPMAX 0x02
+#define MMA9553_REG_CONF_SLEEPTHD 0x04
+#define MMA9553_MASK_CONF_WORD GENMASK(15, 0)
+
+#define MMA9553_REG_CONF_CONF_STEPLEN 0x06
+#define MMA9553_MASK_CONF_CONFIG BIT(15)
+#define MMA9553_MASK_CONF_ACT_DBCNTM BIT(14)
+#define MMA9553_MASK_CONF_SLP_DBCNTM BIT(13)
+#define MMA9553_MASK_CONF_STEPLEN GENMASK(7, 0)
+
+#define MMA9553_REG_CONF_HEIGHT_WEIGHT 0x08
+#define MMA9553_MASK_CONF_HEIGHT GENMASK(15, 8)
+#define MMA9553_MASK_CONF_WEIGHT GENMASK(7, 0)
+
+#define MMA9553_REG_CONF_FILTER 0x0A
+#define MMA9553_MASK_CONF_FILTSTEP GENMASK(15, 8)
+#define MMA9553_MASK_CONF_MALE BIT(7)
+#define MMA9553_MASK_CONF_FILTTIME GENMASK(6, 0)
+
+#define MMA9553_REG_CONF_SPEED_STEP 0x0C
+#define MMA9553_MASK_CONF_SPDPRD GENMASK(15, 8)
+#define MMA9553_MASK_CONF_STEPCOALESCE GENMASK(7, 0)
+
+#define MMA9553_REG_CONF_ACTTHD 0x0E
+#define MMA9553_MAX_ACTTHD GENMASK(15, 0)
+
+/* Pedometer status registers (R-only) */
+#define MMA9553_REG_STATUS 0x00
+#define MMA9553_MASK_STATUS_MRGFL BIT(15)
+#define MMA9553_MASK_STATUS_SUSPCHG BIT(14)
+#define MMA9553_MASK_STATUS_STEPCHG BIT(13)
+#define MMA9553_MASK_STATUS_ACTCHG BIT(12)
+#define MMA9553_MASK_STATUS_SUSP BIT(11)
+#define MMA9553_MASK_STATUS_ACTIVITY GENMASK(10, 8)
+#define MMA9553_MASK_STATUS_VERSION GENMASK(7, 0)
+
+#define MMA9553_REG_STEPCNT 0x02
+#define MMA9553_REG_DISTANCE 0x04
+#define MMA9553_REG_SPEED 0x06
+#define MMA9553_REG_CALORIES 0x08
+#define MMA9553_REG_SLEEPCNT 0x0A
+
+/* Pedometer events are always mapped to this pin. */
+#define MMA9553_DEFAULT_GPIO_PIN mma9551_gpio6
+#define MMA9553_DEFAULT_GPIO_POLARITY 0
+
+/* Bitnum used for GPIO configuration = bit number in high status byte */
+#define MMA9553_STATUS_TO_BITNUM(bit) (ffs(bit) - 9)
+#define MMA9553_MAX_BITNUM MMA9553_STATUS_TO_BITNUM(BIT(16))
+
+#define MMA9553_DEFAULT_SAMPLE_RATE 30 /* Hz */
+
+/*
+ * The internal activity level must be stable for ACTTHD samples before
+ * ACTIVITY is updated. The ACTIVITY variable contains the current activity
+ * level and is updated every time a step is detected or once a second
+ * if there are no steps.
+ */
+#define MMA9553_ACTIVITY_THD_TO_SEC(thd) ((thd) / MMA9553_DEFAULT_SAMPLE_RATE)
+#define MMA9553_ACTIVITY_SEC_TO_THD(sec) ((sec) * MMA9553_DEFAULT_SAMPLE_RATE)
+
+/*
+ * Autonomously suspend pedometer if acceleration vector magnitude
+ * is near 1g (4096 at 0.244 mg/LSB resolution) for 30 seconds.
+ */
+#define MMA9553_DEFAULT_SLEEPMIN 3688 /* 0,9 g */
+#define MMA9553_DEFAULT_SLEEPMAX 4508 /* 1,1 g */
+#define MMA9553_DEFAULT_SLEEPTHD (MMA9553_DEFAULT_SAMPLE_RATE * 30)
+
+#define MMA9553_CONFIG_RETRIES 2
+
+/* Status register - activity field */
+enum activity_level {
+ ACTIVITY_UNKNOWN,
+ ACTIVITY_REST,
+ ACTIVITY_WALKING,
+ ACTIVITY_JOGGING,
+ ACTIVITY_RUNNING,
+};
+
+static const struct mma9553_event_info {
+ enum iio_chan_type type;
+ enum iio_modifier mod;
+ enum iio_event_direction dir;
+} mma9553_events_info[] = {
+ {
+ .type = IIO_STEPS,
+ .mod = IIO_NO_MOD,
+ .dir = IIO_EV_DIR_NONE,
+ },
+ {
+ .type = IIO_ACTIVITY,
+ .mod = IIO_MOD_STILL,
+ .dir = IIO_EV_DIR_RISING,
+ },
+ {
+ .type = IIO_ACTIVITY,
+ .mod = IIO_MOD_STILL,
+ .dir = IIO_EV_DIR_FALLING,
+ },
+ {
+ .type = IIO_ACTIVITY,
+ .mod = IIO_MOD_WALKING,
+ .dir = IIO_EV_DIR_RISING,
+ },
+ {
+ .type = IIO_ACTIVITY,
+ .mod = IIO_MOD_WALKING,
+ .dir = IIO_EV_DIR_FALLING,
+ },
+ {
+ .type = IIO_ACTIVITY,
+ .mod = IIO_MOD_JOGGING,
+ .dir = IIO_EV_DIR_RISING,
+ },
+ {
+ .type = IIO_ACTIVITY,
+ .mod = IIO_MOD_JOGGING,
+ .dir = IIO_EV_DIR_FALLING,
+ },
+ {
+ .type = IIO_ACTIVITY,
+ .mod = IIO_MOD_RUNNING,
+ .dir = IIO_EV_DIR_RISING,
+ },
+ {
+ .type = IIO_ACTIVITY,
+ .mod = IIO_MOD_RUNNING,
+ .dir = IIO_EV_DIR_FALLING,
+ },
+};
+
+#define MMA9553_EVENTS_INFO_SIZE ARRAY_SIZE(mma9553_events_info)
+
+struct mma9553_event {
+ const struct mma9553_event_info *info;
+ bool enabled;
+};
+
+struct mma9553_conf_regs {
+ u16 sleepmin;
+ u16 sleepmax;
+ u16 sleepthd;
+ u16 config;
+ u16 height_weight;
+ u16 filter;
+ u16 speed_step;
+ u16 actthd;
+} __packed;
+
+struct mma9553_data {
+ struct i2c_client *client;
+ /*
+ * 1. Serialize access to HW (requested by mma9551_core API).
+ * 2. Serialize sequences that power on/off the device and access HW.
+ */
+ struct mutex mutex;
+ struct mma9553_conf_regs conf;
+ struct mma9553_event events[MMA9553_EVENTS_INFO_SIZE];
+ int num_events;
+ u8 gpio_bitnum;
+ /*
+ * This is used for all features that depend on step count:
+ * step count, distance, speed, calories.
+ */
+ bool stepcnt_enabled;
+ u16 stepcnt;
+ u8 activity;
+ s64 timestamp;
+};
+
+static u8 mma9553_get_bits(u16 val, u16 mask)
+{
+ return (val & mask) >> (ffs(mask) - 1);
+}
+
+static u16 mma9553_set_bits(u16 current_val, u16 val, u16 mask)
+{
+ return (current_val & ~mask) | (val << (ffs(mask) - 1));
+}
+
+static enum iio_modifier mma9553_activity_to_mod(enum activity_level activity)
+{
+ switch (activity) {
+ case ACTIVITY_RUNNING:
+ return IIO_MOD_RUNNING;
+ case ACTIVITY_JOGGING:
+ return IIO_MOD_JOGGING;
+ case ACTIVITY_WALKING:
+ return IIO_MOD_WALKING;
+ case ACTIVITY_REST:
+ return IIO_MOD_STILL;
+ case ACTIVITY_UNKNOWN:
+ default:
+ return IIO_NO_MOD;
+ }
+}
+
+static void mma9553_init_events(struct mma9553_data *data)
+{
+ int i;
+
+ data->num_events = MMA9553_EVENTS_INFO_SIZE;
+ for (i = 0; i < data->num_events; i++) {
+ data->events[i].info = &mma9553_events_info[i];
+ data->events[i].enabled = false;
+ }
+}
+
+static struct mma9553_event *mma9553_get_event(struct mma9553_data *data,
+ enum iio_chan_type type,
+ enum iio_modifier mod,
+ enum iio_event_direction dir)
+{
+ int i;
+
+ for (i = 0; i < data->num_events; i++)
+ if (data->events[i].info->type == type &&
+ data->events[i].info->mod == mod &&
+ data->events[i].info->dir == dir)
+ return &data->events[i];
+
+ return NULL;
+}
+
+static bool mma9553_is_any_event_enabled(struct mma9553_data *data,
+ bool check_type,
+ enum iio_chan_type type)
+{
+ int i;
+
+ for (i = 0; i < data->num_events; i++)
+ if ((check_type && data->events[i].info->type == type &&
+ data->events[i].enabled) ||
+ (!check_type && data->events[i].enabled))
+ return true;
+
+ return false;
+}
+
+static int mma9553_set_config(struct mma9553_data *data, u16 reg,
+ u16 *p_reg_val, u16 val, u16 mask)
+{
+ int ret, retries;
+ u16 reg_val, config;
+
+ reg_val = *p_reg_val;
+ if (val == mma9553_get_bits(reg_val, mask))
+ return 0;
+
+ reg_val = mma9553_set_bits(reg_val, val, mask);
+ ret = mma9551_write_config_word(data->client, MMA9551_APPID_PEDOMETER,
+ reg, reg_val);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "error writing config register 0x%x\n", reg);
+ return ret;
+ }
+
+ *p_reg_val = reg_val;
+
+ /* Reinitializes the pedometer with current configuration values */
+ config = mma9553_set_bits(data->conf.config, 1,
+ MMA9553_MASK_CONF_CONFIG);
+
+ ret = mma9551_write_config_word(data->client, MMA9551_APPID_PEDOMETER,
+ MMA9553_REG_CONF_CONF_STEPLEN, config);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "error writing config register 0x%x\n",
+ MMA9553_REG_CONF_CONF_STEPLEN);
+ return ret;
+ }
+
+ retries = MMA9553_CONFIG_RETRIES;
+ do {
+ mma9551_sleep(MMA9553_DEFAULT_SAMPLE_RATE);
+ ret = mma9551_read_config_word(data->client,
+ MMA9551_APPID_PEDOMETER,
+ MMA9553_REG_CONF_CONF_STEPLEN,
+ &config);
+ if (ret < 0)
+ return ret;
+ } while (mma9553_get_bits(config, MMA9553_MASK_CONF_CONFIG) &&
+ --retries > 0);
+
+ return 0;
+}
+
+static int mma9553_read_activity_stepcnt(struct mma9553_data *data,
+ u8 *activity, u16 *stepcnt)
+{
+ u16 buf[2];
+ int ret;
+
+ ret = mma9551_read_status_words(data->client, MMA9551_APPID_PEDOMETER,
+ MMA9553_REG_STATUS, ARRAY_SIZE(buf),
+ buf);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "error reading status and stepcnt\n");
+ return ret;
+ }
+
+ *activity = mma9553_get_bits(buf[0], MMA9553_MASK_STATUS_ACTIVITY);
+ *stepcnt = buf[1];
+
+ return 0;
+}
+
+static int mma9553_conf_gpio(struct mma9553_data *data)
+{
+ u8 bitnum = 0, appid = MMA9551_APPID_PEDOMETER;
+ int ret;
+ struct mma9553_event *ev_step_detect;
+ bool activity_enabled;
+
+ activity_enabled = mma9553_is_any_event_enabled(data, true,
+ IIO_ACTIVITY);
+ ev_step_detect = mma9553_get_event(data, IIO_STEPS, IIO_NO_MOD,
+ IIO_EV_DIR_NONE);
+
+ /*
+ * If both step detector and activity are enabled, use the MRGFL bit.
+ * This bit is the logical OR of the SUSPCHG, STEPCHG, and ACTCHG flags.
+ */
+ if (activity_enabled && ev_step_detect->enabled)
+ bitnum = MMA9553_STATUS_TO_BITNUM(MMA9553_MASK_STATUS_MRGFL);
+ else if (ev_step_detect->enabled)
+ bitnum = MMA9553_STATUS_TO_BITNUM(MMA9553_MASK_STATUS_STEPCHG);
+ else if (activity_enabled)
+ bitnum = MMA9553_STATUS_TO_BITNUM(MMA9553_MASK_STATUS_ACTCHG);
+ else /* Reset */
+ appid = MMA9551_APPID_NONE;
+
+ if (data->gpio_bitnum == bitnum)
+ return 0;
+
+ /* Save initial values for activity and stepcnt */
+ if (activity_enabled || ev_step_detect->enabled) {
+ ret = mma9553_read_activity_stepcnt(data, &data->activity,
+ &data->stepcnt);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = mma9551_gpio_config(data->client, MMA9553_DEFAULT_GPIO_PIN, appid,
+ bitnum, MMA9553_DEFAULT_GPIO_POLARITY);
+ if (ret < 0)
+ return ret;
+ data->gpio_bitnum = bitnum;
+
+ return 0;
+}
+
+static int mma9553_init(struct mma9553_data *data)
+{
+ int ret;
+
+ ret = mma9551_read_version(data->client);
+ if (ret)
+ return ret;
+
+ /*
+ * Read all the pedometer configuration registers. This is used as
+ * a device identification command to differentiate the MMA9553L
+ * from the MMA9550L.
+ */
+ ret = mma9551_read_config_words(data->client, MMA9551_APPID_PEDOMETER,
+ MMA9553_REG_CONF_SLEEPMIN,
+ sizeof(data->conf) / sizeof(u16),
+ (u16 *)&data->conf);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "failed to read configuration registers\n");
+ return ret;
+ }
+
+ /* Reset GPIO */
+ data->gpio_bitnum = MMA9553_MAX_BITNUM;
+ ret = mma9553_conf_gpio(data);
+ if (ret < 0)
+ return ret;
+
+ ret = mma9551_app_reset(data->client, MMA9551_RSC_PED);
+ if (ret < 0)
+ return ret;
+
+ /* Init config registers */
+ data->conf.sleepmin = MMA9553_DEFAULT_SLEEPMIN;
+ data->conf.sleepmax = MMA9553_DEFAULT_SLEEPMAX;
+ data->conf.sleepthd = MMA9553_DEFAULT_SLEEPTHD;
+ data->conf.config = mma9553_set_bits(data->conf.config, 1,
+ MMA9553_MASK_CONF_CONFIG);
+ /*
+ * Clear the activity debounce counter when the activity level changes,
+ * so that the confidence level applies for any activity level.
+ */
+ data->conf.config = mma9553_set_bits(data->conf.config, 1,
+ MMA9553_MASK_CONF_ACT_DBCNTM);
+ ret = mma9551_write_config_words(data->client, MMA9551_APPID_PEDOMETER,
+ MMA9553_REG_CONF_SLEEPMIN,
+ sizeof(data->conf) / sizeof(u16),
+ (u16 *)&data->conf);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "failed to write configuration registers\n");
+ return ret;
+ }
+
+ return mma9551_set_device_state(data->client, true);
+}
+
+static int mma9553_read_status_word(struct mma9553_data *data, u16 reg,
+ u16 *tmp)
+{
+ bool powered_on;
+ int ret;
+
+ /*
+ * The HW only counts steps and other dependent
+ * parameters (speed, distance, calories, activity)
+ * if power is on (from enabling an event or the
+ * step counter).
+ */
+ powered_on = mma9553_is_any_event_enabled(data, false, 0) ||
+ data->stepcnt_enabled;
+ if (!powered_on) {
+ dev_err(&data->client->dev, "No channels enabled\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&data->mutex);
+ ret = mma9551_read_status_word(data->client, MMA9551_APPID_PEDOMETER,
+ reg, tmp);
+ mutex_unlock(&data->mutex);
+ return ret;
+}
+
+static int mma9553_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mma9553_data *data = iio_priv(indio_dev);
+ int ret;
+ u16 tmp;
+ u8 activity;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (chan->type) {
+ case IIO_STEPS:
+ ret = mma9553_read_status_word(data,
+ MMA9553_REG_STEPCNT,
+ &tmp);
+ if (ret < 0)
+ return ret;
+ *val = tmp;
+ return IIO_VAL_INT;
+ case IIO_DISTANCE:
+ ret = mma9553_read_status_word(data,
+ MMA9553_REG_DISTANCE,
+ &tmp);
+ if (ret < 0)
+ return ret;
+ *val = tmp;
+ return IIO_VAL_INT;
+ case IIO_ACTIVITY:
+ ret = mma9553_read_status_word(data,
+ MMA9553_REG_STATUS,
+ &tmp);
+ if (ret < 0)
+ return ret;
+
+ activity =
+ mma9553_get_bits(tmp, MMA9553_MASK_STATUS_ACTIVITY);
+
+ /*
+ * The device does not support confidence value levels,
+ * so we will always have 100% for current activity and
+ * 0% for the others.
+ */
+ if (chan->channel2 == mma9553_activity_to_mod(activity))
+ *val = 100;
+ else
+ *val = 0;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_VELOCITY: /* m/h */
+ if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z)
+ return -EINVAL;
+ ret = mma9553_read_status_word(data,
+ MMA9553_REG_SPEED,
+ &tmp);
+ if (ret < 0)
+ return ret;
+ *val = tmp;
+ return IIO_VAL_INT;
+ case IIO_ENERGY: /* Cal or kcal */
+ ret = mma9553_read_status_word(data,
+ MMA9553_REG_CALORIES,
+ &tmp);
+ if (ret < 0)
+ return ret;
+ *val = tmp;
+ return IIO_VAL_INT;
+ case IIO_ACCEL:
+ mutex_lock(&data->mutex);
+ ret = mma9551_read_accel_chan(data->client,
+ chan, val, val2);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VELOCITY: /* m/h to m/s */
+ if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z)
+ return -EINVAL;
+ *val = 0;
+ *val2 = 277; /* 0.000277 */
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ENERGY: /* Cal or kcal to J */
+ *val = 4184;
+ return IIO_VAL_INT;
+ case IIO_ACCEL:
+ return mma9551_read_accel_scale(val, val2);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_ENABLE:
+ *val = data->stepcnt_enabled;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBHEIGHT:
+ tmp = mma9553_get_bits(data->conf.height_weight,
+ MMA9553_MASK_CONF_HEIGHT);
+ *val = tmp / 100; /* cm to m */
+ *val2 = (tmp % 100) * 10000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_CALIBWEIGHT:
+ *val = mma9553_get_bits(data->conf.height_weight,
+ MMA9553_MASK_CONF_WEIGHT);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_DEBOUNCE_COUNT:
+ switch (chan->type) {
+ case IIO_STEPS:
+ *val = mma9553_get_bits(data->conf.filter,
+ MMA9553_MASK_CONF_FILTSTEP);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_DEBOUNCE_TIME:
+ switch (chan->type) {
+ case IIO_STEPS:
+ *val = mma9553_get_bits(data->conf.filter,
+ MMA9553_MASK_CONF_FILTTIME);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_INT_TIME:
+ switch (chan->type) {
+ case IIO_VELOCITY:
+ if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z)
+ return -EINVAL;
+ *val = mma9553_get_bits(data->conf.speed_step,
+ MMA9553_MASK_CONF_SPDPRD);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma9553_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mma9553_data *data = iio_priv(indio_dev);
+ int ret, tmp;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_ENABLE:
+ if (data->stepcnt_enabled == !!val)
+ return 0;
+ mutex_lock(&data->mutex);
+ ret = mma9551_set_power_state(data->client, val);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return ret;
+ }
+ data->stepcnt_enabled = val;
+ mutex_unlock(&data->mutex);
+ return 0;
+ case IIO_CHAN_INFO_CALIBHEIGHT:
+ /* m to cm */
+ tmp = val * 100 + val2 / 10000;
+ if (tmp < 0 || tmp > 255)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = mma9553_set_config(data,
+ MMA9553_REG_CONF_HEIGHT_WEIGHT,
+ &data->conf.height_weight,
+ tmp, MMA9553_MASK_CONF_HEIGHT);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_CHAN_INFO_CALIBWEIGHT:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = mma9553_set_config(data,
+ MMA9553_REG_CONF_HEIGHT_WEIGHT,
+ &data->conf.height_weight,
+ val, MMA9553_MASK_CONF_WEIGHT);
+ mutex_unlock(&data->mutex);
+ return ret;
+ case IIO_CHAN_INFO_DEBOUNCE_COUNT:
+ switch (chan->type) {
+ case IIO_STEPS:
+ /*
+ * Set to 0 to disable step filtering. If the value
+ * specified is greater than 6, then 6 will be used.
+ */
+ if (val < 0)
+ return -EINVAL;
+ if (val > 6)
+ val = 6;
+ mutex_lock(&data->mutex);
+ ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER,
+ &data->conf.filter, val,
+ MMA9553_MASK_CONF_FILTSTEP);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_DEBOUNCE_TIME:
+ switch (chan->type) {
+ case IIO_STEPS:
+ if (val < 0 || val > 127)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER,
+ &data->conf.filter, val,
+ MMA9553_MASK_CONF_FILTTIME);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_INT_TIME:
+ switch (chan->type) {
+ case IIO_VELOCITY:
+ if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z)
+ return -EINVAL;
+ /*
+ * If set to a value greater than 5, then 5 will be
+ * used. Warning: Do not set SPDPRD to 0 or 1 as
+ * this may cause undesirable behavior.
+ */
+ if (val < 2)
+ return -EINVAL;
+ if (val > 5)
+ val = 5;
+ mutex_lock(&data->mutex);
+ ret = mma9553_set_config(data,
+ MMA9553_REG_CONF_SPEED_STEP,
+ &data->conf.speed_step, val,
+ MMA9553_MASK_CONF_SPDPRD);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma9553_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 mma9553_data *data = iio_priv(indio_dev);
+ struct mma9553_event *event;
+
+ event = mma9553_get_event(data, chan->type, chan->channel2, dir);
+ if (!event)
+ return -EINVAL;
+
+ return event->enabled;
+}
+
+static int mma9553_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct mma9553_data *data = iio_priv(indio_dev);
+ struct mma9553_event *event;
+ int ret;
+
+ event = mma9553_get_event(data, chan->type, chan->channel2, dir);
+ if (!event)
+ return -EINVAL;
+
+ if (event->enabled == state)
+ return 0;
+
+ mutex_lock(&data->mutex);
+
+ ret = mma9551_set_power_state(data->client, state);
+ if (ret < 0)
+ goto err_out;
+ event->enabled = state;
+
+ ret = mma9553_conf_gpio(data);
+ if (ret < 0)
+ goto err_conf_gpio;
+
+ mutex_unlock(&data->mutex);
+
+ return 0;
+
+err_conf_gpio:
+ if (state) {
+ event->enabled = false;
+ mma9551_set_power_state(data->client, false);
+ }
+err_out:
+ mutex_unlock(&data->mutex);
+ return ret;
+}
+
+static int mma9553_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct mma9553_data *data = iio_priv(indio_dev);
+
+ *val2 = 0;
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (chan->type) {
+ case IIO_STEPS:
+ *val = mma9553_get_bits(data->conf.speed_step,
+ MMA9553_MASK_CONF_STEPCOALESCE);
+ return IIO_VAL_INT;
+ case IIO_ACTIVITY:
+ /*
+ * The device does not support confidence value levels.
+ * We set an average of 50%.
+ */
+ *val = 50;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ switch (chan->type) {
+ case IIO_ACTIVITY:
+ *val = MMA9553_ACTIVITY_THD_TO_SEC(data->conf.actthd);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma9553_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct mma9553_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (chan->type) {
+ case IIO_STEPS:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = mma9553_set_config(data,
+ MMA9553_REG_CONF_SPEED_STEP,
+ &data->conf.speed_step, val,
+ MMA9553_MASK_CONF_STEPCOALESCE);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_INFO_PERIOD:
+ switch (chan->type) {
+ case IIO_ACTIVITY:
+ if (val < 0 || val > MMA9553_ACTIVITY_THD_TO_SEC(
+ MMA9553_MAX_ACTTHD))
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = mma9553_set_config(data, MMA9553_REG_CONF_ACTTHD,
+ &data->conf.actthd,
+ MMA9553_ACTIVITY_SEC_TO_THD
+ (val), MMA9553_MASK_CONF_WORD);
+ mutex_unlock(&data->mutex);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mma9553_get_calibgender_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct mma9553_data *data = iio_priv(indio_dev);
+ u8 gender;
+
+ gender = mma9553_get_bits(data->conf.filter, MMA9553_MASK_CONF_MALE);
+ /*
+ * HW expects 0 for female and 1 for male,
+ * while iio index is 0 for male and 1 for female.
+ */
+ return !gender;
+}
+
+static int mma9553_set_calibgender_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int mode)
+{
+ struct mma9553_data *data = iio_priv(indio_dev);
+ u8 gender = !mode;
+ int ret;
+
+ if ((mode != 0) && (mode != 1))
+ return -EINVAL;
+ mutex_lock(&data->mutex);
+ ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER,
+ &data->conf.filter, gender,
+ MMA9553_MASK_CONF_MALE);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static const struct iio_event_spec mma9553_step_event = {
+ .type = IIO_EV_TYPE_CHANGE,
+ .dir = IIO_EV_DIR_NONE,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE),
+};
+
+static const struct iio_event_spec mma9553_activity_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD),
+ },
+};
+
+static const char * const mma9553_calibgender_modes[] = { "male", "female" };
+
+static const struct iio_enum mma9553_calibgender_enum = {
+ .items = mma9553_calibgender_modes,
+ .num_items = ARRAY_SIZE(mma9553_calibgender_modes),
+ .get = mma9553_get_calibgender_mode,
+ .set = mma9553_set_calibgender_mode,
+};
+
+static const struct iio_chan_spec_ext_info mma9553_ext_info[] = {
+ IIO_ENUM("calibgender", IIO_SHARED_BY_TYPE, &mma9553_calibgender_enum),
+ IIO_ENUM_AVAILABLE("calibgender", IIO_SHARED_BY_TYPE, &mma9553_calibgender_enum),
+ { }
+};
+
+#define MMA9553_PEDOMETER_CHANNEL(_type, _mask) { \
+ .type = _type, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \
+ BIT(IIO_CHAN_INFO_CALIBHEIGHT) | \
+ _mask, \
+ .ext_info = mma9553_ext_info, \
+}
+
+#define MMA9553_ACTIVITY_CHANNEL(_chan2) { \
+ .type = IIO_ACTIVITY, \
+ .modified = 1, \
+ .channel2 = _chan2, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBHEIGHT) | \
+ BIT(IIO_CHAN_INFO_ENABLE), \
+ .event_spec = mma9553_activity_events, \
+ .num_event_specs = ARRAY_SIZE(mma9553_activity_events), \
+ .ext_info = mma9553_ext_info, \
+}
+
+static const struct iio_chan_spec mma9553_channels[] = {
+ MMA9551_ACCEL_CHANNEL(IIO_MOD_X),
+ MMA9551_ACCEL_CHANNEL(IIO_MOD_Y),
+ MMA9551_ACCEL_CHANNEL(IIO_MOD_Z),
+
+ {
+ .type = IIO_STEPS,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_ENABLE) |
+ BIT(IIO_CHAN_INFO_DEBOUNCE_COUNT) |
+ BIT(IIO_CHAN_INFO_DEBOUNCE_TIME),
+ .event_spec = &mma9553_step_event,
+ .num_event_specs = 1,
+ },
+
+ MMA9553_PEDOMETER_CHANNEL(IIO_DISTANCE, BIT(IIO_CHAN_INFO_PROCESSED)),
+ {
+ .type = IIO_VELOCITY,
+ .modified = 1,
+ .channel2 = IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_ENABLE),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBHEIGHT),
+ .ext_info = mma9553_ext_info,
+ },
+ MMA9553_PEDOMETER_CHANNEL(IIO_ENERGY, BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_CALIBWEIGHT)),
+
+ MMA9553_ACTIVITY_CHANNEL(IIO_MOD_RUNNING),
+ MMA9553_ACTIVITY_CHANNEL(IIO_MOD_JOGGING),
+ MMA9553_ACTIVITY_CHANNEL(IIO_MOD_WALKING),
+ MMA9553_ACTIVITY_CHANNEL(IIO_MOD_STILL),
+};
+
+static const struct iio_info mma9553_info = {
+ .read_raw = mma9553_read_raw,
+ .write_raw = mma9553_write_raw,
+ .read_event_config = mma9553_read_event_config,
+ .write_event_config = mma9553_write_event_config,
+ .read_event_value = mma9553_read_event_value,
+ .write_event_value = mma9553_write_event_value,
+};
+
+static irqreturn_t mma9553_irq_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct mma9553_data *data = iio_priv(indio_dev);
+
+ data->timestamp = iio_get_time_ns(indio_dev);
+ /*
+ * Since we only configure the interrupt pin when an
+ * event is enabled, we are sure we have at least
+ * one event enabled at this point.
+ */
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t mma9553_event_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct mma9553_data *data = iio_priv(indio_dev);
+ u16 stepcnt;
+ u8 activity;
+ struct mma9553_event *ev_activity, *ev_prev_activity, *ev_step_detect;
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = mma9553_read_activity_stepcnt(data, &activity, &stepcnt);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ return IRQ_HANDLED;
+ }
+
+ ev_prev_activity = mma9553_get_event(data, IIO_ACTIVITY,
+ mma9553_activity_to_mod(
+ data->activity),
+ IIO_EV_DIR_FALLING);
+ ev_activity = mma9553_get_event(data, IIO_ACTIVITY,
+ mma9553_activity_to_mod(activity),
+ IIO_EV_DIR_RISING);
+ ev_step_detect = mma9553_get_event(data, IIO_STEPS, IIO_NO_MOD,
+ IIO_EV_DIR_NONE);
+
+ if (ev_step_detect->enabled && (stepcnt != data->stepcnt)) {
+ data->stepcnt = stepcnt;
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_STEPS, 0,
+ IIO_EV_TYPE_CHANGE,
+ IIO_EV_DIR_NONE),
+ data->timestamp);
+ }
+
+ if (activity != data->activity) {
+ data->activity = activity;
+ /* ev_activity can be NULL if activity == ACTIVITY_UNKNOWN */
+ if (ev_prev_activity && ev_prev_activity->enabled)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACTIVITY, 0,
+ ev_prev_activity->info->mod,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_FALLING),
+ data->timestamp);
+
+ if (ev_activity && ev_activity->enabled)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACTIVITY, 0,
+ ev_activity->info->mod,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_RISING),
+ data->timestamp);
+ }
+ mutex_unlock(&data->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static int mma9553_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct mma9553_data *data;
+ struct iio_dev *indio_dev;
+ const char *name = NULL;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+
+ if (id)
+ name = id->name;
+ else
+ name = iio_get_acpi_device_name(&client->dev);
+ if (!name)
+ return -ENOSYS;
+
+ mutex_init(&data->mutex);
+ mma9553_init_events(data);
+
+ ret = mma9553_init(data);
+ if (ret < 0)
+ return ret;
+
+ indio_dev->channels = mma9553_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mma9553_channels);
+ indio_dev->name = name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &mma9553_info;
+
+ if (client->irq > 0) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ mma9553_irq_handler,
+ mma9553_event_handler,
+ IRQF_TRIGGER_RISING,
+ "mma9553_event", indio_dev);
+ if (ret)
+ goto out_poweroff;
+ }
+
+ ret = pm_runtime_set_active(&client->dev);
+ if (ret < 0)
+ goto out_poweroff;
+
+ pm_runtime_enable(&client->dev);
+ pm_runtime_set_autosuspend_delay(&client->dev,
+ MMA9551_AUTO_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(&client->dev);
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "unable to register iio device\n");
+ goto err_pm_cleanup;
+ }
+
+ dev_dbg(&indio_dev->dev, "Registered device %s\n", name);
+ return 0;
+
+err_pm_cleanup:
+ pm_runtime_dont_use_autosuspend(&client->dev);
+ pm_runtime_disable(&client->dev);
+out_poweroff:
+ mma9551_set_device_state(client, false);
+ return ret;
+}
+
+static void mma9553_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct mma9553_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ pm_runtime_disable(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+
+ mutex_lock(&data->mutex);
+ mma9551_set_device_state(data->client, false);
+ mutex_unlock(&data->mutex);
+}
+
+static int mma9553_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mma9553_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = mma9551_set_device_state(data->client, false);
+ mutex_unlock(&data->mutex);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "powering off device failed\n");
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static int mma9553_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mma9553_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = mma9551_set_device_state(data->client, true);
+ if (ret < 0)
+ return ret;
+
+ mma9551_sleep(MMA9553_DEFAULT_SAMPLE_RATE);
+
+ return 0;
+}
+
+static int mma9553_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mma9553_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = mma9551_set_device_state(data->client, false);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static int mma9553_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct mma9553_data *data = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&data->mutex);
+ ret = mma9551_set_device_state(data->client, true);
+ mutex_unlock(&data->mutex);
+
+ return ret;
+}
+
+static const struct dev_pm_ops mma9553_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(mma9553_suspend, mma9553_resume)
+ RUNTIME_PM_OPS(mma9553_runtime_suspend, mma9553_runtime_resume, NULL)
+};
+
+static const struct acpi_device_id mma9553_acpi_match[] = {
+ {"MMA9553", 0},
+ { }
+};
+
+MODULE_DEVICE_TABLE(acpi, mma9553_acpi_match);
+
+static const struct i2c_device_id mma9553_id[] = {
+ { .name = "mma9553" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, mma9553_id);
+
+static struct i2c_driver mma9553_driver = {
+ .driver = {
+ .name = "mma9553",
+ .acpi_match_table = mma9553_acpi_match,
+ .pm = pm_ptr(&mma9553_pm_ops),
+ },
+ .probe = mma9553_probe,
+ .remove = mma9553_remove,
+ .id_table = mma9553_id,
+};
+
+module_i2c_driver(mma9553_driver);
+
+MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MMA9553L pedometer platform driver");
+MODULE_IMPORT_NS("IIO_MMA9551");
diff --git a/drivers/iio/accel/msa311.c b/drivers/iio/accel/msa311.c
new file mode 100644
index 000000000..caf9b1b63
--- /dev/null
+++ b/drivers/iio/accel/msa311.c
@@ -0,0 +1,1295 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MEMSensing digital 3-Axis accelerometer
+ *
+ * MSA311 is a tri-axial, low-g accelerometer with I2C digital output for
+ * sensitivity consumer applications. It has dynamic user-selectable full
+ * scales range of +-2g/+-4g/+-8g/+-16g and allows acceleration measurements
+ * with output data rates from 1Hz to 1000Hz.
+ *
+ * MSA311 is available in an ultra small (2mm x 2mm, height 0.95mm) LGA package
+ * and is guaranteed to operate over -40C to +85C.
+ *
+ * This driver supports following MSA311 features:
+ * - IIO interface
+ * - Different power modes: NORMAL, SUSPEND
+ * - ODR (Output Data Rate) selection
+ * - Scale selection
+ * - IIO triggered buffer
+ * - NEW_DATA interrupt + trigger
+ *
+ * Below features to be done:
+ * - Motion Events: ACTIVE, TAP, ORIENT, FREEFALL
+ * - Low Power mode
+ *
+ * Copyright (c) 2022, SberDevices. All Rights Reserved.
+ *
+ * Author: Dmitry Rokosov <ddrokosov@sberdevices.ru>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/string_choices.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define MSA311_SOFT_RESET_REG 0x00
+#define MSA311_PARTID_REG 0x01
+#define MSA311_ACC_X_REG 0x02
+#define MSA311_ACC_Y_REG 0x04
+#define MSA311_ACC_Z_REG 0x06
+#define MSA311_MOTION_INT_REG 0x09
+#define MSA311_DATA_INT_REG 0x0A
+#define MSA311_TAP_ACTIVE_STS_REG 0x0B
+#define MSA311_ORIENT_STS_REG 0x0C
+#define MSA311_RANGE_REG 0x0F
+#define MSA311_ODR_REG 0x10
+#define MSA311_PWR_MODE_REG 0x11
+#define MSA311_SWAP_POLARITY_REG 0x12
+#define MSA311_INT_SET_0_REG 0x16
+#define MSA311_INT_SET_1_REG 0x17
+#define MSA311_INT_MAP_0_REG 0x19
+#define MSA311_INT_MAP_1_REG 0x1A
+#define MSA311_INT_CONFIG_REG 0x20
+#define MSA311_INT_LATCH_REG 0x21
+#define MSA311_FREEFALL_DUR_REG 0x22
+#define MSA311_FREEFALL_TH_REG 0x23
+#define MSA311_FREEFALL_HY_REG 0x24
+#define MSA311_ACTIVE_DUR_REG 0x27
+#define MSA311_ACTIVE_TH_REG 0x28
+#define MSA311_TAP_DUR_REG 0x2A
+#define MSA311_TAP_TH_REG 0x2B
+#define MSA311_ORIENT_HY_REG 0x2C
+#define MSA311_Z_BLOCK_REG 0x2D
+#define MSA311_OFFSET_X_REG 0x38
+#define MSA311_OFFSET_Y_REG 0x39
+#define MSA311_OFFSET_Z_REG 0x3A
+
+enum msa311_fields {
+ /* Soft_Reset */
+ F_SOFT_RESET_I2C, F_SOFT_RESET_SPI,
+ /* Motion_Interrupt */
+ F_ORIENT_INT, F_S_TAP_INT, F_D_TAP_INT, F_ACTIVE_INT, F_FREEFALL_INT,
+ /* Data_Interrupt */
+ F_NEW_DATA_INT,
+ /* Tap_Active_Status */
+ F_TAP_SIGN, F_TAP_FIRST_X, F_TAP_FIRST_Y, F_TAP_FIRST_Z, F_ACTV_SIGN,
+ F_ACTV_FIRST_X, F_ACTV_FIRST_Y, F_ACTV_FIRST_Z,
+ /* Orientation_Status */
+ F_ORIENT_Z, F_ORIENT_X_Y,
+ /* Range */
+ F_FS,
+ /* ODR */
+ F_X_AXIS_DIS, F_Y_AXIS_DIS, F_Z_AXIS_DIS, F_ODR,
+ /* Power Mode/Bandwidth */
+ F_PWR_MODE, F_LOW_POWER_BW,
+ /* Swap_Polarity */
+ F_X_POLARITY, F_Y_POLARITY, F_Z_POLARITY, F_X_Y_SWAP,
+ /* Int_Set_0 */
+ F_ORIENT_INT_EN, F_S_TAP_INT_EN, F_D_TAP_INT_EN, F_ACTIVE_INT_EN_Z,
+ F_ACTIVE_INT_EN_Y, F_ACTIVE_INT_EN_X,
+ /* Int_Set_1 */
+ F_NEW_DATA_INT_EN, F_FREEFALL_INT_EN,
+ /* Int_Map_0 */
+ F_INT1_ORIENT, F_INT1_S_TAP, F_INT1_D_TAP, F_INT1_ACTIVE,
+ F_INT1_FREEFALL,
+ /* Int_Map_1 */
+ F_INT1_NEW_DATA,
+ /* Int_Config */
+ F_INT1_OD, F_INT1_LVL,
+ /* Int_Latch */
+ F_RESET_INT, F_LATCH_INT,
+ /* Freefall_Hy */
+ F_FREEFALL_MODE, F_FREEFALL_HY,
+ /* Active_Dur */
+ F_ACTIVE_DUR,
+ /* Tap_Dur */
+ F_TAP_QUIET, F_TAP_SHOCK, F_TAP_DUR,
+ /* Tap_Th */
+ F_TAP_TH,
+ /* Orient_Hy */
+ F_ORIENT_HYST, F_ORIENT_BLOCKING, F_ORIENT_MODE,
+ /* Z_Block */
+ F_Z_BLOCKING,
+ /* End of register map */
+ F_MAX_FIELDS,
+};
+
+static const struct reg_field msa311_reg_fields[] = {
+ /* Soft_Reset */
+ [F_SOFT_RESET_I2C] = REG_FIELD(MSA311_SOFT_RESET_REG, 2, 2),
+ [F_SOFT_RESET_SPI] = REG_FIELD(MSA311_SOFT_RESET_REG, 5, 5),
+ /* Motion_Interrupt */
+ [F_ORIENT_INT] = REG_FIELD(MSA311_MOTION_INT_REG, 6, 6),
+ [F_S_TAP_INT] = REG_FIELD(MSA311_MOTION_INT_REG, 5, 5),
+ [F_D_TAP_INT] = REG_FIELD(MSA311_MOTION_INT_REG, 4, 4),
+ [F_ACTIVE_INT] = REG_FIELD(MSA311_MOTION_INT_REG, 2, 2),
+ [F_FREEFALL_INT] = REG_FIELD(MSA311_MOTION_INT_REG, 0, 0),
+ /* Data_Interrupt */
+ [F_NEW_DATA_INT] = REG_FIELD(MSA311_DATA_INT_REG, 0, 0),
+ /* Tap_Active_Status */
+ [F_TAP_SIGN] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 7, 7),
+ [F_TAP_FIRST_X] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 6, 6),
+ [F_TAP_FIRST_Y] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 5, 5),
+ [F_TAP_FIRST_Z] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 4, 4),
+ [F_ACTV_SIGN] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 3, 3),
+ [F_ACTV_FIRST_X] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 2, 2),
+ [F_ACTV_FIRST_Y] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 1, 1),
+ [F_ACTV_FIRST_Z] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 0, 0),
+ /* Orientation_Status */
+ [F_ORIENT_Z] = REG_FIELD(MSA311_ORIENT_STS_REG, 6, 6),
+ [F_ORIENT_X_Y] = REG_FIELD(MSA311_ORIENT_STS_REG, 4, 5),
+ /* Range */
+ [F_FS] = REG_FIELD(MSA311_RANGE_REG, 0, 1),
+ /* ODR */
+ [F_X_AXIS_DIS] = REG_FIELD(MSA311_ODR_REG, 7, 7),
+ [F_Y_AXIS_DIS] = REG_FIELD(MSA311_ODR_REG, 6, 6),
+ [F_Z_AXIS_DIS] = REG_FIELD(MSA311_ODR_REG, 5, 5),
+ [F_ODR] = REG_FIELD(MSA311_ODR_REG, 0, 3),
+ /* Power Mode/Bandwidth */
+ [F_PWR_MODE] = REG_FIELD(MSA311_PWR_MODE_REG, 6, 7),
+ [F_LOW_POWER_BW] = REG_FIELD(MSA311_PWR_MODE_REG, 1, 4),
+ /* Swap_Polarity */
+ [F_X_POLARITY] = REG_FIELD(MSA311_SWAP_POLARITY_REG, 3, 3),
+ [F_Y_POLARITY] = REG_FIELD(MSA311_SWAP_POLARITY_REG, 2, 2),
+ [F_Z_POLARITY] = REG_FIELD(MSA311_SWAP_POLARITY_REG, 1, 1),
+ [F_X_Y_SWAP] = REG_FIELD(MSA311_SWAP_POLARITY_REG, 0, 0),
+ /* Int_Set_0 */
+ [F_ORIENT_INT_EN] = REG_FIELD(MSA311_INT_SET_0_REG, 6, 6),
+ [F_S_TAP_INT_EN] = REG_FIELD(MSA311_INT_SET_0_REG, 5, 5),
+ [F_D_TAP_INT_EN] = REG_FIELD(MSA311_INT_SET_0_REG, 4, 4),
+ [F_ACTIVE_INT_EN_Z] = REG_FIELD(MSA311_INT_SET_0_REG, 2, 2),
+ [F_ACTIVE_INT_EN_Y] = REG_FIELD(MSA311_INT_SET_0_REG, 1, 1),
+ [F_ACTIVE_INT_EN_X] = REG_FIELD(MSA311_INT_SET_0_REG, 0, 0),
+ /* Int_Set_1 */
+ [F_NEW_DATA_INT_EN] = REG_FIELD(MSA311_INT_SET_1_REG, 4, 4),
+ [F_FREEFALL_INT_EN] = REG_FIELD(MSA311_INT_SET_1_REG, 3, 3),
+ /* Int_Map_0 */
+ [F_INT1_ORIENT] = REG_FIELD(MSA311_INT_MAP_0_REG, 6, 6),
+ [F_INT1_S_TAP] = REG_FIELD(MSA311_INT_MAP_0_REG, 5, 5),
+ [F_INT1_D_TAP] = REG_FIELD(MSA311_INT_MAP_0_REG, 4, 4),
+ [F_INT1_ACTIVE] = REG_FIELD(MSA311_INT_MAP_0_REG, 2, 2),
+ [F_INT1_FREEFALL] = REG_FIELD(MSA311_INT_MAP_0_REG, 0, 0),
+ /* Int_Map_1 */
+ [F_INT1_NEW_DATA] = REG_FIELD(MSA311_INT_MAP_1_REG, 0, 0),
+ /* Int_Config */
+ [F_INT1_OD] = REG_FIELD(MSA311_INT_CONFIG_REG, 1, 1),
+ [F_INT1_LVL] = REG_FIELD(MSA311_INT_CONFIG_REG, 0, 0),
+ /* Int_Latch */
+ [F_RESET_INT] = REG_FIELD(MSA311_INT_LATCH_REG, 7, 7),
+ [F_LATCH_INT] = REG_FIELD(MSA311_INT_LATCH_REG, 0, 3),
+ /* Freefall_Hy */
+ [F_FREEFALL_MODE] = REG_FIELD(MSA311_FREEFALL_HY_REG, 2, 2),
+ [F_FREEFALL_HY] = REG_FIELD(MSA311_FREEFALL_HY_REG, 0, 1),
+ /* Active_Dur */
+ [F_ACTIVE_DUR] = REG_FIELD(MSA311_ACTIVE_DUR_REG, 0, 1),
+ /* Tap_Dur */
+ [F_TAP_QUIET] = REG_FIELD(MSA311_TAP_DUR_REG, 7, 7),
+ [F_TAP_SHOCK] = REG_FIELD(MSA311_TAP_DUR_REG, 6, 6),
+ [F_TAP_DUR] = REG_FIELD(MSA311_TAP_DUR_REG, 0, 2),
+ /* Tap_Th */
+ [F_TAP_TH] = REG_FIELD(MSA311_TAP_TH_REG, 0, 4),
+ /* Orient_Hy */
+ [F_ORIENT_HYST] = REG_FIELD(MSA311_ORIENT_HY_REG, 4, 6),
+ [F_ORIENT_BLOCKING] = REG_FIELD(MSA311_ORIENT_HY_REG, 2, 3),
+ [F_ORIENT_MODE] = REG_FIELD(MSA311_ORIENT_HY_REG, 0, 1),
+ /* Z_Block */
+ [F_Z_BLOCKING] = REG_FIELD(MSA311_Z_BLOCK_REG, 0, 3),
+};
+
+#define MSA311_WHO_AM_I 0x13
+
+/*
+ * Possible Full Scale ranges
+ *
+ * Axis data is 12-bit signed value, so
+ *
+ * fs0 = (2 + 2) * 9.81 / (2^11) = 0.009580
+ * fs1 = (4 + 4) * 9.81 / (2^11) = 0.019160
+ * fs2 = (8 + 8) * 9.81 / (2^11) = 0.038320
+ * fs3 = (16 + 16) * 9.81 / (2^11) = 0.076641
+ */
+enum {
+ MSA311_FS_2G,
+ MSA311_FS_4G,
+ MSA311_FS_8G,
+ MSA311_FS_16G,
+};
+
+struct iio_decimal_fract {
+ int integral;
+ int microfract;
+};
+
+static const struct iio_decimal_fract msa311_fs_table[] = {
+ {0, 9580}, {0, 19160}, {0, 38320}, {0, 76641},
+};
+
+/* Possible Output Data Rate values */
+enum {
+ MSA311_ODR_1_HZ,
+ MSA311_ODR_1_95_HZ,
+ MSA311_ODR_3_9_HZ,
+ MSA311_ODR_7_81_HZ,
+ MSA311_ODR_15_63_HZ,
+ MSA311_ODR_31_25_HZ,
+ MSA311_ODR_62_5_HZ,
+ MSA311_ODR_125_HZ,
+ MSA311_ODR_250_HZ,
+ MSA311_ODR_500_HZ,
+ MSA311_ODR_1000_HZ,
+};
+
+static const struct iio_decimal_fract msa311_odr_table[] = {
+ {1, 0}, {1, 950000}, {3, 900000}, {7, 810000}, {15, 630000},
+ {31, 250000}, {62, 500000}, {125, 0}, {250, 0}, {500, 0}, {1000, 0},
+};
+
+/* All supported power modes */
+#define MSA311_PWR_MODE_NORMAL 0b00
+#define MSA311_PWR_MODE_LOW 0b01
+#define MSA311_PWR_MODE_UNKNOWN 0b10
+#define MSA311_PWR_MODE_SUSPEND 0b11
+static const char * const msa311_pwr_modes[] = {
+ [MSA311_PWR_MODE_NORMAL] = "normal",
+ [MSA311_PWR_MODE_LOW] = "low",
+ [MSA311_PWR_MODE_UNKNOWN] = "unknown",
+ [MSA311_PWR_MODE_SUSPEND] = "suspend",
+};
+
+/* Autosuspend delay */
+#define MSA311_PWR_SLEEP_DELAY_MS 2000
+
+/* Possible INT1 types and levels */
+enum {
+ MSA311_INT1_OD_PUSH_PULL,
+ MSA311_INT1_OD_OPEN_DRAIN,
+};
+
+enum {
+ MSA311_INT1_LVL_LOW,
+ MSA311_INT1_LVL_HIGH,
+};
+
+/* Latch INT modes */
+#define MSA311_LATCH_INT_NOT_LATCHED 0b0000
+#define MSA311_LATCH_INT_250MS 0b0001
+#define MSA311_LATCH_INT_500MS 0b0010
+#define MSA311_LATCH_INT_1S 0b0011
+#define MSA311_LATCH_INT_2S 0b0100
+#define MSA311_LATCH_INT_4S 0b0101
+#define MSA311_LATCH_INT_8S 0b0110
+#define MSA311_LATCH_INT_1MS 0b1010
+#define MSA311_LATCH_INT_2MS 0b1011
+#define MSA311_LATCH_INT_25MS 0b1100
+#define MSA311_LATCH_INT_50MS 0b1101
+#define MSA311_LATCH_INT_100MS 0b1110
+#define MSA311_LATCH_INT_LATCHED 0b0111
+
+static const struct regmap_range msa311_readonly_registers[] = {
+ regmap_reg_range(MSA311_PARTID_REG, MSA311_ORIENT_STS_REG),
+};
+
+static const struct regmap_access_table msa311_writeable_table = {
+ .no_ranges = msa311_readonly_registers,
+ .n_no_ranges = ARRAY_SIZE(msa311_readonly_registers),
+};
+
+static const struct regmap_range msa311_writeonly_registers[] = {
+ regmap_reg_range(MSA311_SOFT_RESET_REG, MSA311_SOFT_RESET_REG),
+};
+
+static const struct regmap_access_table msa311_readable_table = {
+ .no_ranges = msa311_writeonly_registers,
+ .n_no_ranges = ARRAY_SIZE(msa311_writeonly_registers),
+};
+
+static const struct regmap_range msa311_volatile_registers[] = {
+ regmap_reg_range(MSA311_ACC_X_REG, MSA311_ORIENT_STS_REG),
+};
+
+static const struct regmap_access_table msa311_volatile_table = {
+ .yes_ranges = msa311_volatile_registers,
+ .n_yes_ranges = ARRAY_SIZE(msa311_volatile_registers),
+};
+
+static const struct regmap_config msa311_regmap_config = {
+ .name = "msa311",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MSA311_OFFSET_Z_REG,
+ .wr_table = &msa311_writeable_table,
+ .rd_table = &msa311_readable_table,
+ .volatile_table = &msa311_volatile_table,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+#define MSA311_GENMASK(field) ({ \
+ typeof(&(msa311_reg_fields)[0]) _field; \
+ _field = &msa311_reg_fields[(field)]; \
+ GENMASK(_field->msb, _field->lsb); \
+})
+
+/**
+ * struct msa311_priv - MSA311 internal private state
+ * @regs: Underlying I2C bus adapter used to abstract slave
+ * register accesses
+ * @fields: Abstract objects for each registers fields access
+ * @dev: Device handler associated with appropriate bus client
+ * @lock: Protects msa311 device state between setup and data access routines
+ * (power transitions, samp_freq/scale tune, retrieving axes data, etc)
+ * @chip_name: Chip name in the format "msa311-%02x" % partid
+ * @new_data_trig: Optional NEW_DATA interrupt driven trigger used
+ * to notify external consumers a new sample is ready
+ */
+struct msa311_priv {
+ struct regmap *regs;
+ struct regmap_field *fields[F_MAX_FIELDS];
+
+ struct device *dev;
+ struct mutex lock;
+ char *chip_name;
+
+ struct iio_trigger *new_data_trig;
+};
+
+enum msa311_si {
+ MSA311_SI_X,
+ MSA311_SI_Y,
+ MSA311_SI_Z,
+ MSA311_SI_TIMESTAMP,
+};
+
+#define MSA311_ACCEL_CHANNEL(axis) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .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_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = MSA311_SI_##axis, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .shift = 4, \
+ .endianness = IIO_LE, \
+ }, \
+ .datasheet_name = "ACC_"#axis, \
+}
+
+static const struct iio_chan_spec msa311_channels[] = {
+ MSA311_ACCEL_CHANNEL(X),
+ MSA311_ACCEL_CHANNEL(Y),
+ MSA311_ACCEL_CHANNEL(Z),
+ IIO_CHAN_SOFT_TIMESTAMP(MSA311_SI_TIMESTAMP),
+};
+
+/**
+ * msa311_get_odr() - Read Output Data Rate (ODR) value from MSA311 accel
+ * @msa311: MSA311 internal private state
+ * @odr: output ODR value
+ *
+ * This function should be called under msa311->lock.
+ *
+ * Return: 0 on success, -ERRNO in other failures
+ */
+static int msa311_get_odr(struct msa311_priv *msa311, unsigned int *odr)
+{
+ int err;
+
+ err = regmap_field_read(msa311->fields[F_ODR], odr);
+ if (err)
+ return err;
+
+ /*
+ * Filter the same 1000Hz ODR register values based on datasheet info.
+ * ODR can be equal to 1010-1111 for 1000Hz, but function returns 1010
+ * all the time.
+ */
+ if (*odr > MSA311_ODR_1000_HZ)
+ *odr = MSA311_ODR_1000_HZ;
+
+ return 0;
+}
+
+/**
+ * msa311_set_odr() - Setup Output Data Rate (ODR) value for MSA311 accel
+ * @msa311: MSA311 internal private state
+ * @odr: requested ODR value
+ *
+ * This function should be called under msa311->lock. Possible ODR values:
+ * - 1Hz (not available in normal mode)
+ * - 1.95Hz (not available in normal mode)
+ * - 3.9Hz
+ * - 7.81Hz
+ * - 15.63Hz
+ * - 31.25Hz
+ * - 62.5Hz
+ * - 125Hz
+ * - 250Hz
+ * - 500Hz
+ * - 1000Hz
+ *
+ * Return: 0 on success, -EINVAL for bad ODR value in the certain power mode,
+ * -ERRNO in other failures
+ */
+static int msa311_set_odr(struct msa311_priv *msa311, unsigned int odr)
+{
+ struct device *dev = msa311->dev;
+ unsigned int pwr_mode;
+ bool good_odr;
+ int err;
+
+ err = regmap_field_read(msa311->fields[F_PWR_MODE], &pwr_mode);
+ if (err)
+ return err;
+
+ /* Filter bad ODR values */
+ if (pwr_mode == MSA311_PWR_MODE_NORMAL)
+ good_odr = (odr > MSA311_ODR_1_95_HZ);
+ else
+ good_odr = false;
+
+ if (!good_odr) {
+ dev_err(dev,
+ "can't set odr %u.%06uHz, not available in %s mode\n",
+ msa311_odr_table[odr].integral,
+ msa311_odr_table[odr].microfract,
+ msa311_pwr_modes[pwr_mode]);
+ return -EINVAL;
+ }
+
+ return regmap_field_write(msa311->fields[F_ODR], odr);
+}
+
+/**
+ * msa311_wait_for_next_data() - Wait next accel data available after resume
+ * @msa311: MSA311 internal private state
+ *
+ * Return: 0 on success, -EINTR if msleep() was interrupted,
+ * -ERRNO in other failures
+ */
+static int msa311_wait_for_next_data(struct msa311_priv *msa311)
+{
+ static const unsigned int unintr_thresh_ms = 20;
+ struct device *dev = msa311->dev;
+ unsigned long freq_uhz;
+ unsigned long wait_ms;
+ unsigned int odr;
+ int err;
+
+ err = msa311_get_odr(msa311, &odr);
+ if (err) {
+ dev_err(dev, "can't get actual frequency (%pe)\n",
+ ERR_PTR(err));
+ return err;
+ }
+
+ /*
+ * After msa311 resuming is done, we need to wait for data
+ * to be refreshed by accel logic.
+ * A certain timeout is calculated based on the current ODR value.
+ * If requested timeout isn't so long (let's assume 20ms),
+ * we can wait for next data in uninterruptible sleep.
+ */
+ freq_uhz = msa311_odr_table[odr].integral * MICROHZ_PER_HZ +
+ msa311_odr_table[odr].microfract;
+ wait_ms = (MICROHZ_PER_HZ / freq_uhz) * MSEC_PER_SEC;
+
+ if (wait_ms < unintr_thresh_ms)
+ usleep_range(wait_ms * USEC_PER_MSEC,
+ unintr_thresh_ms * USEC_PER_MSEC);
+ else if (msleep_interruptible(wait_ms))
+ return -EINTR;
+
+ return 0;
+}
+
+/**
+ * msa311_set_pwr_mode() - Install certain MSA311 power mode
+ * @msa311: MSA311 internal private state
+ * @mode: Power mode can be equal to NORMAL or SUSPEND
+ *
+ * This function should be called under msa311->lock.
+ *
+ * Return: 0 on success, -ERRNO on failure
+ */
+static int msa311_set_pwr_mode(struct msa311_priv *msa311, unsigned int mode)
+{
+ struct device *dev = msa311->dev;
+ unsigned int prev_mode;
+ int err;
+
+ if (mode >= ARRAY_SIZE(msa311_pwr_modes))
+ return -EINVAL;
+
+ dev_dbg(dev, "transition to %s mode\n", msa311_pwr_modes[mode]);
+
+ err = regmap_field_read(msa311->fields[F_PWR_MODE], &prev_mode);
+ if (err)
+ return err;
+
+ err = regmap_field_write(msa311->fields[F_PWR_MODE], mode);
+ if (err)
+ return err;
+
+ /* Wait actual data if we wake up */
+ if (prev_mode == MSA311_PWR_MODE_SUSPEND &&
+ mode == MSA311_PWR_MODE_NORMAL)
+ return msa311_wait_for_next_data(msa311);
+
+ return 0;
+}
+
+/**
+ * msa311_get_axis() - Read MSA311 accel data for certain IIO channel axis spec
+ * @msa311: MSA311 internal private state
+ * @chan: IIO channel specification
+ * @axis: Output accel axis data for requested IIO channel spec
+ *
+ * This function should be called under msa311->lock.
+ *
+ * Return: 0 on success, -EINVAL for unknown IIO channel specification,
+ * -ERRNO in other failures
+ */
+static int msa311_get_axis(struct msa311_priv *msa311,
+ const struct iio_chan_spec * const chan,
+ __le16 *axis)
+{
+ struct device *dev = msa311->dev;
+ unsigned int axis_reg;
+
+ if (chan->scan_index < MSA311_SI_X || chan->scan_index > MSA311_SI_Z) {
+ dev_err(dev, "invalid scan_index value [%d]\n",
+ chan->scan_index);
+ return -EINVAL;
+ }
+
+ /* Axes data layout has 2 byte gap for each axis starting from X axis */
+ axis_reg = MSA311_ACC_X_REG + (chan->scan_index << 1);
+
+ return regmap_bulk_read(msa311->regs, axis_reg, axis, sizeof(*axis));
+}
+
+static int msa311_read_raw_data(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2)
+{
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ struct device *dev = msa311->dev;
+ __le16 axis;
+ int err;
+
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ err = pm_runtime_resume_and_get(dev);
+ if (err) {
+ iio_device_release_direct(indio_dev);
+ return err;
+ }
+
+ mutex_lock(&msa311->lock);
+ err = msa311_get_axis(msa311, chan, &axis);
+ mutex_unlock(&msa311->lock);
+
+ pm_runtime_put_autosuspend(dev);
+
+ iio_device_release_direct(indio_dev);
+
+ if (err) {
+ dev_err(dev, "can't get axis %s (%pe)\n",
+ chan->datasheet_name, ERR_PTR(err));
+ return err;
+ }
+
+ /*
+ * Axis data format is:
+ * ACC_X = (ACC_X_MSB[7:0] << 4) | ACC_X_LSB[7:4]
+ */
+ *val = sign_extend32(le16_to_cpu(axis) >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+
+ return IIO_VAL_INT;
+}
+
+static int msa311_read_scale(struct iio_dev *indio_dev, int *val, int *val2)
+{
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ struct device *dev = msa311->dev;
+ unsigned int fs;
+ int err;
+
+ mutex_lock(&msa311->lock);
+ err = regmap_field_read(msa311->fields[F_FS], &fs);
+ mutex_unlock(&msa311->lock);
+ if (err) {
+ dev_err(dev, "can't get actual scale (%pe)\n", ERR_PTR(err));
+ return err;
+ }
+
+ *val = msa311_fs_table[fs].integral;
+ *val2 = msa311_fs_table[fs].microfract;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int msa311_read_samp_freq(struct iio_dev *indio_dev,
+ int *val, int *val2)
+{
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ struct device *dev = msa311->dev;
+ unsigned int odr;
+ int err;
+
+ mutex_lock(&msa311->lock);
+ err = msa311_get_odr(msa311, &odr);
+ mutex_unlock(&msa311->lock);
+ if (err) {
+ dev_err(dev, "can't get actual frequency (%pe)\n",
+ ERR_PTR(err));
+ return err;
+ }
+
+ *val = msa311_odr_table[odr].integral;
+ *val2 = msa311_odr_table[odr].microfract;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int msa311_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return msa311_read_raw_data(indio_dev, chan, val, val2);
+
+ case IIO_CHAN_INFO_SCALE:
+ return msa311_read_scale(indio_dev, val, val2);
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return msa311_read_samp_freq(indio_dev, val, val2);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int msa311_read_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 = (int *)msa311_odr_table;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ /* ODR value has 2 ints (integer and fractional parts) */
+ *length = ARRAY_SIZE(msa311_odr_table) * 2;
+ return IIO_AVAIL_LIST;
+
+ case IIO_CHAN_INFO_SCALE:
+ *vals = (int *)msa311_fs_table;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ /* FS value has 2 ints (integer and fractional parts) */
+ *length = ARRAY_SIZE(msa311_fs_table) * 2;
+ return IIO_AVAIL_LIST;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int msa311_write_scale(struct iio_dev *indio_dev, int val, int val2)
+{
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ struct device *dev = msa311->dev;
+ unsigned int fs;
+ int err;
+
+ /* We do not have fs >= 1, so skip such values */
+ if (val)
+ return 0;
+
+ err = pm_runtime_resume_and_get(dev);
+ if (err)
+ return err;
+
+ err = -EINVAL;
+ for (fs = 0; fs < ARRAY_SIZE(msa311_fs_table); fs++)
+ /* Do not check msa311_fs_table[fs].integral, it's always 0 */
+ if (val2 == msa311_fs_table[fs].microfract) {
+ mutex_lock(&msa311->lock);
+ err = regmap_field_write(msa311->fields[F_FS], fs);
+ mutex_unlock(&msa311->lock);
+ break;
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ if (err)
+ dev_err(dev, "can't update scale (%pe)\n", ERR_PTR(err));
+
+ return err;
+}
+
+static int msa311_write_samp_freq(struct iio_dev *indio_dev, int val, int val2)
+{
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ struct device *dev = msa311->dev;
+ unsigned int odr;
+ int err;
+
+ /*
+ * Sampling frequency changing is prohibited when buffer mode is
+ * enabled, because sometimes MSA311 chip returns outliers during
+ * frequency values growing up in the read operation moment.
+ */
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+
+ err = pm_runtime_resume_and_get(dev);
+ if (err) {
+ iio_device_release_direct(indio_dev);
+ return err;
+ }
+
+ err = -EINVAL;
+ for (odr = 0; odr < ARRAY_SIZE(msa311_odr_table); odr++)
+ if (val == msa311_odr_table[odr].integral &&
+ val2 == msa311_odr_table[odr].microfract) {
+ mutex_lock(&msa311->lock);
+ err = msa311_set_odr(msa311, odr);
+ mutex_unlock(&msa311->lock);
+ break;
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ iio_device_release_direct(indio_dev);
+
+ if (err)
+ dev_err(dev, "can't update frequency (%pe)\n", ERR_PTR(err));
+
+ return err;
+}
+
+static int msa311_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return msa311_write_scale(indio_dev, val, val2);
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return msa311_write_samp_freq(indio_dev, val, val2);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int msa311_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval,
+ unsigned int *readval)
+{
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ struct device *dev = msa311->dev;
+ int err;
+
+ if (reg > regmap_get_max_register(msa311->regs))
+ return -EINVAL;
+
+ err = pm_runtime_resume_and_get(dev);
+ if (err)
+ return err;
+
+ mutex_lock(&msa311->lock);
+
+ if (readval)
+ err = regmap_read(msa311->regs, reg, readval);
+ else
+ err = regmap_write(msa311->regs, reg, writeval);
+
+ mutex_unlock(&msa311->lock);
+
+ pm_runtime_put_autosuspend(dev);
+
+ if (err)
+ dev_err(dev, "can't %s register %u from debugfs (%pe)\n",
+ str_read_write(readval), reg, ERR_PTR(err));
+
+ return err;
+}
+
+static int msa311_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ struct device *dev = msa311->dev;
+
+ return pm_runtime_resume_and_get(dev);
+}
+
+static int msa311_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ struct device *dev = msa311->dev;
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
+static int msa311_set_new_data_trig_state(struct iio_trigger *trig, bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ struct device *dev = msa311->dev;
+ int err;
+
+ mutex_lock(&msa311->lock);
+ err = regmap_field_write(msa311->fields[F_NEW_DATA_INT_EN], state);
+ mutex_unlock(&msa311->lock);
+ if (err)
+ dev_err(dev,
+ "can't %s buffer due to new_data_int failure (%pe)\n",
+ str_enable_disable(state), ERR_PTR(err));
+
+ return err;
+}
+
+static int msa311_validate_device(struct iio_trigger *trig,
+ struct iio_dev *indio_dev)
+{
+ return iio_trigger_get_drvdata(trig) == indio_dev ? 0 : -EINVAL;
+}
+
+static irqreturn_t msa311_buffer_thread(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct msa311_priv *msa311 = iio_priv(pf->indio_dev);
+ struct iio_dev *indio_dev = pf->indio_dev;
+ const struct iio_chan_spec *chan;
+ struct device *dev = msa311->dev;
+ int bit, err, i = 0;
+ __le16 axis;
+ struct {
+ __le16 channels[MSA311_SI_Z + 1];
+ aligned_s64 ts;
+ } buf = { };
+
+ mutex_lock(&msa311->lock);
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ chan = &msa311_channels[bit];
+
+ err = msa311_get_axis(msa311, chan, &axis);
+ if (err) {
+ mutex_unlock(&msa311->lock);
+ dev_err(dev, "can't get axis %s (%pe)\n",
+ chan->datasheet_name, ERR_PTR(err));
+ goto notify_done;
+ }
+
+ buf.channels[i++] = axis;
+ }
+
+ mutex_unlock(&msa311->lock);
+
+ iio_push_to_buffers_with_ts(indio_dev, &buf, sizeof(buf),
+ iio_get_time_ns(indio_dev));
+
+notify_done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t msa311_irq_thread(int irq, void *p)
+{
+ struct msa311_priv *msa311 = iio_priv(p);
+ unsigned int new_data_int_enabled;
+ struct device *dev = msa311->dev;
+ int err;
+
+ mutex_lock(&msa311->lock);
+
+ /*
+ * We do not check NEW_DATA int status, because based on the
+ * specification it's cleared automatically after a fixed time.
+ * So just check that is enabled by driver logic.
+ */
+ err = regmap_field_read(msa311->fields[F_NEW_DATA_INT_EN],
+ &new_data_int_enabled);
+
+ mutex_unlock(&msa311->lock);
+ if (err) {
+ dev_err(dev, "can't read new_data interrupt state (%pe)\n",
+ ERR_PTR(err));
+ return IRQ_NONE;
+ }
+
+ if (new_data_int_enabled)
+ iio_trigger_poll_nested(msa311->new_data_trig);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_info msa311_info = {
+ .read_raw = msa311_read_raw,
+ .read_avail = msa311_read_avail,
+ .write_raw = msa311_write_raw,
+ .debugfs_reg_access = msa311_debugfs_reg_access,
+};
+
+static const struct iio_buffer_setup_ops msa311_buffer_setup_ops = {
+ .preenable = msa311_buffer_preenable,
+ .postdisable = msa311_buffer_postdisable,
+};
+
+static const struct iio_trigger_ops msa311_new_data_trig_ops = {
+ .set_trigger_state = msa311_set_new_data_trig_state,
+ .validate_device = msa311_validate_device,
+};
+
+static int msa311_check_partid(struct msa311_priv *msa311)
+{
+ struct device *dev = msa311->dev;
+ unsigned int partid;
+ int err;
+
+ err = regmap_read(msa311->regs, MSA311_PARTID_REG, &partid);
+ if (err)
+ return dev_err_probe(dev, err, "failed to read partid\n");
+
+ if (partid != MSA311_WHO_AM_I)
+ dev_warn(dev, "invalid partid (%#x), expected (%#x)\n",
+ partid, MSA311_WHO_AM_I);
+
+ msa311->chip_name = devm_kasprintf(dev, GFP_KERNEL,
+ "msa311-%02x", partid);
+ if (!msa311->chip_name)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int msa311_soft_reset(struct msa311_priv *msa311)
+{
+ struct device *dev = msa311->dev;
+ int err;
+
+ err = regmap_write(msa311->regs, MSA311_SOFT_RESET_REG,
+ MSA311_GENMASK(F_SOFT_RESET_I2C) |
+ MSA311_GENMASK(F_SOFT_RESET_SPI));
+ if (err)
+ return dev_err_probe(dev, err, "can't soft reset all logic\n");
+
+ return 0;
+}
+
+static int msa311_chip_init(struct msa311_priv *msa311)
+{
+ struct device *dev = msa311->dev;
+ const char zero_bulk[2] = { };
+ int err;
+
+ err = regmap_write(msa311->regs, MSA311_RANGE_REG, MSA311_FS_16G);
+ if (err)
+ return dev_err_probe(dev, err, "failed to setup accel range\n");
+
+ /* Disable all interrupts by default */
+ err = regmap_bulk_write(msa311->regs, MSA311_INT_SET_0_REG,
+ zero_bulk, sizeof(zero_bulk));
+ if (err)
+ return dev_err_probe(dev, err,
+ "can't disable set0/set1 interrupts\n");
+
+ /* Unmap all INT1 interrupts by default */
+ err = regmap_bulk_write(msa311->regs, MSA311_INT_MAP_0_REG,
+ zero_bulk, sizeof(zero_bulk));
+ if (err)
+ return dev_err_probe(dev, err,
+ "failed to unmap map0/map1 interrupts\n");
+
+ /* Disable all axes by default */
+ err = regmap_clear_bits(msa311->regs, MSA311_ODR_REG,
+ MSA311_GENMASK(F_X_AXIS_DIS) |
+ MSA311_GENMASK(F_Y_AXIS_DIS) |
+ MSA311_GENMASK(F_Z_AXIS_DIS));
+ if (err)
+ return dev_err_probe(dev, err, "can't enable all axes\n");
+
+ err = msa311_set_odr(msa311, MSA311_ODR_125_HZ);
+ if (err)
+ return dev_err_probe(dev, err,
+ "failed to set accel frequency\n");
+
+ return 0;
+}
+
+static int msa311_setup_interrupts(struct msa311_priv *msa311)
+{
+ struct device *dev = msa311->dev;
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct iio_dev *indio_dev = i2c_get_clientdata(i2c);
+ struct iio_trigger *trig;
+ int err;
+
+ /* Keep going without interrupts if no initialized I2C IRQ */
+ if (i2c->irq <= 0)
+ return 0;
+
+ err = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL,
+ msa311_irq_thread, IRQF_ONESHOT,
+ msa311->chip_name, indio_dev);
+ if (err)
+ return err;
+
+ trig = devm_iio_trigger_alloc(dev, "%s-new-data", msa311->chip_name);
+ if (!trig)
+ return -ENOMEM;
+
+ msa311->new_data_trig = trig;
+ msa311->new_data_trig->ops = &msa311_new_data_trig_ops;
+ iio_trigger_set_drvdata(msa311->new_data_trig, indio_dev);
+
+ err = devm_iio_trigger_register(dev, msa311->new_data_trig);
+ if (err)
+ return dev_err_probe(dev, err,
+ "can't register newdata trigger\n");
+
+ err = regmap_field_write(msa311->fields[F_INT1_OD],
+ MSA311_INT1_OD_PUSH_PULL);
+ if (err)
+ return dev_err_probe(dev, err,
+ "can't enable push-pull interrupt\n");
+
+ err = regmap_field_write(msa311->fields[F_INT1_LVL],
+ MSA311_INT1_LVL_HIGH);
+ if (err)
+ return dev_err_probe(dev, err,
+ "can't set active interrupt level\n");
+
+ err = regmap_field_write(msa311->fields[F_LATCH_INT],
+ MSA311_LATCH_INT_LATCHED);
+ if (err)
+ return dev_err_probe(dev, err,
+ "can't latch interrupt\n");
+
+ err = regmap_field_write(msa311->fields[F_RESET_INT], 1);
+ if (err)
+ return dev_err_probe(dev, err,
+ "can't reset interrupt\n");
+
+ err = regmap_field_write(msa311->fields[F_INT1_NEW_DATA], 1);
+ if (err)
+ return dev_err_probe(dev, err,
+ "can't map new data interrupt\n");
+
+ return 0;
+}
+
+static int msa311_regmap_init(struct msa311_priv *msa311)
+{
+ struct regmap_field **fields = msa311->fields;
+ struct device *dev = msa311->dev;
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct regmap *regmap;
+ int i;
+
+ regmap = devm_regmap_init_i2c(i2c, &msa311_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "failed to register i2c regmap\n");
+
+ msa311->regs = regmap;
+
+ for (i = 0; i < F_MAX_FIELDS; i++) {
+ fields[i] = devm_regmap_field_alloc(dev,
+ msa311->regs,
+ msa311_reg_fields[i]);
+ if (IS_ERR(msa311->fields[i]))
+ return dev_err_probe(dev, PTR_ERR(msa311->fields[i]),
+ "can't alloc field[%d]\n", i);
+ }
+
+ return 0;
+}
+
+static void msa311_powerdown(void *msa311)
+{
+ msa311_set_pwr_mode(msa311, MSA311_PWR_MODE_SUSPEND);
+}
+
+static int msa311_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct msa311_priv *msa311;
+ struct iio_dev *indio_dev;
+ int err;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*msa311));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ msa311 = iio_priv(indio_dev);
+ msa311->dev = dev;
+ i2c_set_clientdata(i2c, indio_dev);
+
+ err = msa311_regmap_init(msa311);
+ if (err)
+ return err;
+
+ mutex_init(&msa311->lock);
+
+ err = devm_regulator_get_enable(dev, "vdd");
+ if (err)
+ return dev_err_probe(dev, err, "can't get vdd supply\n");
+
+ err = msa311_check_partid(msa311);
+ if (err)
+ return err;
+
+ err = msa311_soft_reset(msa311);
+ if (err)
+ return err;
+
+ err = msa311_set_pwr_mode(msa311, MSA311_PWR_MODE_NORMAL);
+ if (err)
+ return dev_err_probe(dev, err, "failed to power on device\n");
+
+ /*
+ * Register powerdown deferred callback which suspends the chip
+ * after module unloaded.
+ *
+ * MSA311 should be in SUSPEND mode in the two cases:
+ * 1) When driver is loaded, but we do not have any data or
+ * configuration requests to it (we are solving it using
+ * autosuspend feature).
+ * 2) When driver is unloaded and device is not used (devm action is
+ * used in this case).
+ */
+ err = devm_add_action_or_reset(dev, msa311_powerdown, msa311);
+ if (err)
+ return err;
+
+ err = pm_runtime_set_active(dev);
+ if (err)
+ return err;
+
+ err = devm_pm_runtime_enable(dev);
+ if (err)
+ return err;
+
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_autosuspend_delay(dev, MSA311_PWR_SLEEP_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+
+ err = msa311_chip_init(msa311);
+ if (err)
+ return err;
+
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = msa311_channels;
+ indio_dev->num_channels = ARRAY_SIZE(msa311_channels);
+ indio_dev->name = msa311->chip_name;
+ indio_dev->info = &msa311_info;
+
+ err = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ msa311_buffer_thread,
+ &msa311_buffer_setup_ops);
+ if (err)
+ return dev_err_probe(dev, err,
+ "can't setup IIO trigger buffer\n");
+
+ err = msa311_setup_interrupts(msa311);
+ if (err)
+ return err;
+
+ pm_runtime_put_autosuspend(dev);
+
+ err = devm_iio_device_register(dev, indio_dev);
+ if (err)
+ return dev_err_probe(dev, err, "IIO device register failed\n");
+
+ return 0;
+}
+
+static int msa311_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ int err;
+
+ mutex_lock(&msa311->lock);
+ err = msa311_set_pwr_mode(msa311, MSA311_PWR_MODE_SUSPEND);
+ mutex_unlock(&msa311->lock);
+ if (err)
+ dev_err(dev, "failed to power off device (%pe)\n",
+ ERR_PTR(err));
+
+ return err;
+}
+
+static int msa311_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct msa311_priv *msa311 = iio_priv(indio_dev);
+ int err;
+
+ mutex_lock(&msa311->lock);
+ err = msa311_set_pwr_mode(msa311, MSA311_PWR_MODE_NORMAL);
+ mutex_unlock(&msa311->lock);
+ if (err)
+ dev_err(dev, "failed to power on device (%pe)\n",
+ ERR_PTR(err));
+
+ return err;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(msa311_pm_ops, msa311_runtime_suspend,
+ msa311_runtime_resume, NULL);
+
+static const struct i2c_device_id msa311_i2c_id[] = {
+ { .name = "msa311" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, msa311_i2c_id);
+
+static const struct of_device_id msa311_of_match[] = {
+ { .compatible = "memsensing,msa311" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, msa311_of_match);
+
+static struct i2c_driver msa311_driver = {
+ .driver = {
+ .name = "msa311",
+ .of_match_table = msa311_of_match,
+ .pm = pm_ptr(&msa311_pm_ops),
+ },
+ .probe = msa311_probe,
+ .id_table = msa311_i2c_id,
+};
+module_i2c_driver(msa311_driver);
+
+MODULE_AUTHOR("Dmitry Rokosov <ddrokosov@sberdevices.ru>");
+MODULE_DESCRIPTION("MEMSensing MSA311 3-axis accelerometer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/mxc4005.c b/drivers/iio/accel/mxc4005.c
new file mode 100644
index 000000000..a66fee828
--- /dev/null
+++ b/drivers/iio/accel/mxc4005.c
@@ -0,0 +1,600 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 3-axis accelerometer driver for MXC4005XC Memsic sensor
+ *
+ * Copyright (c) 2014, Intel Corporation.
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#define MXC4005_DRV_NAME "mxc4005"
+
+#define MXC4005_REG_XOUT_UPPER 0x03
+#define MXC4005_REG_XOUT_LOWER 0x04
+#define MXC4005_REG_YOUT_UPPER 0x05
+#define MXC4005_REG_YOUT_LOWER 0x06
+#define MXC4005_REG_ZOUT_UPPER 0x07
+#define MXC4005_REG_ZOUT_LOWER 0x08
+
+#define MXC4005_REG_INT_MASK0 0x0A
+
+#define MXC4005_REG_INT_MASK1 0x0B
+#define MXC4005_REG_INT_MASK1_BIT_DRDYE 0x01
+
+#define MXC4005_REG_INT_CLR0 0x00
+
+#define MXC4005_REG_INT_CLR1 0x01
+#define MXC4005_REG_INT_CLR1_BIT_DRDYC 0x01
+#define MXC4005_REG_INT_CLR1_SW_RST 0x10
+
+#define MXC4005_REG_CONTROL 0x0D
+#define MXC4005_REG_CONTROL_MASK_FSR GENMASK(6, 5)
+#define MXC4005_CONTROL_FSR_SHIFT 5
+
+#define MXC4005_REG_DEVICE_ID 0x0E
+
+/* Datasheet does not specify a reset time, this is a conservative guess */
+#define MXC4005_RESET_TIME_US 2000
+
+enum mxc4005_axis {
+ AXIS_X,
+ AXIS_Y,
+ AXIS_Z,
+};
+
+enum mxc4005_range {
+ MXC4005_RANGE_2G,
+ MXC4005_RANGE_4G,
+ MXC4005_RANGE_8G,
+};
+
+struct mxc4005_data {
+ struct device *dev;
+ struct mutex mutex;
+ struct regmap *regmap;
+ struct iio_trigger *dready_trig;
+ struct iio_mount_matrix orientation;
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ __be16 chans[3];
+ aligned_s64 timestamp;
+ } scan;
+ bool trigger_enabled;
+ unsigned int control;
+ unsigned int int_mask1;
+};
+
+/*
+ * MXC4005 can operate in the following ranges:
+ * +/- 2G, 4G, 8G (the default +/-2G)
+ *
+ * (2 + 2) * 9.81 / (2^12 - 1) = 0.009582
+ * (4 + 4) * 9.81 / (2^12 - 1) = 0.019164
+ * (8 + 8) * 9.81 / (2^12 - 1) = 0.038329
+ */
+static const struct {
+ u8 range;
+ int scale;
+} mxc4005_scale_table[] = {
+ {MXC4005_RANGE_2G, 9582},
+ {MXC4005_RANGE_4G, 19164},
+ {MXC4005_RANGE_8G, 38329},
+};
+
+
+static IIO_CONST_ATTR(in_accel_scale_available, "0.009582 0.019164 0.038329");
+
+static struct attribute *mxc4005_attributes[] = {
+ &iio_const_attr_in_accel_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group mxc4005_attrs_group = {
+ .attrs = mxc4005_attributes,
+};
+
+static bool mxc4005_is_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MXC4005_REG_XOUT_UPPER:
+ case MXC4005_REG_XOUT_LOWER:
+ case MXC4005_REG_YOUT_UPPER:
+ case MXC4005_REG_YOUT_LOWER:
+ case MXC4005_REG_ZOUT_UPPER:
+ case MXC4005_REG_ZOUT_LOWER:
+ case MXC4005_REG_DEVICE_ID:
+ case MXC4005_REG_CONTROL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mxc4005_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MXC4005_REG_INT_CLR0:
+ case MXC4005_REG_INT_CLR1:
+ case MXC4005_REG_INT_MASK0:
+ case MXC4005_REG_INT_MASK1:
+ case MXC4005_REG_CONTROL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config mxc4005_regmap_config = {
+ .name = "mxc4005_regmap",
+
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = MXC4005_REG_DEVICE_ID,
+
+ .readable_reg = mxc4005_is_readable_reg,
+ .writeable_reg = mxc4005_is_writeable_reg,
+};
+
+static int mxc4005_read_xyz(struct mxc4005_data *data)
+{
+ int ret;
+
+ ret = regmap_bulk_read(data->regmap, MXC4005_REG_XOUT_UPPER,
+ data->scan.chans, sizeof(data->scan.chans));
+ if (ret < 0) {
+ dev_err(data->dev, "failed to read axes\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mxc4005_read_axis(struct mxc4005_data *data,
+ unsigned int addr)
+{
+ __be16 reg;
+ int ret;
+
+ ret = regmap_bulk_read(data->regmap, addr, &reg, sizeof(reg));
+ if (ret < 0) {
+ dev_err(data->dev, "failed to read reg %02x\n", addr);
+ return ret;
+ }
+
+ return be16_to_cpu(reg);
+}
+
+static int mxc4005_read_scale(struct mxc4005_data *data)
+{
+ unsigned int reg;
+ int ret;
+ int i;
+
+ ret = regmap_read(data->regmap, MXC4005_REG_CONTROL, &reg);
+ if (ret < 0) {
+ dev_err(data->dev, "failed to read reg_control\n");
+ return ret;
+ }
+
+ i = reg >> MXC4005_CONTROL_FSR_SHIFT;
+
+ if (i < 0 || i >= ARRAY_SIZE(mxc4005_scale_table))
+ return -EINVAL;
+
+ return mxc4005_scale_table[i].scale;
+}
+
+static int mxc4005_set_scale(struct mxc4005_data *data, int val)
+{
+ unsigned int reg;
+ int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(mxc4005_scale_table); i++) {
+ if (mxc4005_scale_table[i].scale == val) {
+ reg = i << MXC4005_CONTROL_FSR_SHIFT;
+ ret = regmap_update_bits(data->regmap,
+ MXC4005_REG_CONTROL,
+ MXC4005_REG_CONTROL_MASK_FSR,
+ reg);
+ if (ret < 0)
+ dev_err(data->dev,
+ "failed to write reg_control\n");
+ return ret;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int mxc4005_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mxc4005_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ if (iio_buffer_enabled(indio_dev))
+ return -EBUSY;
+
+ ret = mxc4005_read_axis(data, chan->address);
+ 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:
+ ret = mxc4005_read_scale(data);
+ if (ret < 0)
+ return ret;
+
+ *val = 0;
+ *val2 = ret;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mxc4005_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mxc4005_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ if (val != 0)
+ return -EINVAL;
+
+ return mxc4005_set_scale(data, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_mount_matrix *
+mxc4005_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct mxc4005_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info mxc4005_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, mxc4005_get_mount_matrix),
+ { }
+};
+
+static const struct iio_info mxc4005_info = {
+ .read_raw = mxc4005_read_raw,
+ .write_raw = mxc4005_write_raw,
+ .attrs = &mxc4005_attrs_group,
+};
+
+static const unsigned long mxc4005_scan_masks[] = {
+ BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z),
+ 0
+};
+
+#define MXC4005_CHANNEL(_axis, _addr) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .address = _addr, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = AXIS_##_axis, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .shift = 4, \
+ .endianness = IIO_BE, \
+ }, \
+ .ext_info = mxc4005_ext_info, \
+}
+
+static const struct iio_chan_spec mxc4005_channels[] = {
+ MXC4005_CHANNEL(X, MXC4005_REG_XOUT_UPPER),
+ MXC4005_CHANNEL(Y, MXC4005_REG_YOUT_UPPER),
+ MXC4005_CHANNEL(Z, MXC4005_REG_ZOUT_UPPER),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static irqreturn_t mxc4005_trigger_handler(int irq, void *private)
+{
+ struct iio_poll_func *pf = private;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct mxc4005_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = mxc4005_read_xyz(data);
+ if (ret < 0)
+ goto err;
+
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ pf->timestamp);
+
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static void mxc4005_clr_intr(struct mxc4005_data *data)
+{
+ int ret;
+
+ /* clear interrupt */
+ ret = regmap_write(data->regmap, MXC4005_REG_INT_CLR1,
+ MXC4005_REG_INT_CLR1_BIT_DRDYC);
+ if (ret < 0)
+ dev_err(data->dev, "failed to write to reg_int_clr1\n");
+}
+
+static int mxc4005_set_trigger_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct mxc4005_data *data = iio_priv(indio_dev);
+ unsigned int val;
+ int ret;
+
+ mutex_lock(&data->mutex);
+
+ val = state ? MXC4005_REG_INT_MASK1_BIT_DRDYE : 0;
+ ret = regmap_write(data->regmap, MXC4005_REG_INT_MASK1, val);
+ if (ret < 0) {
+ mutex_unlock(&data->mutex);
+ dev_err(data->dev, "failed to update reg_int_mask1");
+ return ret;
+ }
+
+ data->int_mask1 = val;
+ data->trigger_enabled = state;
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static void mxc4005_trigger_reen(struct iio_trigger *trig)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct mxc4005_data *data = iio_priv(indio_dev);
+
+ if (!data->dready_trig)
+ return;
+
+ mxc4005_clr_intr(data);
+}
+
+static const struct iio_trigger_ops mxc4005_trigger_ops = {
+ .set_trigger_state = mxc4005_set_trigger_state,
+ .reenable = mxc4005_trigger_reen,
+};
+
+static int mxc4005_chip_init(struct mxc4005_data *data)
+{
+ int ret;
+ unsigned int reg;
+
+ ret = regmap_read(data->regmap, MXC4005_REG_DEVICE_ID, &reg);
+ if (ret < 0) {
+ dev_err(data->dev, "failed to read chip id\n");
+ return ret;
+ }
+
+ dev_dbg(data->dev, "MXC4005 chip id %02x\n", reg);
+
+ ret = regmap_write(data->regmap, MXC4005_REG_INT_CLR1,
+ MXC4005_REG_INT_CLR1_SW_RST);
+ if (ret < 0)
+ return dev_err_probe(data->dev, ret, "resetting chip\n");
+
+ fsleep(MXC4005_RESET_TIME_US);
+
+ ret = regmap_write(data->regmap, MXC4005_REG_INT_MASK0, 0);
+ if (ret < 0)
+ return dev_err_probe(data->dev, ret, "writing INT_MASK0\n");
+
+ ret = regmap_write(data->regmap, MXC4005_REG_INT_MASK1, 0);
+ if (ret < 0)
+ return dev_err_probe(data->dev, ret, "writing INT_MASK1\n");
+
+ return 0;
+}
+
+static int mxc4005_probe(struct i2c_client *client)
+{
+ struct mxc4005_data *data;
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_i2c(client, &mxc4005_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "failed to initialize regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->dev = &client->dev;
+ data->regmap = regmap;
+
+ ret = mxc4005_chip_init(data);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to initialize chip\n");
+ return ret;
+ }
+
+ mutex_init(&data->mutex);
+
+ if (!iio_read_acpi_mount_matrix(&client->dev, &data->orientation, "ROTM")) {
+ ret = iio_read_mount_matrix(&client->dev, &data->orientation);
+ if (ret)
+ return ret;
+ }
+
+ indio_dev->channels = mxc4005_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mxc4005_channels);
+ indio_dev->available_scan_masks = mxc4005_scan_masks;
+ indio_dev->name = MXC4005_DRV_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &mxc4005_info;
+
+ ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
+ iio_pollfunc_store_time,
+ mxc4005_trigger_handler,
+ NULL);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "failed to setup iio triggered buffer\n");
+ return ret;
+ }
+
+ if (client->irq > 0) {
+ data->dready_trig = devm_iio_trigger_alloc(&client->dev,
+ "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->dready_trig)
+ return -ENOMEM;
+
+ ret = devm_request_irq(&client->dev, client->irq,
+ iio_trigger_generic_data_rdy_poll,
+ IRQF_TRIGGER_FALLING | IRQF_NO_THREAD,
+ "mxc4005_event", data->dready_trig);
+ if (ret)
+ return ret;
+
+ data->dready_trig->ops = &mxc4005_trigger_ops;
+ iio_trigger_set_drvdata(data->dready_trig, indio_dev);
+ ret = devm_iio_trigger_register(&client->dev,
+ data->dready_trig);
+ if (ret) {
+ dev_err(&client->dev,
+ "failed to register trigger\n");
+ return ret;
+ }
+
+ indio_dev->trig = iio_trigger_get(data->dready_trig);
+ }
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int mxc4005_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct mxc4005_data *data = iio_priv(indio_dev);
+ int ret;
+
+ /* Save control to restore it on resume */
+ ret = regmap_read(data->regmap, MXC4005_REG_CONTROL, &data->control);
+ if (ret < 0)
+ dev_err(data->dev, "failed to read reg_control\n");
+
+ return ret;
+}
+
+static int mxc4005_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct mxc4005_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = regmap_write(data->regmap, MXC4005_REG_INT_CLR1,
+ MXC4005_REG_INT_CLR1_SW_RST);
+ if (ret) {
+ dev_err(data->dev, "failed to reset chip: %d\n", ret);
+ return ret;
+ }
+
+ fsleep(MXC4005_RESET_TIME_US);
+
+ ret = regmap_write(data->regmap, MXC4005_REG_CONTROL, data->control);
+ if (ret) {
+ dev_err(data->dev, "failed to restore control register\n");
+ return ret;
+ }
+
+ ret = regmap_write(data->regmap, MXC4005_REG_INT_MASK0, 0);
+ if (ret) {
+ dev_err(data->dev, "failed to restore interrupt 0 mask\n");
+ return ret;
+ }
+
+ ret = regmap_write(data->regmap, MXC4005_REG_INT_MASK1, data->int_mask1);
+ if (ret) {
+ dev_err(data->dev, "failed to restore interrupt 1 mask\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(mxc4005_pm_ops, mxc4005_suspend, mxc4005_resume);
+
+static const struct acpi_device_id mxc4005_acpi_match[] = {
+ {"MXC4005", 0},
+ {"MXC6655", 0},
+ {"MDA6655", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, mxc4005_acpi_match);
+
+static const struct of_device_id mxc4005_of_match[] = {
+ { .compatible = "memsic,mxc4005", },
+ { .compatible = "memsic,mxc6655", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mxc4005_of_match);
+
+static const struct i2c_device_id mxc4005_id[] = {
+ { .name = "mxc4005" },
+ { .name = "mxc6655" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mxc4005_id);
+
+static struct i2c_driver mxc4005_driver = {
+ .driver = {
+ .name = MXC4005_DRV_NAME,
+ .acpi_match_table = mxc4005_acpi_match,
+ .of_match_table = mxc4005_of_match,
+ .pm = pm_sleep_ptr(&mxc4005_pm_ops),
+ },
+ .probe = mxc4005_probe,
+ .id_table = mxc4005_id,
+};
+
+module_i2c_driver(mxc4005_driver);
+
+MODULE_AUTHOR("Teodora Baluta <teodora.baluta@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MXC4005 3-axis accelerometer driver");
diff --git a/drivers/iio/accel/mxc6255.c b/drivers/iio/accel/mxc6255.c
new file mode 100644
index 000000000..a9f41cf27
--- /dev/null
+++ b/drivers/iio/accel/mxc6255.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MXC6255 - MEMSIC orientation sensing accelerometer
+ *
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * IIO driver for MXC6255 (7-bit I2C slave address 0x15).
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/iio/iio.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/iio/sysfs.h>
+
+#define MXC6255_DRV_NAME "mxc6255"
+
+#define MXC6255_REG_XOUT 0x00
+#define MXC6255_REG_YOUT 0x01
+#define MXC6255_REG_CHIP_ID 0x08
+
+#define MXC6255_CHIP_ID 0x05
+
+/*
+ * MXC6255 has only one measurement range: +/- 2G.
+ * The acceleration output is an 8-bit value.
+ *
+ * Scale is calculated as follows:
+ * (2 + 2) * 9.80665 / (2^8 - 1) = 0.153829
+ *
+ * Scale value for +/- 2G measurement range
+ */
+#define MXC6255_SCALE 153829
+
+enum mxc6255_axis {
+ AXIS_X,
+ AXIS_Y,
+};
+
+struct mxc6255_data {
+ struct i2c_client *client;
+ struct regmap *regmap;
+};
+
+static int mxc6255_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mxc6255_data *data = iio_priv(indio_dev);
+ unsigned int reg;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_read(data->regmap, chan->address, &reg);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Error reading reg %lu\n", chan->address);
+ return ret;
+ }
+
+ *val = sign_extend32(reg, 7);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = MXC6255_SCALE;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info mxc6255_info = {
+ .read_raw = mxc6255_read_raw,
+};
+
+#define MXC6255_CHANNEL(_axis, reg) { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .address = reg, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec mxc6255_channels[] = {
+ MXC6255_CHANNEL(X, MXC6255_REG_XOUT),
+ MXC6255_CHANNEL(Y, MXC6255_REG_YOUT),
+};
+
+static bool mxc6255_is_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MXC6255_REG_XOUT:
+ case MXC6255_REG_YOUT:
+ case MXC6255_REG_CHIP_ID:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config mxc6255_regmap_config = {
+ .name = "mxc6255_regmap",
+
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .readable_reg = mxc6255_is_readable_reg,
+};
+
+static int mxc6255_probe(struct i2c_client *client)
+{
+ struct mxc6255_data *data;
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ unsigned int chip_id;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_i2c(client, &mxc6255_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "Error initializing regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ data->regmap = regmap;
+
+ indio_dev->name = MXC6255_DRV_NAME;
+ indio_dev->channels = mxc6255_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mxc6255_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &mxc6255_info;
+
+ ret = regmap_read(data->regmap, MXC6255_REG_CHIP_ID, &chip_id);
+ if (ret < 0) {
+ dev_err(&client->dev, "Error reading chip id %d\n", ret);
+ return ret;
+ }
+
+ if ((chip_id & 0x1f) != MXC6255_CHIP_ID) {
+ dev_err(&client->dev, "Invalid chip id %x\n", chip_id);
+ return -ENODEV;
+ }
+
+ dev_dbg(&client->dev, "Chip id %x\n", chip_id);
+
+ ret = devm_iio_device_register(&client->dev, indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "Could not register IIO device\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct acpi_device_id mxc6255_acpi_match[] = {
+ {"MXC6225", 0},
+ {"MXC6255", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, mxc6255_acpi_match);
+
+static const struct i2c_device_id mxc6255_id[] = {
+ { .name = "mxc6225" },
+ { .name = "mxc6255" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mxc6255_id);
+
+static struct i2c_driver mxc6255_driver = {
+ .driver = {
+ .name = MXC6255_DRV_NAME,
+ .acpi_match_table = mxc6255_acpi_match,
+ },
+ .probe = mxc6255_probe,
+ .id_table = mxc6255_id,
+};
+
+module_i2c_driver(mxc6255_driver);
+
+MODULE_AUTHOR("Teodora Baluta <teodora.baluta@intel.com>");
+MODULE_DESCRIPTION("MEMSIC MXC6255 orientation sensing accelerometer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c
new file mode 100644
index 000000000..8f1ac1d33
--- /dev/null
+++ b/drivers/iio/accel/sca3000.c
@@ -0,0 +1,1526 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sca3000_core.c -- support VTI sca3000 series accelerometers via SPI
+ *
+ * Copyright (c) 2009 Jonathan Cameron <jic23@kernel.org>
+ *
+ * See industrialio/accels/sca3000.h for comments.
+ */
+
+#include <linux/cleanup.h>
+#include <linux/interrupt.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/kfifo_buf.h>
+
+#define SCA3000_WRITE_REG(a) (((a) << 2) | 0x02)
+#define SCA3000_READ_REG(a) ((a) << 2)
+
+#define SCA3000_REG_REVID_ADDR 0x00
+#define SCA3000_REG_REVID_MAJOR_MASK GENMASK(8, 4)
+#define SCA3000_REG_REVID_MINOR_MASK GENMASK(3, 0)
+
+#define SCA3000_REG_STATUS_ADDR 0x02
+#define SCA3000_LOCKED BIT(5)
+#define SCA3000_EEPROM_CS_ERROR BIT(1)
+#define SCA3000_SPI_FRAME_ERROR BIT(0)
+
+/* All reads done using register decrement so no need to directly access LSBs */
+#define SCA3000_REG_X_MSB_ADDR 0x05
+#define SCA3000_REG_Y_MSB_ADDR 0x07
+#define SCA3000_REG_Z_MSB_ADDR 0x09
+
+#define SCA3000_REG_RING_OUT_ADDR 0x0f
+
+/* Temp read untested - the e05 doesn't have the sensor */
+#define SCA3000_REG_TEMP_MSB_ADDR 0x13
+
+#define SCA3000_REG_MODE_ADDR 0x14
+#define SCA3000_MODE_PROT_MASK 0x28
+#define SCA3000_REG_MODE_RING_BUF_ENABLE BIT(7)
+#define SCA3000_REG_MODE_RING_BUF_8BIT BIT(6)
+
+/*
+ * Free fall detection triggers an interrupt if the acceleration
+ * is below a threshold for equivalent of 25cm drop
+ */
+#define SCA3000_REG_MODE_FREE_FALL_DETECT BIT(4)
+#define SCA3000_REG_MODE_MEAS_MODE_NORMAL 0x00
+#define SCA3000_REG_MODE_MEAS_MODE_OP_1 0x01
+#define SCA3000_REG_MODE_MEAS_MODE_OP_2 0x02
+
+/*
+ * In motion detection mode the accelerations are band pass filtered
+ * (approx 1 - 25Hz) and then a programmable threshold used to trigger
+ * and interrupt.
+ */
+#define SCA3000_REG_MODE_MEAS_MODE_MOT_DET 0x03
+#define SCA3000_REG_MODE_MODE_MASK 0x03
+
+#define SCA3000_REG_BUF_COUNT_ADDR 0x15
+
+#define SCA3000_REG_INT_STATUS_ADDR 0x16
+#define SCA3000_REG_INT_STATUS_THREE_QUARTERS BIT(7)
+#define SCA3000_REG_INT_STATUS_HALF BIT(6)
+
+#define SCA3000_INT_STATUS_FREE_FALL BIT(3)
+#define SCA3000_INT_STATUS_Y_TRIGGER BIT(2)
+#define SCA3000_INT_STATUS_X_TRIGGER BIT(1)
+#define SCA3000_INT_STATUS_Z_TRIGGER BIT(0)
+
+/* Used to allow access to multiplexed registers */
+#define SCA3000_REG_CTRL_SEL_ADDR 0x18
+/* Only available for SCA3000-D03 and SCA3000-D01 */
+#define SCA3000_REG_CTRL_SEL_I2C_DISABLE 0x01
+#define SCA3000_REG_CTRL_SEL_MD_CTRL 0x02
+#define SCA3000_REG_CTRL_SEL_MD_Y_TH 0x03
+#define SCA3000_REG_CTRL_SEL_MD_X_TH 0x04
+#define SCA3000_REG_CTRL_SEL_MD_Z_TH 0x05
+/*
+ * BE VERY CAREFUL WITH THIS, IF 3 BITS ARE NOT SET the device
+ * will not function
+ */
+#define SCA3000_REG_CTRL_SEL_OUT_CTRL 0x0B
+
+#define SCA3000_REG_OUT_CTRL_PROT_MASK 0xE0
+#define SCA3000_REG_OUT_CTRL_BUF_X_EN 0x10
+#define SCA3000_REG_OUT_CTRL_BUF_Y_EN 0x08
+#define SCA3000_REG_OUT_CTRL_BUF_Z_EN 0x04
+#define SCA3000_REG_OUT_CTRL_BUF_DIV_MASK 0x03
+#define SCA3000_REG_OUT_CTRL_BUF_DIV_4 0x02
+#define SCA3000_REG_OUT_CTRL_BUF_DIV_2 0x01
+
+
+/*
+ * Control which motion detector interrupts are on.
+ * For now only OR combinations are supported.
+ */
+#define SCA3000_MD_CTRL_PROT_MASK 0xC0
+#define SCA3000_MD_CTRL_OR_Y BIT(0)
+#define SCA3000_MD_CTRL_OR_X BIT(1)
+#define SCA3000_MD_CTRL_OR_Z BIT(2)
+/* Currently unsupported */
+#define SCA3000_MD_CTRL_AND_Y BIT(3)
+#define SCA3000_MD_CTRL_AND_X BIT(4)
+#define SCA3000_MD_CTRL_AND_Z BIT(5)
+
+/*
+ * Some control registers of complex access methods requiring this register to
+ * be used to remove a lock.
+ */
+#define SCA3000_REG_UNLOCK_ADDR 0x1e
+
+#define SCA3000_REG_INT_MASK_ADDR 0x21
+#define SCA3000_REG_INT_MASK_PROT_MASK 0x1C
+
+#define SCA3000_REG_INT_MASK_RING_THREE_QUARTER BIT(7)
+#define SCA3000_REG_INT_MASK_RING_HALF BIT(6)
+
+#define SCA3000_REG_INT_MASK_ALL_INTS 0x02
+#define SCA3000_REG_INT_MASK_ACTIVE_HIGH 0x01
+#define SCA3000_REG_INT_MASK_ACTIVE_LOW 0x00
+/* Values of multiplexed registers (write to ctrl_data after select) */
+#define SCA3000_REG_CTRL_DATA_ADDR 0x22
+
+/*
+ * Measurement modes available on some sca3000 series chips. Code assumes others
+ * may become available in the future.
+ *
+ * Bypass - Bypass the low-pass filter in the signal channel so as to increase
+ * signal bandwidth.
+ *
+ * Narrow - Narrow low-pass filtering of the signal channel and half output
+ * data rate by decimation.
+ *
+ * Wide - Widen low-pass filtering of signal channel to increase bandwidth
+ */
+#define SCA3000_OP_MODE_BYPASS 0x01
+#define SCA3000_OP_MODE_NARROW 0x02
+#define SCA3000_OP_MODE_WIDE 0x04
+#define SCA3000_MAX_TX 6
+#define SCA3000_MAX_RX 2
+
+/**
+ * struct sca3000_state - device instance state information
+ * @us: the associated spi device
+ * @info: chip variant information
+ * @mo_det_use_count: reference counter for the motion detection unit
+ * @lock: lock used to protect elements of sca3000_state
+ * and the underlying device state.
+ * @tx: dma-able transmit buffer
+ * @rx: dma-able receive buffer
+ **/
+struct sca3000_state {
+ struct spi_device *us;
+ const struct sca3000_chip_info *info;
+ int mo_det_use_count;
+ struct mutex lock;
+ /* Can these share a cacheline ? */
+ u8 rx[384] __aligned(IIO_DMA_MINALIGN);
+ u8 tx[6] __aligned(IIO_DMA_MINALIGN);
+};
+
+/**
+ * struct sca3000_chip_info - model dependent parameters
+ * @name: name of the chip
+ * @scale: scale * 10^-6
+ * @temp_output: some devices have temperature sensors.
+ * @measurement_mode_freq: normal mode sampling frequency
+ * @measurement_mode_3db_freq: 3db cutoff frequency of the low pass filter for
+ * the normal measurement mode.
+ * @option_mode_1: first optional mode. Not all models have one
+ * @option_mode_1_freq: option mode 1 sampling frequency
+ * @option_mode_1_3db_freq: 3db cutoff frequency of the low pass filter for
+ * the first option mode.
+ * @option_mode_2: second optional mode. Not all chips have one
+ * @option_mode_2_freq: option mode 2 sampling frequency
+ * @option_mode_2_3db_freq: 3db cutoff frequency of the low pass filter for
+ * the second option mode.
+ * @mot_det_mult_xz: Bit wise multipliers to calculate the threshold
+ * for motion detection in the x and z axis.
+ * @mot_det_mult_y: Bit wise multipliers to calculate the threshold
+ * for motion detection in the y axis.
+ *
+ * This structure is used to hold information about the functionality of a given
+ * sca3000 variant.
+ **/
+struct sca3000_chip_info {
+ const char *name;
+ unsigned int scale;
+ bool temp_output;
+ int measurement_mode_freq;
+ int measurement_mode_3db_freq;
+ int option_mode_1;
+ int option_mode_1_freq;
+ int option_mode_1_3db_freq;
+ int option_mode_2;
+ int option_mode_2_freq;
+ int option_mode_2_3db_freq;
+ int mot_det_mult_xz[6];
+ int mot_det_mult_y[7];
+};
+
+static const struct sca3000_chip_info sca3000_chip_info_d01 = {
+ .name = "sca3000_d01",
+ .scale = 7357,
+ .temp_output = true,
+ .measurement_mode_freq = 250,
+ .measurement_mode_3db_freq = 45,
+ .option_mode_1 = SCA3000_OP_MODE_BYPASS,
+ .option_mode_1_freq = 250,
+ .option_mode_1_3db_freq = 70,
+ .mot_det_mult_xz = { 50, 100, 200, 350, 650, 1300 },
+ .mot_det_mult_y = { 50, 100, 150, 250, 450, 850, 1750 },
+};
+
+static const struct sca3000_chip_info sca3000_chip_info_e02 = {
+ .name = "sca3000_e02",
+ .scale = 9810,
+ .measurement_mode_freq = 125,
+ .measurement_mode_3db_freq = 40,
+ .option_mode_1 = SCA3000_OP_MODE_NARROW,
+ .option_mode_1_freq = 63,
+ .option_mode_1_3db_freq = 11,
+ .mot_det_mult_xz = { 100, 150, 300, 550, 1050, 2050 },
+ .mot_det_mult_y = { 50, 100, 200, 350, 700, 1350, 2700 },
+};
+
+static const struct sca3000_chip_info sca3000_chip_info_e04 = {
+ .name = "sca3000_e04",
+ .scale = 19620,
+ .measurement_mode_freq = 100,
+ .measurement_mode_3db_freq = 38,
+ .option_mode_1 = SCA3000_OP_MODE_NARROW,
+ .option_mode_1_freq = 50,
+ .option_mode_1_3db_freq = 9,
+ .option_mode_2 = SCA3000_OP_MODE_WIDE,
+ .option_mode_2_freq = 400,
+ .option_mode_2_3db_freq = 70,
+ .mot_det_mult_xz = { 200, 300, 600, 1100, 2100, 4100 },
+ .mot_det_mult_y = { 100, 200, 400, 7000, 1400, 2700, 54000 },
+};
+
+static const struct sca3000_chip_info sca3000_chip_info_e05 = {
+ .name = "sca3000_e05",
+ .scale = 61313,
+ .measurement_mode_freq = 200,
+ .measurement_mode_3db_freq = 60,
+ .option_mode_1 = SCA3000_OP_MODE_NARROW,
+ .option_mode_1_freq = 50,
+ .option_mode_1_3db_freq = 9,
+ .option_mode_2 = SCA3000_OP_MODE_WIDE,
+ .option_mode_2_freq = 400,
+ .option_mode_2_3db_freq = 75,
+ .mot_det_mult_xz = { 600, 900, 1700, 3200, 6100, 11900 },
+ .mot_det_mult_y = { 300, 600, 1200, 2000, 4100, 7800, 15600 },
+};
+
+static int sca3000_write_reg(struct sca3000_state *st, u8 address, u8 val)
+{
+ st->tx[0] = SCA3000_WRITE_REG(address);
+ st->tx[1] = val;
+ return spi_write(st->us, st->tx, 2);
+}
+
+static int sca3000_read_data_short(struct sca3000_state *st,
+ u8 reg_address_high,
+ int len)
+{
+ struct spi_transfer xfer[2] = {
+ {
+ .len = 1,
+ .tx_buf = st->tx,
+ }, {
+ .len = len,
+ .rx_buf = st->rx,
+ }
+ };
+ st->tx[0] = SCA3000_READ_REG(reg_address_high);
+
+ return spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer));
+}
+
+/**
+ * sca3000_reg_lock_on() - test if the ctrl register lock is on
+ * @st: Driver specific device instance data.
+ *
+ * Lock must be held.
+ **/
+static int sca3000_reg_lock_on(struct sca3000_state *st)
+{
+ int ret;
+
+ ret = sca3000_read_data_short(st, SCA3000_REG_STATUS_ADDR, 1);
+ if (ret < 0)
+ return ret;
+
+ return !(st->rx[0] & SCA3000_LOCKED);
+}
+
+/**
+ * __sca3000_unlock_reg_lock() - unlock the control registers
+ * @st: Driver specific device instance data.
+ *
+ * Note the device does not appear to support doing this in a single transfer.
+ * This should only ever be used as part of ctrl reg read.
+ * Lock must be held before calling this
+ */
+static int __sca3000_unlock_reg_lock(struct sca3000_state *st)
+{
+ struct spi_transfer xfer[3] = {
+ {
+ .len = 2,
+ .cs_change = 1,
+ .tx_buf = st->tx,
+ }, {
+ .len = 2,
+ .cs_change = 1,
+ .tx_buf = st->tx + 2,
+ }, {
+ .len = 2,
+ .tx_buf = st->tx + 4,
+ },
+ };
+ st->tx[0] = SCA3000_WRITE_REG(SCA3000_REG_UNLOCK_ADDR);
+ st->tx[1] = 0x00;
+ st->tx[2] = SCA3000_WRITE_REG(SCA3000_REG_UNLOCK_ADDR);
+ st->tx[3] = 0x50;
+ st->tx[4] = SCA3000_WRITE_REG(SCA3000_REG_UNLOCK_ADDR);
+ st->tx[5] = 0xA0;
+
+ return spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer));
+}
+
+/**
+ * sca3000_write_ctrl_reg() - write to a lock protect ctrl register
+ * @st: Driver specific device instance data.
+ * @sel: selects which registers we wish to write to
+ * @val: the value to be written
+ *
+ * Certain control registers are protected against overwriting by the lock
+ * register and use a shared write address. This function allows writing of
+ * these registers.
+ * Lock must be held.
+ */
+static int sca3000_write_ctrl_reg(struct sca3000_state *st,
+ u8 sel,
+ uint8_t val)
+{
+ int ret;
+
+ ret = sca3000_reg_lock_on(st);
+ if (ret < 0)
+ return ret;
+ if (ret) {
+ ret = __sca3000_unlock_reg_lock(st);
+ if (ret)
+ return ret;
+ }
+
+ /* Set the control select register */
+ ret = sca3000_write_reg(st, SCA3000_REG_CTRL_SEL_ADDR, sel);
+ if (ret)
+ return ret;
+
+ /* Write the actual value into the register */
+ return sca3000_write_reg(st, SCA3000_REG_CTRL_DATA_ADDR, val);
+}
+
+/**
+ * sca3000_read_ctrl_reg() - read from lock protected control register.
+ * @st: Driver specific device instance data.
+ * @ctrl_reg: Which ctrl register do we want to read.
+ *
+ * Lock must be held.
+ */
+static int sca3000_read_ctrl_reg(struct sca3000_state *st,
+ u8 ctrl_reg)
+{
+ int ret;
+
+ ret = sca3000_reg_lock_on(st);
+ if (ret < 0)
+ return ret;
+ if (ret) {
+ ret = __sca3000_unlock_reg_lock(st);
+ if (ret)
+ return ret;
+ }
+ /* Set the control select register */
+ ret = sca3000_write_reg(st, SCA3000_REG_CTRL_SEL_ADDR, ctrl_reg);
+ if (ret)
+ return ret;
+ ret = sca3000_read_data_short(st, SCA3000_REG_CTRL_DATA_ADDR, 1);
+ if (ret)
+ return ret;
+ return st->rx[0];
+}
+
+/**
+ * sca3000_print_rev() - sysfs interface to read the chip revision number
+ * @indio_dev: Device instance specific generic IIO data.
+ * Driver specific device instance data can be obtained via
+ * iio_priv(indio_dev)
+ */
+static int sca3000_print_rev(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct sca3000_state *st = iio_priv(indio_dev);
+
+ mutex_lock(&st->lock);
+ ret = sca3000_read_data_short(st, SCA3000_REG_REVID_ADDR, 1);
+ if (ret < 0)
+ goto error_ret;
+ dev_info(&indio_dev->dev,
+ "sca3000 revision major=%lu, minor=%lu\n",
+ st->rx[0] & SCA3000_REG_REVID_MAJOR_MASK,
+ st->rx[0] & SCA3000_REG_REVID_MINOR_MASK);
+error_ret:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static ssize_t
+sca3000_show_available_3db_freqs(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int len;
+
+ len = sprintf(buf, "%d", st->info->measurement_mode_3db_freq);
+ if (st->info->option_mode_1)
+ len += sprintf(buf + len, " %d",
+ st->info->option_mode_1_3db_freq);
+ if (st->info->option_mode_2)
+ len += sprintf(buf + len, " %d",
+ st->info->option_mode_2_3db_freq);
+ len += sprintf(buf + len, "\n");
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(in_accel_filter_low_pass_3db_frequency_available,
+ S_IRUGO, sca3000_show_available_3db_freqs,
+ NULL, 0);
+
+static const struct iio_event_spec sca3000_event = {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE),
+};
+
+/*
+ * Note the hack in the number of bits to pretend we have 2 more than
+ * we do in the fifo.
+ */
+#define SCA3000_CHAN(index, mod) \
+ { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = mod, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |\
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY),\
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
+ .address = index, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 13, \
+ .storagebits = 16, \
+ .shift = 3, \
+ .endianness = IIO_BE, \
+ }, \
+ .event_spec = &sca3000_event, \
+ .num_event_specs = 1, \
+ }
+
+static const struct iio_event_spec sca3000_freefall_event_spec = {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_PERIOD),
+};
+
+static const struct iio_chan_spec sca3000_channels[] = {
+ SCA3000_CHAN(0, IIO_MOD_X),
+ SCA3000_CHAN(1, IIO_MOD_Y),
+ SCA3000_CHAN(2, IIO_MOD_Z),
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X_AND_Y_AND_Z,
+ .scan_index = -1, /* Fake channel */
+ .event_spec = &sca3000_freefall_event_spec,
+ .num_event_specs = 1,
+ },
+};
+
+static const struct iio_chan_spec sca3000_channels_with_temp[] = {
+ SCA3000_CHAN(0, IIO_MOD_X),
+ SCA3000_CHAN(1, IIO_MOD_Y),
+ SCA3000_CHAN(2, IIO_MOD_Z),
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ /* No buffer support */
+ .scan_index = -1,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 9,
+ .storagebits = 16,
+ .shift = 5,
+ .endianness = IIO_BE,
+ },
+ },
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X_AND_Y_AND_Z,
+ .scan_index = -1, /* Fake channel */
+ .event_spec = &sca3000_freefall_event_spec,
+ .num_event_specs = 1,
+ },
+};
+
+static u8 sca3000_addresses[3][3] = {
+ [0] = {SCA3000_REG_X_MSB_ADDR, SCA3000_REG_CTRL_SEL_MD_X_TH,
+ SCA3000_MD_CTRL_OR_X},
+ [1] = {SCA3000_REG_Y_MSB_ADDR, SCA3000_REG_CTRL_SEL_MD_Y_TH,
+ SCA3000_MD_CTRL_OR_Y},
+ [2] = {SCA3000_REG_Z_MSB_ADDR, SCA3000_REG_CTRL_SEL_MD_Z_TH,
+ SCA3000_MD_CTRL_OR_Z},
+};
+
+/**
+ * __sca3000_get_base_freq() - obtain mode specific base frequency
+ * @st: Private driver specific device instance specific state.
+ * @info: chip type specific information.
+ * @base_freq: Base frequency for the current measurement mode.
+ *
+ * lock must be held
+ */
+static inline int __sca3000_get_base_freq(struct sca3000_state *st,
+ const struct sca3000_chip_info *info,
+ int *base_freq)
+{
+ int ret;
+
+ ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1);
+ if (ret)
+ return ret;
+
+ switch (SCA3000_REG_MODE_MODE_MASK & st->rx[0]) {
+ case SCA3000_REG_MODE_MEAS_MODE_NORMAL:
+ *base_freq = info->measurement_mode_freq;
+ break;
+ case SCA3000_REG_MODE_MEAS_MODE_OP_1:
+ *base_freq = info->option_mode_1_freq;
+ break;
+ case SCA3000_REG_MODE_MEAS_MODE_OP_2:
+ *base_freq = info->option_mode_2_freq;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+/**
+ * sca3000_read_raw_samp_freq() - read_raw handler for IIO_CHAN_INFO_SAMP_FREQ
+ * @st: Private driver specific device instance specific state.
+ * @val: The frequency read back.
+ *
+ * lock must be held
+ **/
+static int sca3000_read_raw_samp_freq(struct sca3000_state *st, int *val)
+{
+ int ret;
+
+ ret = __sca3000_get_base_freq(st, st->info, val);
+ if (ret)
+ return ret;
+
+ ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL);
+ if (ret < 0)
+ return ret;
+
+ if (*val > 0) {
+ ret &= SCA3000_REG_OUT_CTRL_BUF_DIV_MASK;
+ switch (ret) {
+ case SCA3000_REG_OUT_CTRL_BUF_DIV_2:
+ *val /= 2;
+ break;
+ case SCA3000_REG_OUT_CTRL_BUF_DIV_4:
+ *val /= 4;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * sca3000_write_raw_samp_freq() - write_raw handler for IIO_CHAN_INFO_SAMP_FREQ
+ * @st: Private driver specific device instance specific state.
+ * @val: The frequency desired.
+ *
+ * lock must be held
+ */
+static int sca3000_write_raw_samp_freq(struct sca3000_state *st, int val)
+{
+ int ret, base_freq, ctrlval;
+
+ ret = __sca3000_get_base_freq(st, st->info, &base_freq);
+ if (ret)
+ return ret;
+
+ ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL);
+ if (ret < 0)
+ return ret;
+
+ ctrlval = ret & ~SCA3000_REG_OUT_CTRL_BUF_DIV_MASK;
+
+ if (val == base_freq / 2)
+ ctrlval |= SCA3000_REG_OUT_CTRL_BUF_DIV_2;
+ else if (val == base_freq / 4)
+ ctrlval |= SCA3000_REG_OUT_CTRL_BUF_DIV_4;
+ else if (val != base_freq)
+ return -EINVAL;
+
+ return sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL,
+ ctrlval);
+}
+
+static int sca3000_read_3db_freq(struct sca3000_state *st, int *val)
+{
+ int ret;
+
+ ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1);
+ if (ret)
+ return ret;
+
+ /* mask bottom 2 bits - only ones that are relevant */
+ st->rx[0] &= SCA3000_REG_MODE_MODE_MASK;
+ switch (st->rx[0]) {
+ case SCA3000_REG_MODE_MEAS_MODE_NORMAL:
+ *val = st->info->measurement_mode_3db_freq;
+ return IIO_VAL_INT;
+ case SCA3000_REG_MODE_MEAS_MODE_MOT_DET:
+ return -EBUSY;
+ case SCA3000_REG_MODE_MEAS_MODE_OP_1:
+ *val = st->info->option_mode_1_3db_freq;
+ return IIO_VAL_INT;
+ case SCA3000_REG_MODE_MEAS_MODE_OP_2:
+ *val = st->info->option_mode_2_3db_freq;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int sca3000_write_3db_freq(struct sca3000_state *st, int val)
+{
+ int ret;
+ int mode;
+
+ if (val == st->info->measurement_mode_3db_freq)
+ mode = SCA3000_REG_MODE_MEAS_MODE_NORMAL;
+ else if (st->info->option_mode_1 &&
+ (val == st->info->option_mode_1_3db_freq))
+ mode = SCA3000_REG_MODE_MEAS_MODE_OP_1;
+ else if (st->info->option_mode_2 &&
+ (val == st->info->option_mode_2_3db_freq))
+ mode = SCA3000_REG_MODE_MEAS_MODE_OP_2;
+ else
+ return -EINVAL;
+ ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1);
+ if (ret)
+ return ret;
+
+ st->rx[0] &= ~SCA3000_REG_MODE_MODE_MASK;
+ st->rx[0] |= (mode & SCA3000_REG_MODE_MODE_MASK);
+
+ return sca3000_write_reg(st, SCA3000_REG_MODE_ADDR, st->rx[0]);
+}
+
+static int sca3000_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret;
+ u8 address;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&st->lock);
+ if (chan->type == IIO_ACCEL) {
+ if (st->mo_det_use_count) {
+ mutex_unlock(&st->lock);
+ return -EBUSY;
+ }
+ address = sca3000_addresses[chan->address][0];
+ ret = sca3000_read_data_short(st, address, 2);
+ if (ret < 0) {
+ mutex_unlock(&st->lock);
+ return ret;
+ }
+ *val = sign_extend32(be16_to_cpup((__be16 *)st->rx) >>
+ chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+ } else {
+ /* get the temperature when available */
+ ret = sca3000_read_data_short(st,
+ SCA3000_REG_TEMP_MSB_ADDR,
+ 2);
+ if (ret < 0) {
+ mutex_unlock(&st->lock);
+ return ret;
+ }
+ *val = (be16_to_cpup((__be16 *)st->rx) >>
+ chan->scan_type.shift) &
+ GENMASK(chan->scan_type.realbits - 1, 0);
+ }
+ mutex_unlock(&st->lock);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ if (chan->type == IIO_ACCEL)
+ *val2 = st->info->scale;
+ else /* temperature */
+ *val2 = 555556;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = -214;
+ *val2 = 600000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ mutex_lock(&st->lock);
+ ret = sca3000_read_raw_samp_freq(st, val);
+ mutex_unlock(&st->lock);
+ return ret ? ret : IIO_VAL_INT;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ mutex_lock(&st->lock);
+ ret = sca3000_read_3db_freq(st, val);
+ mutex_unlock(&st->lock);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int sca3000_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (val2)
+ return -EINVAL;
+ mutex_lock(&st->lock);
+ ret = sca3000_write_raw_samp_freq(st, val);
+ mutex_unlock(&st->lock);
+ return ret;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ if (val2)
+ return -EINVAL;
+ mutex_lock(&st->lock);
+ ret = sca3000_write_3db_freq(st, val);
+ mutex_unlock(&st->lock);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+/**
+ * sca3000_read_av_freq() - sysfs function to get available frequencies
+ * @dev: Device structure for this device.
+ * @attr: Description of the attribute.
+ * @buf: Incoming string
+ *
+ * The later modes are only relevant to the ring buffer - and depend on current
+ * mode. Note that data sheet gives rather wide tolerances for these so integer
+ * division will give good enough answer and not all chips have them specified
+ * at all.
+ **/
+static ssize_t sca3000_read_av_freq(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int len = 0, ret, val;
+
+ mutex_lock(&st->lock);
+ ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1);
+ val = st->rx[0];
+ mutex_unlock(&st->lock);
+ if (ret)
+ return ret;
+
+ switch (val & SCA3000_REG_MODE_MODE_MASK) {
+ case SCA3000_REG_MODE_MEAS_MODE_NORMAL:
+ len += sprintf(buf + len, "%d %d %d\n",
+ st->info->measurement_mode_freq,
+ st->info->measurement_mode_freq / 2,
+ st->info->measurement_mode_freq / 4);
+ break;
+ case SCA3000_REG_MODE_MEAS_MODE_OP_1:
+ len += sprintf(buf + len, "%d %d %d\n",
+ st->info->option_mode_1_freq,
+ st->info->option_mode_1_freq / 2,
+ st->info->option_mode_1_freq / 4);
+ break;
+ case SCA3000_REG_MODE_MEAS_MODE_OP_2:
+ len += sprintf(buf + len, "%d %d %d\n",
+ st->info->option_mode_2_freq,
+ st->info->option_mode_2_freq / 2,
+ st->info->option_mode_2_freq / 4);
+ break;
+ }
+ return len;
+}
+
+/*
+ * Should only really be registered if ring buffer support is compiled in.
+ * Does no harm however and doing it right would add a fair bit of complexity
+ */
+static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(sca3000_read_av_freq);
+
+/*
+ * sca3000_read_event_value() - query of a threshold or period
+ */
+static int sca3000_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ long ret;
+ int i;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ mutex_lock(&st->lock);
+ ret = sca3000_read_ctrl_reg(st,
+ sca3000_addresses[chan->address][1]);
+ mutex_unlock(&st->lock);
+ if (ret < 0)
+ return ret;
+ *val = 0;
+ if (chan->channel2 == IIO_MOD_Y)
+ for_each_set_bit(i, &ret,
+ ARRAY_SIZE(st->info->mot_det_mult_y))
+ *val += st->info->mot_det_mult_y[i];
+ else
+ for_each_set_bit(i, &ret,
+ ARRAY_SIZE(st->info->mot_det_mult_xz))
+ *val += st->info->mot_det_mult_xz[i];
+
+ return IIO_VAL_INT;
+ case IIO_EV_INFO_PERIOD:
+ *val = 0;
+ *val2 = 226000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * sca3000_write_event_value() - control of threshold and period
+ * @indio_dev: Device instance specific IIO information.
+ * @chan: Description of the channel for which the event is being
+ * configured.
+ * @type: The type of event being configured, here magnitude rising
+ * as everything else is read only.
+ * @dir: Direction of the event (here rising)
+ * @info: What information about the event are we configuring.
+ * Here the threshold only.
+ * @val: Integer part of the value being written..
+ * @val2: Non integer part of the value being written. Here always 0.
+ */
+static int sca3000_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret;
+ int i;
+ u8 nonlinear = 0;
+
+ if (chan->channel2 == IIO_MOD_Y) {
+ i = ARRAY_SIZE(st->info->mot_det_mult_y);
+ while (i > 0)
+ if (val >= st->info->mot_det_mult_y[--i]) {
+ nonlinear |= (1 << i);
+ val -= st->info->mot_det_mult_y[i];
+ }
+ } else {
+ i = ARRAY_SIZE(st->info->mot_det_mult_xz);
+ while (i > 0)
+ if (val >= st->info->mot_det_mult_xz[--i]) {
+ nonlinear |= (1 << i);
+ val -= st->info->mot_det_mult_xz[i];
+ }
+ }
+
+ mutex_lock(&st->lock);
+ ret = sca3000_write_ctrl_reg(st,
+ sca3000_addresses[chan->address][1],
+ nonlinear);
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static struct attribute *sca3000_attributes[] = {
+ &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr,
+ &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group sca3000_attribute_group = {
+ .attrs = sca3000_attributes,
+};
+
+static int sca3000_read_data(struct sca3000_state *st,
+ u8 reg_address_high,
+ u8 *rx,
+ int len)
+{
+ int ret;
+ struct spi_transfer xfer[2] = {
+ {
+ .len = 1,
+ .tx_buf = st->tx,
+ }, {
+ .len = len,
+ .rx_buf = rx,
+ }
+ };
+
+ st->tx[0] = SCA3000_READ_REG(reg_address_high);
+ ret = spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer));
+ if (ret) {
+ dev_err(&st->us->dev, "problem reading register\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * sca3000_ring_int_process() - ring specific interrupt handling.
+ * @val: Value of the interrupt status register.
+ * @indio_dev: Device instance specific IIO device structure.
+ */
+static void sca3000_ring_int_process(u8 val, struct iio_dev *indio_dev)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret, i, num_available;
+
+ mutex_lock(&st->lock);
+
+ if (val & SCA3000_REG_INT_STATUS_HALF) {
+ ret = sca3000_read_data_short(st, SCA3000_REG_BUF_COUNT_ADDR,
+ 1);
+ if (ret)
+ goto error_ret;
+ num_available = st->rx[0];
+ /*
+ * num_available is the total number of samples available
+ * i.e. number of time points * number of channels.
+ */
+ ret = sca3000_read_data(st, SCA3000_REG_RING_OUT_ADDR, st->rx,
+ num_available * 2);
+ if (ret)
+ goto error_ret;
+ for (i = 0; i < num_available / 3; i++) {
+ /*
+ * Dirty hack to cover for 11 bit in fifo, 13 bit
+ * direct reading.
+ *
+ * In theory the bottom two bits are undefined.
+ * In reality they appear to always be 0.
+ */
+ iio_push_to_buffers(indio_dev, st->rx + i * 3 * 2);
+ }
+ }
+error_ret:
+ mutex_unlock(&st->lock);
+}
+
+/**
+ * sca3000_event_handler() - handling ring and non ring events
+ * @irq: The irq being handled.
+ * @private: struct iio_device pointer for the device.
+ *
+ * Ring related interrupt handler. Depending on event, push to
+ * the ring buffer event chrdev or the event one.
+ *
+ * This function is complicated by the fact that the devices can signify ring
+ * and non ring events via the same interrupt line and they can only
+ * be distinguished via a read of the relevant status register.
+ */
+static irqreturn_t sca3000_event_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret, val;
+ s64 last_timestamp = iio_get_time_ns(indio_dev);
+
+ /*
+ * Could lead if badly timed to an extra read of status reg,
+ * but ensures no interrupt is missed.
+ */
+ mutex_lock(&st->lock);
+ ret = sca3000_read_data_short(st, SCA3000_REG_INT_STATUS_ADDR, 1);
+ val = st->rx[0];
+ mutex_unlock(&st->lock);
+ if (ret)
+ goto done;
+
+ sca3000_ring_int_process(val, indio_dev);
+
+ if (val & SCA3000_INT_STATUS_FREE_FALL)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_X_AND_Y_AND_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_FALLING),
+ last_timestamp);
+
+ if (val & SCA3000_INT_STATUS_Y_TRIGGER)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_Y,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ last_timestamp);
+
+ if (val & SCA3000_INT_STATUS_X_TRIGGER)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_X,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ last_timestamp);
+
+ if (val & SCA3000_INT_STATUS_Z_TRIGGER)
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL,
+ 0,
+ IIO_MOD_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ last_timestamp);
+
+done:
+ return IRQ_HANDLED;
+}
+
+/*
+ * sca3000_read_event_config() what events are enabled
+ */
+static int sca3000_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 sca3000_state *st = iio_priv(indio_dev);
+ int ret;
+ /* read current value of mode register */
+ mutex_lock(&st->lock);
+
+ ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1);
+ if (ret)
+ goto error_ret;
+
+ switch (chan->channel2) {
+ case IIO_MOD_X_AND_Y_AND_Z:
+ ret = !!(st->rx[0] & SCA3000_REG_MODE_FREE_FALL_DETECT);
+ break;
+ case IIO_MOD_X:
+ case IIO_MOD_Y:
+ case IIO_MOD_Z:
+ /*
+ * Motion detection mode cannot run at the same time as
+ * acceleration data being read.
+ */
+ if ((st->rx[0] & SCA3000_REG_MODE_MODE_MASK)
+ != SCA3000_REG_MODE_MEAS_MODE_MOT_DET) {
+ ret = 0;
+ } else {
+ ret = sca3000_read_ctrl_reg(st,
+ SCA3000_REG_CTRL_SEL_MD_CTRL);
+ if (ret < 0)
+ goto error_ret;
+ /* only supporting logical or's for now */
+ ret = !!(ret & sca3000_addresses[chan->address][2]);
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+error_ret:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int sca3000_freefall_set_state(struct iio_dev *indio_dev, bool state)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret;
+
+ /* read current value of mode register */
+ ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1);
+ if (ret)
+ return ret;
+
+ /* if off and should be on */
+ if (state && !(st->rx[0] & SCA3000_REG_MODE_FREE_FALL_DETECT))
+ return sca3000_write_reg(st, SCA3000_REG_MODE_ADDR,
+ st->rx[0] | SCA3000_REG_MODE_FREE_FALL_DETECT);
+ /* if on and should be off */
+ else if (!state && (st->rx[0] & SCA3000_REG_MODE_FREE_FALL_DETECT))
+ return sca3000_write_reg(st, SCA3000_REG_MODE_ADDR,
+ st->rx[0] & ~SCA3000_REG_MODE_FREE_FALL_DETECT);
+ else
+ return 0;
+}
+
+static int sca3000_motion_detect_set_state(struct iio_dev *indio_dev, int axis,
+ bool state)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret, ctrlval;
+
+ /*
+ * First read the motion detector config to find out if
+ * this axis is on
+ */
+ ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL);
+ if (ret < 0)
+ return ret;
+ ctrlval = ret;
+ /* if off and should be on */
+ if (state && !(ctrlval & sca3000_addresses[axis][2])) {
+ ret = sca3000_write_ctrl_reg(st,
+ SCA3000_REG_CTRL_SEL_MD_CTRL,
+ ctrlval |
+ sca3000_addresses[axis][2]);
+ if (ret)
+ return ret;
+ st->mo_det_use_count++;
+ } else if (!state && (ctrlval & sca3000_addresses[axis][2])) {
+ ret = sca3000_write_ctrl_reg(st,
+ SCA3000_REG_CTRL_SEL_MD_CTRL,
+ ctrlval &
+ ~(sca3000_addresses[axis][2]));
+ if (ret)
+ return ret;
+ st->mo_det_use_count--;
+ }
+
+ /* read current value of mode register */
+ ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1);
+ if (ret)
+ return ret;
+ /* if off and should be on */
+ if ((st->mo_det_use_count) &&
+ ((st->rx[0] & SCA3000_REG_MODE_MODE_MASK)
+ != SCA3000_REG_MODE_MEAS_MODE_MOT_DET))
+ return sca3000_write_reg(st, SCA3000_REG_MODE_ADDR,
+ (st->rx[0] & ~SCA3000_REG_MODE_MODE_MASK)
+ | SCA3000_REG_MODE_MEAS_MODE_MOT_DET);
+ /* if on and should be off */
+ else if (!(st->mo_det_use_count) &&
+ ((st->rx[0] & SCA3000_REG_MODE_MODE_MASK)
+ == SCA3000_REG_MODE_MEAS_MODE_MOT_DET))
+ return sca3000_write_reg(st, SCA3000_REG_MODE_ADDR,
+ st->rx[0] & SCA3000_REG_MODE_MODE_MASK);
+ else
+ return 0;
+}
+
+/**
+ * sca3000_write_event_config() - simple on off control for motion detector
+ * @indio_dev: IIO device instance specific structure. Data specific to this
+ * particular driver may be accessed via iio_priv(indio_dev).
+ * @chan: Description of the channel whose event we are configuring.
+ * @type: The type of event.
+ * @dir: The direction of the event.
+ * @state: Desired state of event being configured.
+ *
+ * This is a per axis control, but enabling any will result in the
+ * motion detector unit being enabled.
+ * N.B. enabling motion detector stops normal data acquisition.
+ * There is a complexity in knowing which mode to return to when
+ * this mode is disabled. Currently normal mode is assumed.
+ **/
+static int sca3000_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ bool state)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&st->lock);
+ switch (chan->channel2) {
+ case IIO_MOD_X_AND_Y_AND_Z:
+ ret = sca3000_freefall_set_state(indio_dev, state);
+ break;
+
+ case IIO_MOD_X:
+ case IIO_MOD_Y:
+ case IIO_MOD_Z:
+ ret = sca3000_motion_detect_set_state(indio_dev,
+ chan->address,
+ state);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static inline
+int __sca3000_hw_ring_state_set(struct iio_dev *indio_dev, bool state)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&st->lock);
+ ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1);
+ if (ret)
+ goto error_ret;
+ if (state) {
+ dev_info(&indio_dev->dev, "supposedly enabling ring buffer\n");
+ ret = sca3000_write_reg(st,
+ SCA3000_REG_MODE_ADDR,
+ (st->rx[0] | SCA3000_REG_MODE_RING_BUF_ENABLE));
+ } else
+ ret = sca3000_write_reg(st,
+ SCA3000_REG_MODE_ADDR,
+ (st->rx[0] & ~SCA3000_REG_MODE_RING_BUF_ENABLE));
+error_ret:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+/**
+ * sca3000_hw_ring_preenable() - hw ring buffer preenable function
+ * @indio_dev: structure representing the IIO device. Device instance
+ * specific state can be accessed via iio_priv(indio_dev).
+ *
+ * Very simple enable function as the chip will allows normal reads
+ * during ring buffer operation so as long as it is indeed running
+ * before we notify the core, the precise ordering does not matter.
+ */
+static int sca3000_hw_ring_preenable(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct sca3000_state *st = iio_priv(indio_dev);
+
+ mutex_lock(&st->lock);
+
+ /* Enable the 50% full interrupt */
+ ret = sca3000_read_data_short(st, SCA3000_REG_INT_MASK_ADDR, 1);
+ if (ret)
+ goto error_unlock;
+ ret = sca3000_write_reg(st,
+ SCA3000_REG_INT_MASK_ADDR,
+ st->rx[0] | SCA3000_REG_INT_MASK_RING_HALF);
+ if (ret)
+ goto error_unlock;
+
+ mutex_unlock(&st->lock);
+
+ return __sca3000_hw_ring_state_set(indio_dev, 1);
+
+error_unlock:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int sca3000_hw_ring_postdisable(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct sca3000_state *st = iio_priv(indio_dev);
+
+ ret = __sca3000_hw_ring_state_set(indio_dev, 0);
+ if (ret)
+ return ret;
+
+ /* Disable the 50% full interrupt */
+ mutex_lock(&st->lock);
+
+ ret = sca3000_read_data_short(st, SCA3000_REG_INT_MASK_ADDR, 1);
+ if (ret)
+ goto unlock;
+ ret = sca3000_write_reg(st,
+ SCA3000_REG_INT_MASK_ADDR,
+ st->rx[0] & ~SCA3000_REG_INT_MASK_RING_HALF);
+unlock:
+ mutex_unlock(&st->lock);
+ return ret;
+}
+
+static const struct iio_buffer_setup_ops sca3000_ring_setup_ops = {
+ .preenable = &sca3000_hw_ring_preenable,
+ .postdisable = &sca3000_hw_ring_postdisable,
+};
+
+/**
+ * sca3000_clean_setup() - get the device into a predictable state
+ * @st: Device instance specific private data structure
+ *
+ * Devices use flash memory to store many of the register values
+ * and hence can come up in somewhat unpredictable states.
+ * Hence reset everything on driver load.
+ */
+static int sca3000_clean_setup(struct sca3000_state *st)
+{
+ int ret;
+
+ mutex_lock(&st->lock);
+ /* Ensure all interrupts have been acknowledged */
+ ret = sca3000_read_data_short(st, SCA3000_REG_INT_STATUS_ADDR, 1);
+ if (ret)
+ goto error_ret;
+
+ /* Turn off all motion detection channels */
+ ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL);
+ if (ret < 0)
+ goto error_ret;
+ ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL,
+ ret & SCA3000_MD_CTRL_PROT_MASK);
+ if (ret)
+ goto error_ret;
+
+ /* Disable ring buffer */
+ ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL);
+ if (ret < 0)
+ goto error_ret;
+ ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL,
+ (ret & SCA3000_REG_OUT_CTRL_PROT_MASK)
+ | SCA3000_REG_OUT_CTRL_BUF_X_EN
+ | SCA3000_REG_OUT_CTRL_BUF_Y_EN
+ | SCA3000_REG_OUT_CTRL_BUF_Z_EN
+ | SCA3000_REG_OUT_CTRL_BUF_DIV_4);
+ if (ret)
+ goto error_ret;
+ /* Enable interrupts, relevant to mode and set up as active low */
+ ret = sca3000_read_data_short(st, SCA3000_REG_INT_MASK_ADDR, 1);
+ if (ret)
+ goto error_ret;
+ ret = sca3000_write_reg(st,
+ SCA3000_REG_INT_MASK_ADDR,
+ (ret & SCA3000_REG_INT_MASK_PROT_MASK)
+ | SCA3000_REG_INT_MASK_ACTIVE_LOW);
+ if (ret)
+ goto error_ret;
+ /*
+ * Select normal measurement mode, free fall off, ring off
+ * Ring in 12 bit mode - it is fine to overwrite reserved bits 3,5
+ * as that occurs in one of the example on the datasheet
+ */
+ ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1);
+ if (ret)
+ goto error_ret;
+ ret = sca3000_write_reg(st, SCA3000_REG_MODE_ADDR,
+ (st->rx[0] & SCA3000_MODE_PROT_MASK));
+
+error_ret:
+ mutex_unlock(&st->lock);
+ return ret;
+}
+
+static const struct iio_info sca3000_info = {
+ .attrs = &sca3000_attribute_group,
+ .read_raw = &sca3000_read_raw,
+ .write_raw = &sca3000_write_raw,
+ .read_event_value = &sca3000_read_event_value,
+ .write_event_value = &sca3000_write_event_value,
+ .read_event_config = &sca3000_read_event_config,
+ .write_event_config = &sca3000_write_event_config,
+};
+
+static void sca3000_stop_all_interrupts(void *data)
+{
+ struct iio_dev *indio_dev = data;
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = sca3000_read_data_short(st, SCA3000_REG_INT_MASK_ADDR, 1);
+ if (ret)
+ return;
+
+ sca3000_write_reg(st, SCA3000_REG_INT_MASK_ADDR,
+ (st->rx[0] &
+ ~(SCA3000_REG_INT_MASK_RING_THREE_QUARTER |
+ SCA3000_REG_INT_MASK_RING_HALF |
+ SCA3000_REG_INT_MASK_ALL_INTS)));
+}
+
+static int sca3000_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct sca3000_state *st;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->us = spi;
+ mutex_init(&st->lock);
+ st->info = spi_get_device_match_data(spi);
+
+ indio_dev->name = st->info->name;
+ indio_dev->info = &sca3000_info;
+ if (st->info->temp_output) {
+ indio_dev->channels = sca3000_channels_with_temp;
+ indio_dev->num_channels =
+ ARRAY_SIZE(sca3000_channels_with_temp);
+ } else {
+ indio_dev->channels = sca3000_channels;
+ indio_dev->num_channels = ARRAY_SIZE(sca3000_channels);
+ }
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &sca3000_ring_setup_ops);
+ if (ret)
+ return ret;
+
+ if (spi->irq) {
+ ret = devm_request_threaded_irq(dev, spi->irq, NULL,
+ &sca3000_event_handler,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "sca3000",
+ indio_dev);
+ if (ret)
+ return ret;
+ }
+ ret = sca3000_clean_setup(st);
+ if (ret)
+ return ret;
+
+ ret = sca3000_print_rev(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, sca3000_stop_all_interrupts, indio_dev);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct spi_device_id sca3000_id[] = {
+ { .name = "sca3000_d01", .driver_data = (kernel_ulong_t)&sca3000_chip_info_d01 },
+ { .name = "sca3000_e02", .driver_data = (kernel_ulong_t)&sca3000_chip_info_e02 },
+ { .name = "sca3000_e04", .driver_data = (kernel_ulong_t)&sca3000_chip_info_e04 },
+ { .name = "sca3000_e05", .driver_data = (kernel_ulong_t)&sca3000_chip_info_e05 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, sca3000_id);
+
+static struct spi_driver sca3000_driver = {
+ .driver = {
+ .name = "sca3000",
+ },
+ .probe = sca3000_probe,
+ .id_table = sca3000_id,
+};
+module_spi_driver(sca3000_driver);
+
+MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
+MODULE_DESCRIPTION("VTI SCA3000 Series Accelerometers SPI driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/sca3300.c b/drivers/iio/accel/sca3300.c
new file mode 100644
index 000000000..67582cc34
--- /dev/null
+++ b/drivers/iio/accel/sca3300.c
@@ -0,0 +1,685 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Murata SCA3300 3-axis industrial accelerometer
+ *
+ * Copyright (c) 2021 Vaisala Oyj. All rights reserved.
+ */
+
+#include <linux/bitops.h>
+#include <linux/crc8.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include <linux/unaligned.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define SCA3300_CRC8_POLYNOMIAL 0x1d
+
+/* Device mode register */
+#define SCA3300_REG_MODE 0xd
+#define SCA3300_MODE_SW_RESET 0x20
+
+/* Last register in map */
+#define SCA3300_REG_SELBANK 0x1f
+
+/* Device status and mask */
+#define SCA3300_REG_STATUS 0x6
+#define SCA3300_STATUS_MASK GENMASK(8, 0)
+
+/* Device ID */
+#define SCA3300_REG_WHOAMI 0x10
+#define SCA3300_WHOAMI_ID 0x51
+#define SCL3300_WHOAMI_ID 0xC1
+
+/* Device return status and mask */
+#define SCA3300_VALUE_RS_ERROR 0x3
+#define SCA3300_MASK_RS_STATUS GENMASK(1, 0)
+
+#define SCL3300_REG_ANG_CTRL 0x0C
+#define SCL3300_ANG_ENABLE 0x1F
+
+enum sca3300_scan_indexes {
+ SCA3300_ACC_X = 0,
+ SCA3300_ACC_Y,
+ SCA3300_ACC_Z,
+ SCA3300_TEMP,
+ SCA3300_INCLI_X,
+ SCA3300_INCLI_Y,
+ SCA3300_INCLI_Z,
+ SCA3300_SCAN_MAX
+};
+
+#define SCA3300_ACCEL_CHANNEL(index, reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_CPU, \
+ }, \
+}
+
+#define SCA3300_INCLI_CHANNEL(index, reg, axis) { \
+ .type = IIO_INCLI, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_CPU, \
+ }, \
+}
+
+#define SCA3300_TEMP_CHANNEL(index, reg) { \
+ .type = IIO_TEMP, \
+ .address = reg, \
+ .scan_index = index, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_CPU, \
+ }, \
+}
+
+static const struct iio_chan_spec sca3300_channels[] = {
+ SCA3300_ACCEL_CHANNEL(SCA3300_ACC_X, 0x1, X),
+ SCA3300_ACCEL_CHANNEL(SCA3300_ACC_Y, 0x2, Y),
+ SCA3300_ACCEL_CHANNEL(SCA3300_ACC_Z, 0x3, Z),
+ SCA3300_TEMP_CHANNEL(SCA3300_TEMP, 0x05),
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static const int sca3300_lp_freq[] = {70, 10};
+static const int sca3300_lp_freq_map[] = {0, 0, 0, 1};
+
+static const int scl3300_lp_freq[] = {40, 70, 10};
+static const int scl3300_lp_freq_map[] = {0, 1, 2};
+
+static const int sca3300_accel_scale[][2] = {{0, 370}, {0, 741}, {0, 185}};
+static const int sca3300_accel_scale_map[] = {0, 1, 2, 2};
+
+static const int scl3300_accel_scale[][2] = {{0, 167}, {0, 333}, {0, 83}};
+static const int scl3300_accel_scale_map[] = {0, 1, 2};
+
+static const int scl3300_incli_scale[][2] = {{0, 5495}};
+static const int scl3300_incli_scale_map[] = {0, 0, 0};
+
+static const int sca3300_avail_modes_map[] = {0, 1, 2, 3};
+static const int scl3300_avail_modes_map[] = {0, 1, 3};
+
+static const struct iio_chan_spec scl3300_channels[] = {
+ SCA3300_ACCEL_CHANNEL(SCA3300_ACC_X, 0x1, X),
+ SCA3300_ACCEL_CHANNEL(SCA3300_ACC_Y, 0x2, Y),
+ SCA3300_ACCEL_CHANNEL(SCA3300_ACC_Z, 0x3, Z),
+ SCA3300_TEMP_CHANNEL(SCA3300_TEMP, 0x05),
+ SCA3300_INCLI_CHANNEL(SCA3300_INCLI_X, 0x09, X),
+ SCA3300_INCLI_CHANNEL(SCA3300_INCLI_Y, 0x0A, Y),
+ SCA3300_INCLI_CHANNEL(SCA3300_INCLI_Z, 0x0B, Z),
+ IIO_CHAN_SOFT_TIMESTAMP(7),
+};
+
+static const unsigned long sca3300_scan_masks[] = {
+ BIT(SCA3300_ACC_X) | BIT(SCA3300_ACC_Y) | BIT(SCA3300_ACC_Z) |
+ BIT(SCA3300_TEMP),
+ 0
+};
+
+static const unsigned long scl3300_scan_masks[] = {
+ BIT(SCA3300_ACC_X) | BIT(SCA3300_ACC_Y) | BIT(SCA3300_ACC_Z) |
+ BIT(SCA3300_TEMP) |
+ BIT(SCA3300_INCLI_X) | BIT(SCA3300_INCLI_Y) | BIT(SCA3300_INCLI_Z),
+ 0
+};
+
+struct sca3300_chip_info {
+ const char *name;
+ const unsigned long *scan_masks;
+ const struct iio_chan_spec *channels;
+ u8 num_channels;
+ u8 num_accel_scales;
+ const int (*accel_scale)[2];
+ const int *accel_scale_map;
+ const int (*incli_scale)[2];
+ const int *incli_scale_map;
+ u8 num_incli_scales;
+ u8 num_freqs;
+ const int *freq_table;
+ const int *freq_map;
+ const int *avail_modes_table;
+ u8 num_avail_modes;
+ u8 chip_id;
+ bool angle_supported;
+};
+
+/**
+ * struct sca3300_data - device data
+ * @spi: SPI device structure
+ * @lock: Data buffer lock
+ * @chip: Sensor chip specific information
+ * @txbuf: Transmit buffer
+ * @rxbuf: Receive buffer
+ */
+struct sca3300_data {
+ struct spi_device *spi;
+ struct mutex lock;
+ const struct sca3300_chip_info *chip;
+ u8 txbuf[4] __aligned(IIO_DMA_MINALIGN);
+ u8 rxbuf[4];
+};
+
+static const struct sca3300_chip_info sca3300_chip_tbl[] = {
+ {
+ .name = "sca3300",
+ .scan_masks = sca3300_scan_masks,
+ .channels = sca3300_channels,
+ .num_channels = ARRAY_SIZE(sca3300_channels),
+ .num_accel_scales = ARRAY_SIZE(sca3300_accel_scale)*2,
+ .accel_scale = sca3300_accel_scale,
+ .accel_scale_map = sca3300_accel_scale_map,
+ .num_freqs = ARRAY_SIZE(sca3300_lp_freq),
+ .freq_table = sca3300_lp_freq,
+ .freq_map = sca3300_lp_freq_map,
+ .avail_modes_table = sca3300_avail_modes_map,
+ .num_avail_modes = 4,
+ .chip_id = SCA3300_WHOAMI_ID,
+ .angle_supported = false,
+ },
+ {
+ .name = "scl3300",
+ .scan_masks = scl3300_scan_masks,
+ .channels = scl3300_channels,
+ .num_channels = ARRAY_SIZE(scl3300_channels),
+ .num_accel_scales = ARRAY_SIZE(scl3300_accel_scale)*2,
+ .accel_scale = scl3300_accel_scale,
+ .accel_scale_map = scl3300_accel_scale_map,
+ .incli_scale = scl3300_incli_scale,
+ .incli_scale_map = scl3300_incli_scale_map,
+ .num_incli_scales = ARRAY_SIZE(scl3300_incli_scale)*2,
+ .num_freqs = ARRAY_SIZE(scl3300_lp_freq),
+ .freq_table = scl3300_lp_freq,
+ .freq_map = scl3300_lp_freq_map,
+ .avail_modes_table = scl3300_avail_modes_map,
+ .num_avail_modes = 3,
+ .chip_id = SCL3300_WHOAMI_ID,
+ .angle_supported = true,
+ },
+};
+
+DECLARE_CRC8_TABLE(sca3300_crc_table);
+
+static int sca3300_transfer(struct sca3300_data *sca_data, int *val)
+{
+ /* Consecutive requests min. 10 us delay (Datasheet section 5.1.2) */
+ struct spi_delay delay = { .value = 10, .unit = SPI_DELAY_UNIT_USECS };
+ int32_t ret;
+ int rs;
+ u8 crc;
+ struct spi_transfer xfers[2] = {
+ {
+ .tx_buf = sca_data->txbuf,
+ .len = ARRAY_SIZE(sca_data->txbuf),
+ .delay = delay,
+ .cs_change = 1,
+ },
+ {
+ .rx_buf = sca_data->rxbuf,
+ .len = ARRAY_SIZE(sca_data->rxbuf),
+ .delay = delay,
+ }
+ };
+
+ /* inverted crc value as described in device data sheet */
+ crc = ~crc8(sca3300_crc_table, &sca_data->txbuf[0], 3, CRC8_INIT_VALUE);
+ sca_data->txbuf[3] = crc;
+
+ ret = spi_sync_transfer(sca_data->spi, xfers, ARRAY_SIZE(xfers));
+ if (ret) {
+ dev_err(&sca_data->spi->dev,
+ "transfer error, error: %d\n", ret);
+ return -EIO;
+ }
+
+ crc = ~crc8(sca3300_crc_table, &sca_data->rxbuf[0], 3, CRC8_INIT_VALUE);
+ if (sca_data->rxbuf[3] != crc) {
+ dev_err(&sca_data->spi->dev, "CRC checksum mismatch");
+ return -EIO;
+ }
+
+ /* get return status */
+ rs = sca_data->rxbuf[0] & SCA3300_MASK_RS_STATUS;
+ if (rs == SCA3300_VALUE_RS_ERROR)
+ ret = -EINVAL;
+
+ *val = sign_extend32(get_unaligned_be16(&sca_data->rxbuf[1]), 15);
+
+ return ret;
+}
+
+static int sca3300_error_handler(struct sca3300_data *sca_data)
+{
+ int ret;
+ int val;
+
+ mutex_lock(&sca_data->lock);
+ sca_data->txbuf[0] = SCA3300_REG_STATUS << 2;
+ ret = sca3300_transfer(sca_data, &val);
+ mutex_unlock(&sca_data->lock);
+ /*
+ * Return status error is cleared after reading status register once,
+ * expect EINVAL here.
+ */
+ if (ret != -EINVAL) {
+ dev_err(&sca_data->spi->dev,
+ "error reading device status: %d\n", ret);
+ return ret;
+ }
+
+ dev_err(&sca_data->spi->dev, "device status: 0x%lx\n",
+ val & SCA3300_STATUS_MASK);
+
+ return 0;
+}
+
+static int sca3300_read_reg(struct sca3300_data *sca_data, u8 reg, int *val)
+{
+ int ret;
+
+ mutex_lock(&sca_data->lock);
+ sca_data->txbuf[0] = reg << 2;
+ ret = sca3300_transfer(sca_data, val);
+ mutex_unlock(&sca_data->lock);
+ if (ret != -EINVAL)
+ return ret;
+
+ return sca3300_error_handler(sca_data);
+}
+
+static int sca3300_write_reg(struct sca3300_data *sca_data, u8 reg, int val)
+{
+ int reg_val = 0;
+ int ret;
+
+ mutex_lock(&sca_data->lock);
+ /* BIT(7) for write operation */
+ sca_data->txbuf[0] = BIT(7) | (reg << 2);
+ put_unaligned_be16(val, &sca_data->txbuf[1]);
+ ret = sca3300_transfer(sca_data, &reg_val);
+ mutex_unlock(&sca_data->lock);
+ if (ret != -EINVAL)
+ return ret;
+
+ return sca3300_error_handler(sca_data);
+}
+
+static int sca3300_set_op_mode(struct sca3300_data *sca_data, int index)
+{
+ if ((index < 0) || (index >= sca_data->chip->num_avail_modes))
+ return -EINVAL;
+
+ return sca3300_write_reg(sca_data, SCA3300_REG_MODE,
+ sca_data->chip->avail_modes_table[index]);
+}
+
+static int sca3300_get_op_mode(struct sca3300_data *sca_data, int *index)
+{
+ int reg_val;
+ int ret;
+ int i;
+
+ ret = sca3300_read_reg(sca_data, SCA3300_REG_MODE, &reg_val);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < sca_data->chip->num_avail_modes; i++) {
+ if (sca_data->chip->avail_modes_table[i] == reg_val)
+ break;
+ }
+ if (i == sca_data->chip->num_avail_modes)
+ return -EINVAL;
+
+ *index = i;
+ return 0;
+}
+
+static int sca3300_set_frequency(struct sca3300_data *data, int val)
+{
+ const struct sca3300_chip_info *chip = data->chip;
+ unsigned int index;
+ int *opmode_scale;
+ int *new_scale;
+ unsigned int i;
+
+ if (sca3300_get_op_mode(data, &index))
+ return -EINVAL;
+
+ /*
+ * Find a mode in which the requested sampling frequency is available
+ * and the scaling currently set is retained.
+ */
+ opmode_scale = (int *)chip->accel_scale[chip->accel_scale_map[index]];
+ for (i = 0; i < chip->num_avail_modes; i++) {
+ new_scale = (int *)chip->accel_scale[chip->accel_scale_map[i]];
+ if ((val == chip->freq_table[chip->freq_map[i]]) &&
+ (opmode_scale[1] == new_scale[1]) &&
+ (opmode_scale[0] == new_scale[0]))
+ break;
+ }
+ if (i == chip->num_avail_modes)
+ return -EINVAL;
+
+ return sca3300_set_op_mode(data, i);
+}
+
+static int sca3300_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct sca3300_data *data = iio_priv(indio_dev);
+ int index;
+ int i;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type != IIO_ACCEL)
+ return -EINVAL;
+ /*
+ * Letting scale take priority over sampling frequency.
+ * That makes sense given we can only ever end up increasing
+ * the sampling frequency which is unlikely to be a problem.
+ */
+ for (i = 0; i < data->chip->num_avail_modes; i++) {
+ index = data->chip->accel_scale_map[i];
+ if ((val == data->chip->accel_scale[index][0]) &&
+ (val2 == data->chip->accel_scale[index][1]))
+ return sca3300_set_op_mode(data, i);
+ }
+ return -EINVAL;
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ return sca3300_set_frequency(data, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int sca3300_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct sca3300_data *data = iio_priv(indio_dev);
+ int index;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = sca3300_read_reg(data, chan->address, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ ret = sca3300_get_op_mode(data, &index);
+ if (ret)
+ return ret;
+ switch (chan->type) {
+ case IIO_INCLI:
+ index = data->chip->incli_scale_map[index];
+ *val = data->chip->incli_scale[index][0];
+ *val2 = data->chip->incli_scale[index][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ index = data->chip->accel_scale_map[index];
+ *val = data->chip->accel_scale[index][0];
+ *val2 = data->chip->accel_scale[index][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ ret = sca3300_get_op_mode(data, &index);
+ if (ret)
+ return ret;
+ index = data->chip->freq_map[index];
+ *val = data->chip->freq_table[index];
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static irqreturn_t sca3300_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct sca3300_data *data = iio_priv(indio_dev);
+ int bit, ret, val, i = 0;
+ IIO_DECLARE_BUFFER_WITH_TS(s16, channels, SCA3300_SCAN_MAX) = { };
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = sca3300_read_reg(data, indio_dev->channels[bit].address, &val);
+ if (ret) {
+ dev_err_ratelimited(&data->spi->dev,
+ "failed to read register, error: %d\n", ret);
+ /* handled, but bailing out due to errors */
+ goto out;
+ }
+ channels[i++] = val;
+ }
+
+ iio_push_to_buffers_with_ts(indio_dev, channels, sizeof(channels),
+ iio_get_time_ns(indio_dev));
+out:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * sca3300_init - Device init sequence. See datasheet rev 2 section
+ * 4.2 Start-Up Sequence for details.
+ */
+static int sca3300_init(struct sca3300_data *sca_data,
+ struct iio_dev *indio_dev)
+{
+ int value = 0;
+ int ret;
+ int i;
+
+ ret = sca3300_write_reg(sca_data, SCA3300_REG_MODE,
+ SCA3300_MODE_SW_RESET);
+ if (ret)
+ return ret;
+
+ /*
+ * Wait 1ms after SW-reset command.
+ * Wait for the settling of signal paths,
+ * 15ms for SCA3300 and 25ms for SCL3300,
+ */
+ usleep_range(26e3, 50e3);
+
+ ret = sca3300_read_reg(sca_data, SCA3300_REG_WHOAMI, &value);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(sca3300_chip_tbl); i++) {
+ if (sca3300_chip_tbl[i].chip_id == value)
+ break;
+ }
+ if (i == ARRAY_SIZE(sca3300_chip_tbl)) {
+ dev_err(&sca_data->spi->dev, "unknown chip id %x\n", value);
+ return -ENODEV;
+ }
+
+ sca_data->chip = &sca3300_chip_tbl[i];
+
+ if (sca_data->chip->angle_supported) {
+ ret = sca3300_write_reg(sca_data, SCL3300_REG_ANG_CTRL,
+ SCL3300_ANG_ENABLE);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sca3300_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval,
+ unsigned int *readval)
+{
+ struct sca3300_data *data = iio_priv(indio_dev);
+ int value;
+ int ret;
+
+ if (reg > SCA3300_REG_SELBANK)
+ return -EINVAL;
+
+ if (!readval)
+ return sca3300_write_reg(data, reg, writeval);
+
+ ret = sca3300_read_reg(data, reg, &value);
+ if (ret)
+ return ret;
+
+ *readval = value;
+
+ return 0;
+}
+
+static int sca3300_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct sca3300_data *data = iio_priv(indio_dev);
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_INCLI:
+ *vals = (const int *)data->chip->incli_scale;
+ *length = data->chip->num_incli_scales;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case IIO_ACCEL:
+ *vals = (const int *)data->chip->accel_scale;
+ *length = data->chip->num_accel_scales;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+ *vals = (const int *)data->chip->freq_table;
+ *length = data->chip->num_freqs;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info sca3300_info = {
+ .read_raw = sca3300_read_raw,
+ .write_raw = sca3300_write_raw,
+ .debugfs_reg_access = &sca3300_debugfs_reg_access,
+ .read_avail = sca3300_read_avail,
+};
+
+static int sca3300_probe(struct spi_device *spi)
+{
+ struct sca3300_data *sca_data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*sca_data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ sca_data = iio_priv(indio_dev);
+ mutex_init(&sca_data->lock);
+ sca_data->spi = spi;
+
+ crc8_populate_msb(sca3300_crc_table, SCA3300_CRC8_POLYNOMIAL);
+
+ indio_dev->info = &sca3300_info;
+
+ ret = sca3300_init(sca_data, indio_dev);
+ if (ret) {
+ dev_err(&spi->dev, "failed to init device, error: %d\n", ret);
+ return ret;
+ }
+
+ indio_dev->name = sca_data->chip->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = sca_data->chip->channels;
+ indio_dev->num_channels = sca_data->chip->num_channels;
+ indio_dev->available_scan_masks = sca_data->chip->scan_masks;
+
+ ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
+ iio_pollfunc_store_time,
+ sca3300_trigger_handler, NULL);
+ if (ret) {
+ dev_err(&spi->dev,
+ "iio triggered buffer setup failed, error: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_iio_device_register(&spi->dev, indio_dev);
+ if (ret) {
+ dev_err(&spi->dev, "iio device register failed, error: %d\n",
+ ret);
+ }
+
+ return ret;
+}
+
+static const struct of_device_id sca3300_dt_ids[] = {
+ { .compatible = "murata,sca3300"},
+ { .compatible = "murata,scl3300"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, sca3300_dt_ids);
+
+static const struct spi_device_id sca3300_ids[] = {
+ { .name = "sca3300" },
+ { .name = "scl3300" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, sca3300_ids);
+
+static struct spi_driver sca3300_driver = {
+ .driver = {
+ .name = "sca3300",
+ .of_match_table = sca3300_dt_ids,
+ },
+ .probe = sca3300_probe,
+ .id_table = sca3300_ids,
+};
+module_spi_driver(sca3300_driver);
+
+MODULE_AUTHOR("Tomas Melin <tomas.melin@vaisala.com>");
+MODULE_DESCRIPTION("Murata SCA3300 SPI Accelerometer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/ssp_accel_sensor.c b/drivers/iio/accel/ssp_accel_sensor.c
new file mode 100644
index 000000000..d1687cdd3
--- /dev/null
+++ b/drivers/iio/accel/ssp_accel_sensor.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved.
+ */
+
+#include <linux/iio/common/ssp_sensors.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include "../common/ssp_sensors/ssp_iio_sensor.h"
+
+#define SSP_CHANNEL_COUNT 3
+
+#define SSP_ACCEL_NAME "ssp-accelerometer"
+static const char ssp_accel_device_name[] = SSP_ACCEL_NAME;
+
+enum ssp_accel_3d_channel {
+ SSP_CHANNEL_SCAN_INDEX_X,
+ SSP_CHANNEL_SCAN_INDEX_Y,
+ SSP_CHANNEL_SCAN_INDEX_Z,
+ SSP_CHANNEL_SCAN_INDEX_TIME,
+};
+
+static int ssp_accel_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ u32 t;
+ struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ t = ssp_get_sensor_delay(data, SSP_ACCELEROMETER_SENSOR);
+ ssp_convert_to_freq(t, val, val2);
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int ssp_accel_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ int ret;
+ struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = ssp_convert_to_time(val, val2);
+ ret = ssp_change_delay(data, SSP_ACCELEROMETER_SENSOR, ret);
+ if (ret < 0)
+ dev_err(&indio_dev->dev, "accel sensor enable fail\n");
+
+ return ret;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info ssp_accel_iio_info = {
+ .read_raw = &ssp_accel_read_raw,
+ .write_raw = &ssp_accel_write_raw,
+};
+
+static const unsigned long ssp_accel_scan_mask[] = { 0x7, 0, };
+
+static const struct iio_chan_spec ssp_acc_channels[] = {
+ SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_X, SSP_CHANNEL_SCAN_INDEX_X),
+ SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_Y, SSP_CHANNEL_SCAN_INDEX_Y),
+ SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_Z, SSP_CHANNEL_SCAN_INDEX_Z),
+ IIO_CHAN_SOFT_TIMESTAMP(SSP_CHANNEL_SCAN_INDEX_TIME),
+};
+
+static int ssp_process_accel_data(struct iio_dev *indio_dev, void *buf,
+ int64_t timestamp)
+{
+ return ssp_common_process_data(indio_dev, buf, SSP_ACCELEROMETER_SIZE,
+ timestamp);
+}
+
+static const struct iio_buffer_setup_ops ssp_accel_buffer_ops = {
+ .postenable = &ssp_common_buffer_postenable,
+ .postdisable = &ssp_common_buffer_postdisable,
+};
+
+static int ssp_accel_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct ssp_sensor_data *spd;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*spd));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ spd = iio_priv(indio_dev);
+
+ spd->process_data = ssp_process_accel_data;
+ spd->type = SSP_ACCELEROMETER_SENSOR;
+
+ indio_dev->name = ssp_accel_device_name;
+ indio_dev->info = &ssp_accel_iio_info;
+ indio_dev->channels = ssp_acc_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ssp_acc_channels);
+ indio_dev->available_scan_masks = ssp_accel_scan_mask;
+
+ ret = devm_iio_kfifo_buffer_setup(&pdev->dev, indio_dev,
+ &ssp_accel_buffer_ops);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ ret = devm_iio_device_register(&pdev->dev, indio_dev);
+ if (ret < 0)
+ return ret;
+
+ /* ssp registering should be done after all iio setup */
+ ssp_register_consumer(indio_dev, SSP_ACCELEROMETER_SENSOR);
+
+ return 0;
+}
+
+static struct platform_driver ssp_accel_driver = {
+ .driver = {
+ .name = SSP_ACCEL_NAME,
+ },
+ .probe = ssp_accel_probe,
+};
+
+module_platform_driver(ssp_accel_driver);
+
+MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>");
+MODULE_DESCRIPTION("Samsung sensorhub accelerometers driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_SSP_SENSORS");
diff --git a/drivers/iio/accel/st_accel.h b/drivers/iio/accel/st_accel.h
new file mode 100644
index 000000000..2659f536c
--- /dev/null
+++ b/drivers/iio/accel/st_accel.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * STMicroelectronics accelerometers driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ * v. 1.0.0
+ */
+
+#ifndef ST_ACCEL_H
+#define ST_ACCEL_H
+
+#include <linux/types.h>
+#include <linux/iio/common/st_sensors.h>
+
+#define H3LIS331DL_ACCEL_DEV_NAME "h3lis331dl_accel"
+#define LIS3LV02DL_ACCEL_DEV_NAME "lis3lv02dl_accel"
+#define LSM303DLHC_ACCEL_DEV_NAME "lsm303dlhc_accel"
+#define LIS3DH_ACCEL_DEV_NAME "lis3dh"
+#define LSM330D_ACCEL_DEV_NAME "lsm330d_accel"
+#define LSM330DL_ACCEL_DEV_NAME "lsm330dl_accel"
+#define LSM330DLC_ACCEL_DEV_NAME "lsm330dlc_accel"
+#define LIS331DL_ACCEL_DEV_NAME "lis331dl_accel"
+#define LIS331DLH_ACCEL_DEV_NAME "lis331dlh"
+#define LSM303DL_ACCEL_DEV_NAME "lsm303dl_accel"
+#define LSM303DLH_ACCEL_DEV_NAME "lsm303dlh_accel"
+#define LSM303DLM_ACCEL_DEV_NAME "lsm303dlm_accel"
+#define LSM330_ACCEL_DEV_NAME "lsm330_accel"
+#define LSM303AGR_ACCEL_DEV_NAME "lsm303agr_accel"
+#define LIS2DH12_ACCEL_DEV_NAME "lis2dh12_accel"
+#define LIS3L02DQ_ACCEL_DEV_NAME "lis3l02dq"
+#define LNG2DM_ACCEL_DEV_NAME "lng2dm"
+#define LIS2DW12_ACCEL_DEV_NAME "lis2dw12"
+#define LIS3DHH_ACCEL_DEV_NAME "lis3dhh"
+#define LIS3DE_ACCEL_DEV_NAME "lis3de"
+#define LIS2DE12_ACCEL_DEV_NAME "lis2de12"
+#define LIS2DS12_ACCEL_DEV_NAME "lis2ds12"
+#define LIS2HH12_ACCEL_DEV_NAME "lis2hh12"
+#define LIS302DL_ACCEL_DEV_NAME "lis302dl"
+#define LSM303C_ACCEL_DEV_NAME "lsm303c_accel"
+#define SC7A20_ACCEL_DEV_NAME "sc7a20"
+#define IIS328DQ_ACCEL_DEV_NAME "iis328dq"
+
+
+#ifdef CONFIG_IIO_BUFFER
+int st_accel_allocate_ring(struct iio_dev *indio_dev);
+int st_accel_trig_set_state(struct iio_trigger *trig, bool state);
+#define ST_ACCEL_TRIGGER_SET_STATE (&st_accel_trig_set_state)
+#else /* CONFIG_IIO_BUFFER */
+static inline int st_accel_allocate_ring(struct iio_dev *indio_dev)
+{
+ return 0;
+}
+#define ST_ACCEL_TRIGGER_SET_STATE NULL
+#endif /* CONFIG_IIO_BUFFER */
+
+#endif /* ST_ACCEL_H */
diff --git a/drivers/iio/accel/st_accel_buffer.c b/drivers/iio/accel/st_accel_buffer.c
new file mode 100644
index 000000000..b2977ae19
--- /dev/null
+++ b/drivers/iio/accel/st_accel_buffer.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * STMicroelectronics accelerometers driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include <linux/iio/common/st_sensors.h>
+#include "st_accel.h"
+
+int st_accel_trig_set_state(struct iio_trigger *trig, bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+
+ return st_sensors_set_dataready_irq(indio_dev, state);
+}
+
+static int st_accel_buffer_postenable(struct iio_dev *indio_dev)
+{
+ int err;
+
+ err = st_sensors_set_axis_enable(indio_dev, indio_dev->active_scan_mask[0]);
+ if (err < 0)
+ return err;
+
+ err = st_sensors_set_enable(indio_dev, true);
+ if (err < 0)
+ goto st_accel_buffer_enable_all_axis;
+
+ return 0;
+
+st_accel_buffer_enable_all_axis:
+ st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS);
+ return err;
+}
+
+static int st_accel_buffer_predisable(struct iio_dev *indio_dev)
+{
+ int err;
+
+ err = st_sensors_set_enable(indio_dev, false);
+ if (err < 0)
+ return err;
+
+ return st_sensors_set_axis_enable(indio_dev,
+ ST_SENSORS_ENABLE_ALL_AXIS);
+}
+
+static const struct iio_buffer_setup_ops st_accel_buffer_setup_ops = {
+ .postenable = &st_accel_buffer_postenable,
+ .predisable = &st_accel_buffer_predisable,
+};
+
+int st_accel_allocate_ring(struct iio_dev *indio_dev)
+{
+ return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev,
+ NULL, &st_sensors_trigger_handler, &st_accel_buffer_setup_ops);
+}
diff --git a/drivers/iio/accel/st_accel_core.c b/drivers/iio/accel/st_accel_core.c
new file mode 100644
index 000000000..1a9447c81
--- /dev/null
+++ b/drivers/iio/accel/st_accel_core.c
@@ -0,0 +1,1622 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * STMicroelectronics accelerometers driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+
+#include <linux/iio/common/st_sensors.h>
+#include "st_accel.h"
+
+#define ST_ACCEL_NUMBER_DATA_CHANNELS 3
+
+/* DEFAULT VALUE FOR SENSORS */
+#define ST_ACCEL_DEFAULT_OUT_X_L_ADDR 0x28
+#define ST_ACCEL_DEFAULT_OUT_Y_L_ADDR 0x2a
+#define ST_ACCEL_DEFAULT_OUT_Z_L_ADDR 0x2c
+
+/* FULLSCALE */
+#define ST_ACCEL_FS_AVL_2G 2
+#define ST_ACCEL_FS_AVL_4G 4
+#define ST_ACCEL_FS_AVL_6G 6
+#define ST_ACCEL_FS_AVL_8G 8
+#define ST_ACCEL_FS_AVL_16G 16
+#define ST_ACCEL_FS_AVL_100G 100
+#define ST_ACCEL_FS_AVL_200G 200
+#define ST_ACCEL_FS_AVL_400G 400
+
+static const struct iio_mount_matrix *
+st_accel_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct st_sensor_data *adata = iio_priv(indio_dev);
+
+ return &adata->mount_matrix;
+}
+
+static const struct iio_chan_spec_ext_info st_accel_mount_matrix_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, st_accel_get_mount_matrix),
+ { }
+};
+
+static const struct iio_chan_spec st_accel_8bit_channels[] = {
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 8, 8,
+ ST_ACCEL_DEFAULT_OUT_X_L_ADDR+1,
+ st_accel_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 8, 8,
+ ST_ACCEL_DEFAULT_OUT_Y_L_ADDR+1,
+ st_accel_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 8, 8,
+ ST_ACCEL_DEFAULT_OUT_Z_L_ADDR+1,
+ st_accel_mount_matrix_ext_info),
+ IIO_CHAN_SOFT_TIMESTAMP(3)
+};
+
+static const struct iio_chan_spec st_accel_12bit_channels[] = {
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 12, 16,
+ ST_ACCEL_DEFAULT_OUT_X_L_ADDR,
+ st_accel_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 12, 16,
+ ST_ACCEL_DEFAULT_OUT_Y_L_ADDR,
+ st_accel_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 12, 16,
+ ST_ACCEL_DEFAULT_OUT_Z_L_ADDR,
+ st_accel_mount_matrix_ext_info),
+ IIO_CHAN_SOFT_TIMESTAMP(3)
+};
+
+static const struct iio_chan_spec st_accel_16bit_channels[] = {
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16,
+ ST_ACCEL_DEFAULT_OUT_X_L_ADDR,
+ st_accel_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16,
+ ST_ACCEL_DEFAULT_OUT_Y_L_ADDR,
+ st_accel_mount_matrix_ext_info),
+ ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL,
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16,
+ ST_ACCEL_DEFAULT_OUT_Z_L_ADDR,
+ st_accel_mount_matrix_ext_info),
+ IIO_CHAN_SOFT_TIMESTAMP(3)
+};
+
+static const struct st_sensor_settings st_accel_sensors_settings[] = {
+ {
+ .wai = 0x33,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LIS3DH_ACCEL_DEV_NAME,
+ [1] = LSM303DLHC_ACCEL_DEV_NAME,
+ [2] = LSM330D_ACCEL_DEV_NAME,
+ [3] = LSM330DL_ACCEL_DEV_NAME,
+ [4] = LSM330DLC_ACCEL_DEV_NAME,
+ [5] = LSM303AGR_ACCEL_DEV_NAME,
+ [6] = LIS2DH12_ACCEL_DEV_NAME,
+ [7] = LIS3DE_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_12bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .odr_avl = {
+ { .hz = 1, .value = 0x01, },
+ { .hz = 10, .value = 0x02, },
+ { .hz = 25, .value = 0x03, },
+ { .hz = 50, .value = 0x04, },
+ { .hz = 100, .value = 0x05, },
+ { .hz = 200, .value = 0x06, },
+ { .hz = 400, .value = 0x07, },
+ { .hz = 1600, .value = 0x08, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x23,
+ .mask = 0x30,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(1000),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_4G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(2000),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x02,
+ .gain = IIO_G_TO_M_S_2(4000),
+ },
+ [3] = {
+ .num = ST_ACCEL_FS_AVL_16G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(12000),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x23,
+ .mask = 0x80,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = 0x10,
+ },
+ .addr_ihl = 0x25,
+ .mask_ihl = 0x02,
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .sim = {
+ .addr = 0x23,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x32,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LIS331DLH_ACCEL_DEV_NAME,
+ [1] = LSM303DL_ACCEL_DEV_NAME,
+ [2] = LSM303DLH_ACCEL_DEV_NAME,
+ [3] = LSM303DLM_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_12bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0x18,
+ .odr_avl = {
+ { .hz = 50, .value = 0x00, },
+ { .hz = 100, .value = 0x01, },
+ { .hz = 400, .value = 0x02, },
+ { .hz = 1000, .value = 0x03, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0xe0,
+ .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x23,
+ .mask = 0x30,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(1000),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_4G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(2000),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(3900),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x23,
+ .mask = 0x80,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = 0x02,
+ .addr_od = 0x22,
+ .mask_od = 0x40,
+ },
+ .int2 = {
+ .addr = 0x22,
+ .mask = 0x10,
+ .addr_od = 0x22,
+ .mask_od = 0x40,
+ },
+ .addr_ihl = 0x22,
+ .mask_ihl = 0x80,
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .sim = {
+ .addr = 0x23,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x40,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LSM330_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_16bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .odr_avl = {
+ { .hz = 3, .value = 0x01, },
+ { .hz = 6, .value = 0x02, },
+ { .hz = 12, .value = 0x03, },
+ { .hz = 25, .value = 0x04, },
+ { .hz = 50, .value = 0x05, },
+ { .hz = 100, .value = 0x06, },
+ { .hz = 200, .value = 0x07, },
+ { .hz = 400, .value = 0x08, },
+ { .hz = 800, .value = 0x09, },
+ { .hz = 1600, .value = 0x0a, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x24,
+ .mask = 0x38,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(61),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_4G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(122),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_6G,
+ .value = 0x02,
+ .gain = IIO_G_TO_M_S_2(183),
+ },
+ [3] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(244),
+ },
+ [4] = {
+ .num = ST_ACCEL_FS_AVL_16G,
+ .value = 0x04,
+ .gain = IIO_G_TO_M_S_2(732),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x20,
+ .mask = 0x08,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x23,
+ .mask = 0x80,
+ },
+ .addr_ihl = 0x23,
+ .mask_ihl = 0x40,
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ .ig1 = {
+ .en_addr = 0x23,
+ .en_mask = 0x08,
+ },
+ },
+ .sim = {
+ .addr = 0x24,
+ .value = BIT(0),
+ },
+ .multi_read_bit = false,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x3a,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LIS3LV02DL_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_12bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0x30, /* DF1 and DF0 */
+ .odr_avl = {
+ { .hz = 40, .value = 0x00, },
+ { .hz = 160, .value = 0x01, },
+ { .hz = 640, .value = 0x02, },
+ { .hz = 2560, .value = 0x03, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0xc0,
+ .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x21,
+ .mask = 0x80,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(1000),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_6G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(3000),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x21,
+ .mask = 0x40,
+ },
+ /*
+ * Data Alignment Setting - needs to be set to get
+ * left-justified data like all other sensors.
+ */
+ .das = {
+ .addr = 0x21,
+ .mask = 0x01,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x21,
+ .mask = 0x04,
+ },
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .sim = {
+ .addr = 0x21,
+ .value = BIT(1),
+ },
+ .multi_read_bit = true,
+ .bootime = 2, /* guess */
+ },
+ {
+ .wai = 0x3b,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LIS331DL_ACCEL_DEV_NAME,
+ [1] = LIS302DL_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_8bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0x80,
+ .odr_avl = {
+ { .hz = 100, .value = 0x00, },
+ { .hz = 400, .value = 0x01, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0x40,
+ .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x20,
+ .mask = 0x20,
+ /*
+ * TODO: check these resulting gain settings, these are
+ * not in the datsheet
+ */
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(18000),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(72000),
+ },
+ },
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = 0x04,
+ .addr_od = 0x22,
+ .mask_od = 0x40,
+ },
+ .int2 = {
+ .addr = 0x22,
+ .mask = 0x20,
+ .addr_od = 0x22,
+ .mask_od = 0x40,
+ },
+ .addr_ihl = 0x22,
+ .mask_ihl = 0x80,
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .sim = {
+ .addr = 0x21,
+ .value = BIT(7),
+ },
+ .multi_read_bit = false,
+ .bootime = 2, /* guess */
+ },
+ {
+ .wai = 0x32,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = H3LIS331DL_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_12bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0x18,
+ .odr_avl = {
+ { .hz = 50, .value = 0x00, },
+ { .hz = 100, .value = 0x01, },
+ { .hz = 400, .value = 0x02, },
+ { .hz = 1000, .value = 0x03, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0x20,
+ .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x23,
+ .mask = 0x30,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_100G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(49000),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_200G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(98000),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_400G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(195000),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x23,
+ .mask = 0x80,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = 0x02,
+ },
+ .int2 = {
+ .addr = 0x22,
+ .mask = 0x10,
+ },
+ .addr_ihl = 0x22,
+ .mask_ihl = 0x80,
+ },
+ .sim = {
+ .addr = 0x23,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x32,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = IIS328DQ_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_12bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0x18,
+ .odr_avl = {
+ { .hz = 50, .value = 0x00, },
+ { .hz = 100, .value = 0x01, },
+ { .hz = 400, .value = 0x02, },
+ { .hz = 1000, .value = 0x03, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0x20,
+ .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x23,
+ .mask = 0x30,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_100G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(980),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_200G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(1950),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_400G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(3910),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x23,
+ .mask = 0x80,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = 0x02,
+ },
+ .int2 = {
+ .addr = 0x22,
+ .mask = 0x10,
+ },
+ .addr_ihl = 0x22,
+ .mask_ihl = 0x80,
+ },
+ .sim = {
+ .addr = 0x23,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+ {
+ /* No WAI register present */
+ .sensors_supported = {
+ [0] = LIS3L02DQ_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_12bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0x30,
+ .odr_avl = {
+ { .hz = 280, .value = 0x00, },
+ { .hz = 560, .value = 0x01, },
+ { .hz = 1120, .value = 0x02, },
+ { .hz = 4480, .value = 0x03, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0xc0,
+ .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .gain = IIO_G_TO_M_S_2(488),
+ },
+ },
+ },
+ /*
+ * The part has a BDU bit but if set the data is never
+ * updated so don't set it.
+ */
+ .bdu = {
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x21,
+ .mask = 0x04,
+ },
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .sim = {
+ .addr = 0x21,
+ .value = BIT(1),
+ },
+ .multi_read_bit = false,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x33,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LNG2DM_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_8bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .odr_avl = {
+ { .hz = 1, .value = 0x01, },
+ { .hz = 10, .value = 0x02, },
+ { .hz = 25, .value = 0x03, },
+ { .hz = 50, .value = 0x04, },
+ { .hz = 100, .value = 0x05, },
+ { .hz = 200, .value = 0x06, },
+ { .hz = 400, .value = 0x07, },
+ { .hz = 1600, .value = 0x08, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x23,
+ .mask = 0x30,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(15600),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_4G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(31200),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x02,
+ .gain = IIO_G_TO_M_S_2(62500),
+ },
+ [3] = {
+ .num = ST_ACCEL_FS_AVL_16G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(187500),
+ },
+ },
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = 0x10,
+ },
+ .addr_ihl = 0x25,
+ .mask_ihl = 0x02,
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .sim = {
+ .addr = 0x23,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x44,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LIS2DW12_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_12bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .odr_avl = {
+ { .hz = 1, .value = 0x01, },
+ { .hz = 12, .value = 0x02, },
+ { .hz = 25, .value = 0x03, },
+ { .hz = 50, .value = 0x04, },
+ { .hz = 100, .value = 0x05, },
+ { .hz = 200, .value = 0x06, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .fs = {
+ .addr = 0x25,
+ .mask = 0x30,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(976),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_4G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(1952),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x02,
+ .gain = IIO_G_TO_M_S_2(3904),
+ },
+ [3] = {
+ .num = ST_ACCEL_FS_AVL_16G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(7808),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x21,
+ .mask = 0x08,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x23,
+ .mask = 0x01,
+ .addr_od = 0x22,
+ .mask_od = 0x20,
+ },
+ .int2 = {
+ .addr = 0x24,
+ .mask = 0x01,
+ .addr_od = 0x22,
+ .mask_od = 0x20,
+ },
+ .addr_ihl = 0x22,
+ .mask_ihl = 0x08,
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x01,
+ },
+ },
+ .sim = {
+ .addr = 0x21,
+ .value = BIT(0),
+ },
+ .multi_read_bit = false,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x11,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LIS3DHH_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_16bit_channels,
+ .odr = {
+ /* just ODR = 1100Hz available */
+ .odr_avl = {
+ { .hz = 1100, .value = 0x00, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0x80,
+ .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .fs = {
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .gain = IIO_G_TO_M_S_2(76),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x20,
+ .mask = 0x01,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x21,
+ .mask = 0x80,
+ .addr_od = 0x23,
+ .mask_od = 0x04,
+ },
+ .int2 = {
+ .addr = 0x22,
+ .mask = 0x80,
+ .addr_od = 0x23,
+ .mask_od = 0x08,
+ },
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .multi_read_bit = false,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x33,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LIS2DE12_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_8bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .odr_avl = {
+ { .hz = 1, .value = 0x01, },
+ { .hz = 10, .value = 0x02, },
+ { .hz = 25, .value = 0x03, },
+ { .hz = 50, .value = 0x04, },
+ { .hz = 100, .value = 0x05, },
+ { .hz = 200, .value = 0x06, },
+ { .hz = 400, .value = 0x07, },
+ { .hz = 1620, .value = 0x08, },
+ { .hz = 5376, .value = 0x09, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x23,
+ .mask = 0x30,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(15600),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_4G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(31200),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x02,
+ .gain = IIO_G_TO_M_S_2(62500),
+ },
+ [3] = {
+ .num = ST_ACCEL_FS_AVL_16G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(187500),
+ },
+ },
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = 0x10,
+ },
+ .addr_ihl = 0x25,
+ .mask_ihl = 0x02,
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .sim = {
+ .addr = 0x23,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x43,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LIS2DS12_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_16bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .odr_avl = {
+ { .hz = 10, .value = 0x01, },
+ { .hz = 50, .value = 0x02, },
+ { .hz = 100, .value = 0x03, },
+ { .hz = 200, .value = 0x04, },
+ { .hz = 400, .value = 0x05, },
+ { .hz = 800, .value = 0x06, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x20,
+ .mask = 0x0c,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(61),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_4G,
+ .value = 0x02,
+ .gain = IIO_G_TO_M_S_2(122),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(244),
+ },
+ [3] = {
+ .num = ST_ACCEL_FS_AVL_16G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(488),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x20,
+ .mask = 0x01,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x23,
+ .mask = 0x01,
+ },
+ .int2 = {
+ .addr = 0x24,
+ .mask = 0x01,
+ },
+ .addr_ihl = 0x22,
+ .mask_ihl = 0x02,
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x01,
+ },
+ },
+ .sim = {
+ .addr = 0x21,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x41,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LIS2HH12_ACCEL_DEV_NAME,
+ [1] = LSM303C_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_16bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0x70,
+ .odr_avl = {
+ { .hz = 10, .value = 0x01, },
+ { .hz = 50, .value = 0x02, },
+ { .hz = 100, .value = 0x03, },
+ { .hz = 200, .value = 0x04, },
+ { .hz = 400, .value = 0x05, },
+ { .hz = 800, .value = 0x06, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0x70,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x23,
+ .mask = 0x30,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(61),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_4G,
+ .value = 0x02,
+ .gain = IIO_G_TO_M_S_2(122),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(244),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x20,
+ .mask = 0x08,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = 0x01,
+ },
+ .int2 = {
+ .addr = 0x25,
+ .mask = 0x01,
+ },
+ .addr_ihl = 0x24,
+ .mask_ihl = 0x02,
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .sim = {
+ .addr = 0x23,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+ {
+ .wai = 0x49,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LSM9DS0_IMU_DEV_NAME,
+ [1] = LSM303D_IMU_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_16bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = GENMASK(7, 4),
+ .odr_avl = {
+ { 3, 0x01, },
+ { 6, 0x02, },
+ { 12, 0x03, },
+ { 25, 0x04, },
+ { 50, 0x05, },
+ { 100, 0x06, },
+ { 200, 0x07, },
+ { 400, 0x08, },
+ { 800, 0x09, },
+ { 1600, 0x0a, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = GENMASK(7, 4),
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x21,
+ .mask = GENMASK(5, 3),
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(61),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_4G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(122),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_6G,
+ .value = 0x02,
+ .gain = IIO_G_TO_M_S_2(183),
+ },
+ [3] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(244),
+ },
+ [4] = {
+ .num = ST_ACCEL_FS_AVL_16G,
+ .value = 0x04,
+ .gain = IIO_G_TO_M_S_2(732),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x20,
+ .mask = BIT(3),
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = BIT(2),
+ },
+ .int2 = {
+ .addr = 0x23,
+ .mask = BIT(3),
+ },
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = GENMASK(2, 0),
+ },
+ },
+ .sim = {
+ .addr = 0x21,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+ {
+ /*
+ * Not an ST part. Register-compatible with the LIS2DH, even
+ * though the WAI value is different.
+ */
+ .wai = 0x11,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = SC7A20_ACCEL_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_accel_12bit_channels,
+ .odr = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .odr_avl = {
+ { .hz = 1, .value = 0x01, },
+ { .hz = 10, .value = 0x02, },
+ { .hz = 25, .value = 0x03, },
+ { .hz = 50, .value = 0x04, },
+ { .hz = 100, .value = 0x05, },
+ { .hz = 200, .value = 0x06, },
+ { .hz = 400, .value = 0x07, },
+ { .hz = 1600, .value = 0x08, },
+ },
+ },
+ .pw = {
+ .addr = 0x20,
+ .mask = 0xf0,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .enable_axis = {
+ .addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
+ .mask = ST_SENSORS_DEFAULT_AXIS_MASK,
+ },
+ .fs = {
+ .addr = 0x23,
+ .mask = 0x30,
+ .fs_avl = {
+ [0] = {
+ .num = ST_ACCEL_FS_AVL_2G,
+ .value = 0x00,
+ .gain = IIO_G_TO_M_S_2(1000),
+ },
+ [1] = {
+ .num = ST_ACCEL_FS_AVL_4G,
+ .value = 0x01,
+ .gain = IIO_G_TO_M_S_2(2000),
+ },
+ [2] = {
+ .num = ST_ACCEL_FS_AVL_8G,
+ .value = 0x02,
+ .gain = IIO_G_TO_M_S_2(4000),
+ },
+ [3] = {
+ .num = ST_ACCEL_FS_AVL_16G,
+ .value = 0x03,
+ .gain = IIO_G_TO_M_S_2(12000),
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x23,
+ .mask = 0x80,
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x22,
+ .mask = 0x10,
+ },
+ .addr_ihl = 0x25,
+ .mask_ihl = 0x02,
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x07,
+ },
+ },
+ .sim = {
+ .addr = 0x23,
+ .value = BIT(0),
+ },
+ .multi_read_bit = true,
+ .bootime = 2,
+ },
+};
+
+/* Default accel DRDY is available on INT1 pin */
+static const struct st_sensors_platform_data default_accel_pdata = {
+ .drdy_int_pin = 1,
+};
+
+static int st_accel_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *ch, int *val,
+ int *val2, long mask)
+{
+ int err;
+ struct st_sensor_data *adata = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ err = st_sensors_read_info_raw(indio_dev, ch, val);
+ if (err < 0)
+ goto read_error;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = adata->current_fullscale->gain / 1000000;
+ *val2 = adata->current_fullscale->gain % 1000000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = adata->odr;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+
+read_error:
+ return err;
+}
+
+static int st_accel_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val, int val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE: {
+ int gain;
+
+ gain = val * 1000000 + val2;
+ return st_sensors_set_fullscale_by_gain(indio_dev, gain);
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (val2)
+ return -EINVAL;
+
+ return st_sensors_set_odr(indio_dev, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL();
+static ST_SENSORS_DEV_ATTR_SCALE_AVAIL(in_accel_scale_available);
+
+static struct attribute *st_accel_attributes[] = {
+ &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+ &iio_dev_attr_in_accel_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group st_accel_attribute_group = {
+ .attrs = st_accel_attributes,
+};
+
+static const struct iio_info accel_info = {
+ .attrs = &st_accel_attribute_group,
+ .read_raw = &st_accel_read_raw,
+ .write_raw = &st_accel_write_raw,
+ .debugfs_reg_access = &st_sensors_debugfs_reg_access,
+};
+
+#ifdef CONFIG_IIO_TRIGGER
+static const struct iio_trigger_ops st_accel_trigger_ops = {
+ .set_trigger_state = ST_ACCEL_TRIGGER_SET_STATE,
+ .validate_device = st_sensors_validate_device,
+};
+#define ST_ACCEL_TRIGGER_OPS (&st_accel_trigger_ops)
+#else
+#define ST_ACCEL_TRIGGER_OPS NULL
+#endif
+
+#ifdef CONFIG_ACPI
+/* Read ST-specific _ONT orientation data from ACPI and generate an
+ * appropriate mount matrix.
+ */
+static int apply_acpi_orientation(struct iio_dev *indio_dev)
+{
+ struct st_sensor_data *adata = iio_priv(indio_dev);
+ struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+ struct acpi_device *adev;
+ union acpi_object *ont;
+ union acpi_object *elements;
+ acpi_status status;
+ struct device *parent = indio_dev->dev.parent;
+ int ret = -EINVAL;
+ unsigned int val;
+ int i, j;
+ int final_ont[3][3] = { { 0 }, };
+
+ /* For some reason, ST's _ONT translation does not apply directly
+ * to the data read from the sensor. Another translation must be
+ * performed first, as described by the matrix below. Perhaps
+ * ST required this specific translation for the first product
+ * where the device was mounted?
+ */
+ const int default_ont[3][3] = {
+ { 0, 1, 0 },
+ { -1, 0, 0 },
+ { 0, 0, -1 },
+ };
+
+
+ adev = ACPI_COMPANION(parent);
+ if (!adev)
+ return -ENXIO;
+
+ /* Read _ONT data, which should be a package of 6 integers. */
+ status = acpi_evaluate_object(adev->handle, "_ONT", NULL, &buffer);
+ if (status == AE_NOT_FOUND) {
+ return -ENXIO;
+ } else if (ACPI_FAILURE(status)) {
+ dev_warn(parent, "failed to execute _ONT: %d\n", status);
+ return status;
+ }
+
+ ont = buffer.pointer;
+ if (ont->type != ACPI_TYPE_PACKAGE || ont->package.count != 6)
+ goto out;
+
+ /* The first 3 integers provide axis order information.
+ * e.g. 0 1 2 would indicate normal X,Y,Z ordering.
+ * e.g. 1 0 2 indicates that data arrives in order Y,X,Z.
+ */
+ elements = ont->package.elements;
+ for (i = 0; i < 3; i++) {
+ if (elements[i].type != ACPI_TYPE_INTEGER)
+ goto out;
+
+ val = elements[i].integer.value;
+ if (val > 2)
+ goto out;
+
+ /* Avoiding full matrix multiplication, we simply reorder the
+ * columns in the default_ont matrix according to the
+ * ordering provided by _ONT.
+ */
+ final_ont[0][i] = default_ont[0][val];
+ final_ont[1][i] = default_ont[1][val];
+ final_ont[2][i] = default_ont[2][val];
+ }
+
+ /* The final 3 integers provide sign flip information.
+ * 0 means no change, 1 means flip.
+ * e.g. 0 0 1 means that Z data should be sign-flipped.
+ * This is applied after the axis reordering from above.
+ */
+ elements += 3;
+ for (i = 0; i < 3; i++) {
+ if (elements[i].type != ACPI_TYPE_INTEGER)
+ goto out;
+
+ val = elements[i].integer.value;
+ if (val != 0 && val != 1)
+ goto out;
+ if (!val)
+ continue;
+
+ /* Flip the values in the indicated column */
+ final_ont[0][i] *= -1;
+ final_ont[1][i] *= -1;
+ final_ont[2][i] *= -1;
+ }
+
+ /* Convert our integer matrix to a string-based iio_mount_matrix */
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++) {
+ int matrix_val = final_ont[i][j];
+ char *str_value;
+
+ switch (matrix_val) {
+ case -1:
+ str_value = "-1";
+ break;
+ case 0:
+ str_value = "0";
+ break;
+ case 1:
+ str_value = "1";
+ break;
+ default:
+ goto out;
+ }
+ adata->mount_matrix.rotation[i * 3 + j] = str_value;
+ }
+ }
+
+ ret = 0;
+ dev_info(parent, "computed mount matrix from ACPI\n");
+
+out:
+ kfree(buffer.pointer);
+ if (ret)
+ dev_dbg(parent,
+ "failed to apply ACPI orientation data: %d\n", ret);
+
+ return ret;
+}
+#else /* !CONFIG_ACPI */
+static int apply_acpi_orientation(struct iio_dev *indio_dev)
+{
+ return -EINVAL;
+}
+#endif
+
+/*
+ * st_accel_get_settings() - get sensor settings from device name
+ * @name: device name buffer reference.
+ *
+ * Return: valid reference on success, NULL otherwise.
+ */
+const struct st_sensor_settings *st_accel_get_settings(const char *name)
+{
+ int index = st_sensors_get_settings_index(name,
+ st_accel_sensors_settings,
+ ARRAY_SIZE(st_accel_sensors_settings));
+ if (index < 0)
+ return NULL;
+
+ return &st_accel_sensors_settings[index];
+}
+EXPORT_SYMBOL_NS(st_accel_get_settings, "IIO_ST_SENSORS");
+
+int st_accel_common_probe(struct iio_dev *indio_dev)
+{
+ struct st_sensor_data *adata = iio_priv(indio_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;
+ indio_dev->info = &accel_info;
+
+ err = st_sensors_verify_id(indio_dev);
+ if (err < 0)
+ return err;
+
+ adata->num_data_channels = ST_ACCEL_NUMBER_DATA_CHANNELS;
+ indio_dev->channels = adata->sensor_settings->ch;
+ indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS;
+
+ /*
+ * First try specific ACPI methods to retrieve orientation then try the
+ * generic function.
+ */
+ err = apply_acpi_orientation(indio_dev);
+ if (err) {
+ err = iio_read_mount_matrix(parent, &adata->mount_matrix);
+ if (err)
+ return err;
+ }
+
+ adata->current_fullscale = &adata->sensor_settings->fs.fs_avl[0];
+ adata->odr = adata->sensor_settings->odr.odr_avl[0].hz;
+
+ if (!pdata)
+ pdata = (struct st_sensors_platform_data *)&default_accel_pdata;
+
+ err = st_sensors_init_sensor(indio_dev, pdata);
+ if (err < 0)
+ return err;
+
+ err = st_accel_allocate_ring(indio_dev);
+ if (err < 0)
+ return err;
+
+ if (adata->irq > 0) {
+ err = st_sensors_allocate_trigger(indio_dev,
+ ST_ACCEL_TRIGGER_OPS);
+ if (err < 0)
+ return err;
+ }
+
+ return devm_iio_device_register(parent, indio_dev);
+}
+EXPORT_SYMBOL_NS(st_accel_common_probe, "IIO_ST_SENSORS");
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics accelerometers driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_ST_SENSORS");
diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c
new file mode 100644
index 000000000..99522a885
--- /dev/null
+++ b/drivers/iio/accel/st_accel_i2c.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * STMicroelectronics accelerometers driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+
+#include <linux/iio/common/st_sensors_i2c.h>
+#include "st_accel.h"
+
+static const struct of_device_id st_accel_of_match[] = {
+ {
+ /* An older compatible */
+ .compatible = "st,lis3lv02d",
+ .data = LIS3LV02DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis3lv02dl-accel",
+ .data = LIS3LV02DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303dlh-accel",
+ .data = LSM303DLH_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303dlhc-accel",
+ .data = LSM303DLHC_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis3dh-accel",
+ .data = LIS3DH_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm330d-accel",
+ .data = LSM330D_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm330dl-accel",
+ .data = LSM330DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm330dlc-accel",
+ .data = LSM330DLC_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis331dl-accel",
+ .data = LIS331DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis331dlh-accel",
+ .data = LIS331DLH_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303dl-accel",
+ .data = LSM303DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303dlm-accel",
+ .data = LSM303DLM_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm330-accel",
+ .data = LSM330_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303agr-accel",
+ .data = LSM303AGR_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis2dh12-accel",
+ .data = LIS2DH12_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,h3lis331dl-accel",
+ .data = H3LIS331DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis3l02dq",
+ .data = LIS3L02DQ_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lng2dm-accel",
+ .data = LNG2DM_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis2dw12",
+ .data = LIS2DW12_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis3de",
+ .data = LIS3DE_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis2de12",
+ .data = LIS2DE12_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis2ds12",
+ .data = LIS2DS12_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis2hh12",
+ .data = LIS2HH12_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis302dl",
+ .data = LIS302DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303c-accel",
+ .data = LSM303C_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "silan,sc7a20",
+ .data = SC7A20_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,iis328dq",
+ .data = IIS328DQ_ACCEL_DEV_NAME,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, st_accel_of_match);
+
+static const struct acpi_device_id st_accel_acpi_match[] = {
+ {"SMO8840", (kernel_ulong_t)LIS2DH12_ACCEL_DEV_NAME},
+ {"SMO8A90", (kernel_ulong_t)LNG2DM_ACCEL_DEV_NAME},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, st_accel_acpi_match);
+
+static const struct i2c_device_id st_accel_id_table[] = {
+ { .name = LSM303DLH_ACCEL_DEV_NAME },
+ { .name = LSM303DLHC_ACCEL_DEV_NAME },
+ { .name = LIS3DH_ACCEL_DEV_NAME },
+ { .name = LSM330D_ACCEL_DEV_NAME },
+ { .name = LSM330DL_ACCEL_DEV_NAME },
+ { .name = LSM330DLC_ACCEL_DEV_NAME },
+ { .name = LIS331DLH_ACCEL_DEV_NAME },
+ { .name = LSM303DL_ACCEL_DEV_NAME },
+ { .name = LSM303DLM_ACCEL_DEV_NAME },
+ { .name = LSM330_ACCEL_DEV_NAME },
+ { .name = LSM303AGR_ACCEL_DEV_NAME },
+ { .name = LIS2DH12_ACCEL_DEV_NAME },
+ { .name = LIS3L02DQ_ACCEL_DEV_NAME },
+ { .name = LNG2DM_ACCEL_DEV_NAME },
+ { .name = H3LIS331DL_ACCEL_DEV_NAME },
+ { .name = LIS331DL_ACCEL_DEV_NAME },
+ { .name = LIS3LV02DL_ACCEL_DEV_NAME },
+ { .name = LIS2DW12_ACCEL_DEV_NAME },
+ { .name = LIS3DE_ACCEL_DEV_NAME },
+ { .name = LIS2DE12_ACCEL_DEV_NAME },
+ { .name = LIS2DS12_ACCEL_DEV_NAME },
+ { .name = LIS2HH12_ACCEL_DEV_NAME },
+ { .name = LIS302DL_ACCEL_DEV_NAME },
+ { .name = LSM303C_ACCEL_DEV_NAME },
+ { .name = SC7A20_ACCEL_DEV_NAME },
+ { .name = IIS328DQ_ACCEL_DEV_NAME },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, st_accel_id_table);
+
+static int st_accel_i2c_probe(struct i2c_client *client)
+{
+ const struct st_sensor_settings *settings;
+ struct st_sensor_data *adata;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ st_sensors_dev_name_probe(&client->dev, client->name, sizeof(client->name));
+
+ settings = st_accel_get_settings(client->name);
+ if (!settings) {
+ dev_err(&client->dev, "device name %s not recognized.\n",
+ client->name);
+ return -ENODEV;
+ }
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adata));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ adata = iio_priv(indio_dev);
+ adata->sensor_settings = (struct st_sensor_settings *)settings;
+
+ ret = st_sensors_i2c_configure(indio_dev, client);
+ if (ret < 0)
+ return ret;
+
+ ret = st_sensors_power_enable(indio_dev);
+ if (ret)
+ return ret;
+
+ return st_accel_common_probe(indio_dev);
+}
+
+static struct i2c_driver st_accel_driver = {
+ .driver = {
+ .name = "st-accel-i2c",
+ .of_match_table = st_accel_of_match,
+ .acpi_match_table = st_accel_acpi_match,
+ },
+ .probe = st_accel_i2c_probe,
+ .id_table = st_accel_id_table,
+};
+module_i2c_driver(st_accel_driver);
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics accelerometers i2c driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_ST_SENSORS");
diff --git a/drivers/iio/accel/st_accel_spi.c b/drivers/iio/accel/st_accel_spi.c
new file mode 100644
index 000000000..171f241c0
--- /dev/null
+++ b/drivers/iio/accel/st_accel_spi.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * STMicroelectronics accelerometers driver
+ *
+ * Copyright 2012-2013 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/iio/iio.h>
+
+#include <linux/iio/common/st_sensors.h>
+#include <linux/iio/common/st_sensors_spi.h>
+#include "st_accel.h"
+
+/*
+ * For new single-chip sensors use <device_name> as compatible string.
+ * For old single-chip devices keep <device_name>-accel to maintain
+ * compatibility
+ */
+static const struct of_device_id st_accel_of_match[] = {
+ {
+ /* An older compatible */
+ .compatible = "st,lis302dl-spi",
+ .data = LIS3LV02DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis3lv02dl-accel",
+ .data = LIS3LV02DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis3dh-accel",
+ .data = LIS3DH_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm330d-accel",
+ .data = LSM330D_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm330dl-accel",
+ .data = LSM330DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm330dlc-accel",
+ .data = LSM330DLC_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis331dlh-accel",
+ .data = LIS331DLH_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm330-accel",
+ .data = LSM330_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303agr-accel",
+ .data = LSM303AGR_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis2dh12-accel",
+ .data = LIS2DH12_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis2ds12",
+ .data = LIS2DS12_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis3l02dq",
+ .data = LIS3L02DQ_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lng2dm-accel",
+ .data = LNG2DM_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,h3lis331dl-accel",
+ .data = H3LIS331DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis331dl-accel",
+ .data = LIS331DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis2dw12",
+ .data = LIS2DW12_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis3dhh",
+ .data = LIS3DHH_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis3de",
+ .data = LIS3DE_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lis302dl",
+ .data = LIS302DL_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,lsm303c-accel",
+ .data = LSM303C_ACCEL_DEV_NAME,
+ },
+ {
+ .compatible = "st,iis328dq",
+ .data = IIS328DQ_ACCEL_DEV_NAME,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, st_accel_of_match);
+
+static int st_accel_spi_probe(struct spi_device *spi)
+{
+ const struct st_sensor_settings *settings;
+ struct st_sensor_data *adata;
+ struct iio_dev *indio_dev;
+ int err;
+
+ st_sensors_dev_name_probe(&spi->dev, spi->modalias, sizeof(spi->modalias));
+
+ settings = st_accel_get_settings(spi->modalias);
+ if (!settings) {
+ dev_err(&spi->dev, "device name %s not recognized.\n",
+ spi->modalias);
+ return -ENODEV;
+ }
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adata));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ adata = iio_priv(indio_dev);
+ adata->sensor_settings = (struct st_sensor_settings *)settings;
+
+ err = st_sensors_spi_configure(indio_dev, spi);
+ if (err < 0)
+ return err;
+
+ err = st_sensors_power_enable(indio_dev);
+ if (err)
+ return err;
+
+ return st_accel_common_probe(indio_dev);
+}
+
+static const struct spi_device_id st_accel_id_table[] = {
+ { .name = LIS3DH_ACCEL_DEV_NAME },
+ { .name = LSM330D_ACCEL_DEV_NAME },
+ { .name = LSM330DL_ACCEL_DEV_NAME },
+ { .name = LSM330DLC_ACCEL_DEV_NAME },
+ { .name = LIS331DLH_ACCEL_DEV_NAME },
+ { .name = LSM330_ACCEL_DEV_NAME },
+ { .name = LSM303AGR_ACCEL_DEV_NAME },
+ { .name = LIS2DH12_ACCEL_DEV_NAME },
+ { .name = LIS2DS12_ACCEL_DEV_NAME },
+ { .name = LIS3L02DQ_ACCEL_DEV_NAME },
+ { .name = LNG2DM_ACCEL_DEV_NAME },
+ { .name = H3LIS331DL_ACCEL_DEV_NAME },
+ { .name = LIS331DL_ACCEL_DEV_NAME },
+ { .name = LIS3LV02DL_ACCEL_DEV_NAME },
+ { .name = LIS2DW12_ACCEL_DEV_NAME },
+ { .name = LIS3DHH_ACCEL_DEV_NAME },
+ { .name = LIS3DE_ACCEL_DEV_NAME },
+ { .name = LIS302DL_ACCEL_DEV_NAME },
+ { .name = LSM303C_ACCEL_DEV_NAME },
+ { .name = IIS328DQ_ACCEL_DEV_NAME },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, st_accel_id_table);
+
+static struct spi_driver st_accel_driver = {
+ .driver = {
+ .name = "st-accel-spi",
+ .of_match_table = st_accel_of_match,
+ },
+ .probe = st_accel_spi_probe,
+ .id_table = st_accel_id_table,
+};
+module_spi_driver(st_accel_driver);
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics accelerometers spi driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_ST_SENSORS");
diff --git a/drivers/iio/accel/stk8312.c b/drivers/iio/accel/stk8312.c
new file mode 100644
index 000000000..50840c344
--- /dev/null
+++ b/drivers/iio/accel/stk8312.c
@@ -0,0 +1,658 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sensortek STK8312 3-Axis Accelerometer
+ *
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * IIO driver for STK8312; 7-bit I2C address: 0x3D.
+ */
+
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#define STK8312_REG_XOUT 0x00
+#define STK8312_REG_YOUT 0x01
+#define STK8312_REG_ZOUT 0x02
+#define STK8312_REG_INTSU 0x06
+#define STK8312_REG_MODE 0x07
+#define STK8312_REG_SR 0x08
+#define STK8312_REG_STH 0x13
+#define STK8312_REG_RESET 0x20
+#define STK8312_REG_AFECTRL 0x24
+#define STK8312_REG_OTPADDR 0x3D
+#define STK8312_REG_OTPDATA 0x3E
+#define STK8312_REG_OTPCTRL 0x3F
+
+#define STK8312_MODE_ACTIVE BIT(0)
+#define STK8312_MODE_STANDBY 0x00
+#define STK8312_MODE_INT_AH_PP 0xC0 /* active-high, push-pull */
+#define STK8312_DREADY_BIT BIT(4)
+#define STK8312_RNG_6G 1
+#define STK8312_RNG_SHIFT 6
+#define STK8312_RNG_MASK GENMASK(7, 6)
+#define STK8312_SR_MASK GENMASK(2, 0)
+#define STK8312_SR_400HZ_IDX 0
+#define STK8312_ALL_CHANNEL_MASK GENMASK(2, 0)
+#define STK8312_ALL_CHANNEL_SIZE 3
+
+#define STK8312_DRIVER_NAME "stk8312"
+
+/*
+ * The accelerometer has two measurement ranges:
+ *
+ * -6g - +6g (8-bit, signed)
+ * -16g - +16g (8-bit, signed)
+ *
+ * scale1 = (6 + 6) * 9.81 / (2^8 - 1) = 0.4616
+ * scale2 = (16 + 16) * 9.81 / (2^8 - 1) = 1.2311
+ */
+#define STK8312_SCALE_AVAIL "0.4616 1.2311"
+
+static const int stk8312_scale_table[][2] = {
+ {0, 461600}, {1, 231100}
+};
+
+static const struct {
+ int val;
+ int val2;
+} stk8312_samp_freq_table[] = {
+ {400, 0}, {200, 0}, {100, 0}, {50, 0}, {25, 0},
+ {12, 500000}, {6, 250000}, {3, 125000}
+};
+
+#define STK8312_ACCEL_CHANNEL(index, reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 8, \
+ .storagebits = 8, \
+ .endianness = IIO_CPU, \
+ }, \
+}
+
+static const struct iio_chan_spec stk8312_channels[] = {
+ STK8312_ACCEL_CHANNEL(0, STK8312_REG_XOUT, X),
+ STK8312_ACCEL_CHANNEL(1, STK8312_REG_YOUT, Y),
+ STK8312_ACCEL_CHANNEL(2, STK8312_REG_ZOUT, Z),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+struct stk8312_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ u8 range;
+ u8 sample_rate_idx;
+ u8 mode;
+ struct iio_trigger *dready_trig;
+ bool dready_trigger_on;
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ s8 chans[3];
+ aligned_s64 timestamp;
+ } scan;
+};
+
+static IIO_CONST_ATTR(in_accel_scale_available, STK8312_SCALE_AVAIL);
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("3.125 6.25 12.5 25 50 100 200 400");
+
+static struct attribute *stk8312_attributes[] = {
+ &iio_const_attr_in_accel_scale_available.dev_attr.attr,
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group stk8312_attribute_group = {
+ .attrs = stk8312_attributes
+};
+
+static int stk8312_otp_init(struct stk8312_data *data)
+{
+ int ret;
+ int count = 10;
+ struct i2c_client *client = data->client;
+
+ ret = i2c_smbus_write_byte_data(client, STK8312_REG_OTPADDR, 0x70);
+ if (ret < 0)
+ goto exit_err;
+ ret = i2c_smbus_write_byte_data(client, STK8312_REG_OTPCTRL, 0x02);
+ if (ret < 0)
+ goto exit_err;
+
+ do {
+ usleep_range(1000, 5000);
+ ret = i2c_smbus_read_byte_data(client, STK8312_REG_OTPCTRL);
+ if (ret < 0)
+ goto exit_err;
+ count--;
+ } while (!(ret & BIT(7)) && count > 0);
+
+ if (count == 0) {
+ ret = -ETIMEDOUT;
+ goto exit_err;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, STK8312_REG_OTPDATA);
+ if (ret == 0)
+ ret = -EINVAL;
+ if (ret < 0)
+ goto exit_err;
+
+ ret = i2c_smbus_write_byte_data(data->client, STK8312_REG_AFECTRL, ret);
+ if (ret < 0)
+ goto exit_err;
+ msleep(150);
+
+ return 0;
+
+exit_err:
+ dev_err(&client->dev, "failed to initialize sensor\n");
+ return ret;
+}
+
+static int stk8312_set_mode(struct stk8312_data *data, u8 mode)
+{
+ int ret;
+ struct i2c_client *client = data->client;
+
+ if (mode == data->mode)
+ return 0;
+
+ ret = i2c_smbus_write_byte_data(client, STK8312_REG_MODE, mode);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to change sensor mode\n");
+ return ret;
+ }
+
+ data->mode = mode;
+ if (mode & STK8312_MODE_ACTIVE) {
+ /* Need to run OTP sequence before entering active mode */
+ usleep_range(1000, 5000);
+ ret = stk8312_otp_init(data);
+ }
+
+ return ret;
+}
+
+static int stk8312_set_interrupts(struct stk8312_data *data, u8 int_mask)
+{
+ int ret;
+ u8 mode;
+ struct i2c_client *client = data->client;
+
+ mode = data->mode;
+ /* We need to go in standby mode to modify registers */
+ ret = stk8312_set_mode(data, STK8312_MODE_STANDBY);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_write_byte_data(client, STK8312_REG_INTSU, int_mask);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to set interrupts\n");
+ stk8312_set_mode(data, mode);
+ return ret;
+ }
+
+ return stk8312_set_mode(data, mode);
+}
+
+static int stk8312_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct stk8312_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (state)
+ ret = stk8312_set_interrupts(data, STK8312_DREADY_BIT);
+ else
+ ret = stk8312_set_interrupts(data, 0x00);
+
+ if (ret < 0) {
+ dev_err(&data->client->dev, "failed to set trigger state\n");
+ return ret;
+ }
+
+ data->dready_trigger_on = state;
+
+ return 0;
+}
+
+static const struct iio_trigger_ops stk8312_trigger_ops = {
+ .set_trigger_state = stk8312_data_rdy_trigger_set_state,
+};
+
+static int stk8312_set_sample_rate(struct stk8312_data *data, u8 rate)
+{
+ int ret;
+ u8 masked_reg;
+ u8 mode;
+ struct i2c_client *client = data->client;
+
+ if (rate == data->sample_rate_idx)
+ return 0;
+
+ mode = data->mode;
+ /* We need to go in standby mode to modify registers */
+ ret = stk8312_set_mode(data, STK8312_MODE_STANDBY);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_byte_data(client, STK8312_REG_SR);
+ if (ret < 0)
+ goto err_activate;
+
+ masked_reg = (ret & (~STK8312_SR_MASK)) | rate;
+
+ ret = i2c_smbus_write_byte_data(client, STK8312_REG_SR, masked_reg);
+ if (ret < 0)
+ goto err_activate;
+
+ data->sample_rate_idx = rate;
+
+ return stk8312_set_mode(data, mode);
+
+err_activate:
+ dev_err(&client->dev, "failed to set sampling rate\n");
+ stk8312_set_mode(data, mode);
+
+ return ret;
+}
+
+static int stk8312_set_range(struct stk8312_data *data, u8 range)
+{
+ int ret;
+ u8 masked_reg;
+ u8 mode;
+ struct i2c_client *client = data->client;
+
+ if (range != 1 && range != 2)
+ return -EINVAL;
+ else if (range == data->range)
+ return 0;
+
+ mode = data->mode;
+ /* We need to go in standby mode to modify registers */
+ ret = stk8312_set_mode(data, STK8312_MODE_STANDBY);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_byte_data(client, STK8312_REG_STH);
+ if (ret < 0)
+ goto err_activate;
+
+ masked_reg = ret & (~STK8312_RNG_MASK);
+ masked_reg |= range << STK8312_RNG_SHIFT;
+
+ ret = i2c_smbus_write_byte_data(client, STK8312_REG_STH, masked_reg);
+ if (ret < 0)
+ goto err_activate;
+
+ data->range = range;
+
+ return stk8312_set_mode(data, mode);
+
+err_activate:
+ dev_err(&client->dev, "failed to change sensor range\n");
+ stk8312_set_mode(data, mode);
+
+ return ret;
+}
+
+static int stk8312_read_accel(struct stk8312_data *data, u8 address)
+{
+ int ret;
+ struct i2c_client *client = data->client;
+
+ if (address > 2)
+ return -EINVAL;
+
+ ret = i2c_smbus_read_byte_data(client, address);
+ if (ret < 0)
+ dev_err(&client->dev, "register read failed\n");
+
+ return ret;
+}
+
+static int stk8312_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct stk8312_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (iio_buffer_enabled(indio_dev))
+ return -EBUSY;
+ mutex_lock(&data->lock);
+ ret = stk8312_set_mode(data, data->mode | STK8312_MODE_ACTIVE);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+ ret = stk8312_read_accel(data, chan->address);
+ if (ret < 0) {
+ stk8312_set_mode(data,
+ data->mode & (~STK8312_MODE_ACTIVE));
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+ *val = sign_extend32(ret, chan->scan_type.realbits - 1);
+ ret = stk8312_set_mode(data,
+ data->mode & (~STK8312_MODE_ACTIVE));
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = stk8312_scale_table[data->range - 1][0];
+ *val2 = stk8312_scale_table[data->range - 1][1];
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = stk8312_samp_freq_table[data->sample_rate_idx].val;
+ *val2 = stk8312_samp_freq_table[data->sample_rate_idx].val2;
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ return -EINVAL;
+}
+
+static int stk8312_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int i;
+ int index = -1;
+ int ret;
+ struct stk8312_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ for (i = 0; i < ARRAY_SIZE(stk8312_scale_table); i++)
+ if (val == stk8312_scale_table[i][0] &&
+ val2 == stk8312_scale_table[i][1]) {
+ index = i + 1;
+ break;
+ }
+ if (index < 0)
+ return -EINVAL;
+
+ mutex_lock(&data->lock);
+ ret = stk8312_set_range(data, index);
+ mutex_unlock(&data->lock);
+
+ return ret;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ for (i = 0; i < ARRAY_SIZE(stk8312_samp_freq_table); i++)
+ if (val == stk8312_samp_freq_table[i].val &&
+ val2 == stk8312_samp_freq_table[i].val2) {
+ index = i;
+ break;
+ }
+ if (index < 0)
+ return -EINVAL;
+ mutex_lock(&data->lock);
+ ret = stk8312_set_sample_rate(data, index);
+ mutex_unlock(&data->lock);
+
+ return ret;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info stk8312_info = {
+ .read_raw = stk8312_read_raw,
+ .write_raw = stk8312_write_raw,
+ .attrs = &stk8312_attribute_group,
+};
+
+static irqreturn_t stk8312_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct stk8312_data *data = iio_priv(indio_dev);
+ int bit, ret, i = 0;
+
+ mutex_lock(&data->lock);
+ /*
+ * Do a bulk read if all channels are requested,
+ * from 0x00 (XOUT) to 0x02 (ZOUT)
+ */
+ if (*(indio_dev->active_scan_mask) == STK8312_ALL_CHANNEL_MASK) {
+ ret = i2c_smbus_read_i2c_block_data(data->client,
+ STK8312_REG_XOUT,
+ STK8312_ALL_CHANNEL_SIZE,
+ data->scan.chans);
+ if (ret < STK8312_ALL_CHANNEL_SIZE) {
+ dev_err(&data->client->dev, "register read failed\n");
+ mutex_unlock(&data->lock);
+ goto err;
+ }
+ } else {
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = stk8312_read_accel(data, bit);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ goto err;
+ }
+ data->scan.chans[i++] = ret;
+ }
+ }
+ mutex_unlock(&data->lock);
+
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ pf->timestamp);
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t stk8312_data_rdy_trig_poll(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct stk8312_data *data = iio_priv(indio_dev);
+
+ if (data->dready_trigger_on)
+ iio_trigger_poll(data->dready_trig);
+
+ return IRQ_HANDLED;
+}
+
+static int stk8312_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct stk8312_data *data = iio_priv(indio_dev);
+
+ return stk8312_set_mode(data, data->mode | STK8312_MODE_ACTIVE);
+}
+
+static int stk8312_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct stk8312_data *data = iio_priv(indio_dev);
+
+ return stk8312_set_mode(data, data->mode & (~STK8312_MODE_ACTIVE));
+}
+
+static const struct iio_buffer_setup_ops stk8312_buffer_setup_ops = {
+ .preenable = stk8312_buffer_preenable,
+ .postdisable = stk8312_buffer_postdisable,
+};
+
+static int stk8312_probe(struct i2c_client *client)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct stk8312_data *data;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ i2c_set_clientdata(client, indio_dev);
+ mutex_init(&data->lock);
+
+ indio_dev->info = &stk8312_info;
+ indio_dev->name = STK8312_DRIVER_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = stk8312_channels;
+ indio_dev->num_channels = ARRAY_SIZE(stk8312_channels);
+
+ /* A software reset is recommended at power-on */
+ ret = i2c_smbus_write_byte_data(data->client, STK8312_REG_RESET, 0x00);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to reset sensor\n");
+ return ret;
+ }
+ data->sample_rate_idx = STK8312_SR_400HZ_IDX;
+ ret = stk8312_set_range(data, STK8312_RNG_6G);
+ if (ret < 0)
+ return ret;
+
+ ret = stk8312_set_mode(data,
+ STK8312_MODE_INT_AH_PP | STK8312_MODE_ACTIVE);
+ if (ret < 0)
+ return ret;
+
+ if (client->irq > 0) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ stk8312_data_rdy_trig_poll,
+ NULL,
+ IRQF_TRIGGER_RISING |
+ IRQF_ONESHOT,
+ "stk8312_event",
+ indio_dev);
+ if (ret)
+ goto err_power_off;
+
+ data->dready_trig = devm_iio_trigger_alloc(&client->dev,
+ "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->dready_trig) {
+ ret = -ENOMEM;
+ goto err_power_off;
+ }
+
+ data->dready_trig->ops = &stk8312_trigger_ops;
+ iio_trigger_set_drvdata(data->dready_trig, indio_dev);
+ ret = iio_trigger_register(data->dready_trig);
+ if (ret) {
+ dev_err(&client->dev, "iio trigger register failed\n");
+ goto err_power_off;
+ }
+ }
+
+ ret = iio_triggered_buffer_setup(indio_dev,
+ iio_pollfunc_store_time,
+ stk8312_trigger_handler,
+ &stk8312_buffer_setup_ops);
+ if (ret < 0) {
+ dev_err(&client->dev, "iio triggered buffer setup failed\n");
+ goto err_trigger_unregister;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "device_register failed\n");
+ goto err_buffer_cleanup;
+ }
+
+ return 0;
+
+err_buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+err_trigger_unregister:
+ if (data->dready_trig)
+ iio_trigger_unregister(data->dready_trig);
+err_power_off:
+ stk8312_set_mode(data, STK8312_MODE_STANDBY);
+ return ret;
+}
+
+static void stk8312_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct stk8312_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+
+ if (data->dready_trig)
+ iio_trigger_unregister(data->dready_trig);
+
+ stk8312_set_mode(data, STK8312_MODE_STANDBY);
+}
+
+static int stk8312_suspend(struct device *dev)
+{
+ struct stk8312_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return stk8312_set_mode(data, data->mode & (~STK8312_MODE_ACTIVE));
+}
+
+static int stk8312_resume(struct device *dev)
+{
+ struct stk8312_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return stk8312_set_mode(data, data->mode | STK8312_MODE_ACTIVE);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(stk8312_pm_ops, stk8312_suspend,
+ stk8312_resume);
+
+static const struct i2c_device_id stk8312_i2c_id[] = {
+ /* Deprecated in favour of lowercase form */
+ { .name = "STK8312" },
+ { .name = "stk8312" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, stk8312_i2c_id);
+
+static struct i2c_driver stk8312_driver = {
+ .driver = {
+ .name = STK8312_DRIVER_NAME,
+ .pm = pm_sleep_ptr(&stk8312_pm_ops),
+ },
+ .probe = stk8312_probe,
+ .remove = stk8312_remove,
+ .id_table = stk8312_i2c_id,
+};
+
+module_i2c_driver(stk8312_driver);
+
+MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>");
+MODULE_DESCRIPTION("STK8312 3-Axis Accelerometer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/accel/stk8ba50.c b/drivers/iio/accel/stk8ba50.c
new file mode 100644
index 000000000..6c1e286c0
--- /dev/null
+++ b/drivers/iio/accel/stk8ba50.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sensortek STK8BA50 3-Axis Accelerometer
+ *
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * STK8BA50 7-bit I2C address: 0x18.
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitops.h>
+#include <linux/dev_printk.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#define STK8BA50_REG_XOUT 0x02
+#define STK8BA50_REG_YOUT 0x04
+#define STK8BA50_REG_ZOUT 0x06
+#define STK8BA50_REG_RANGE 0x0F
+#define STK8BA50_REG_BWSEL 0x10
+#define STK8BA50_REG_POWMODE 0x11
+#define STK8BA50_REG_SWRST 0x14
+#define STK8BA50_REG_INTEN2 0x17
+#define STK8BA50_REG_INTMAP2 0x1A
+
+#define STK8BA50_MODE_NORMAL 0
+#define STK8BA50_MODE_SUSPEND 1
+#define STK8BA50_MODE_POWERBIT BIT(7)
+#define STK8BA50_DATA_SHIFT 6
+#define STK8BA50_RESET_CMD 0xB6
+#define STK8BA50_SR_1792HZ_IDX 7
+#define STK8BA50_DREADY_INT_MASK 0x10
+#define STK8BA50_DREADY_INT_MAP 0x81
+#define STK8BA50_ALL_CHANNEL_MASK 7
+#define STK8BA50_ALL_CHANNEL_SIZE 6
+
+#define STK8BA50_DRIVER_NAME "stk8ba50"
+
+#define STK8BA50_SCALE_AVAIL "0.0384 0.0767 0.1534 0.3069"
+
+/*
+ * The accelerometer has four measurement ranges:
+ * +/-2g; +/-4g; +/-8g; +/-16g
+ *
+ * Acceleration values are 10-bit, 2's complement.
+ * Scales are calculated as following:
+ *
+ * scale1 = (2 + 2) * 9.81 / (2^10 - 1) = 0.0384
+ * scale2 = (4 + 4) * 9.81 / (2^10 - 1) = 0.0767
+ * etc.
+ *
+ * Scales are stored in this format:
+ * { <register value>, <scale value> }
+ *
+ * Locally, the range is stored as a table index.
+ */
+static const struct {
+ u8 reg_val;
+ u32 scale_val;
+} stk8ba50_scale_table[] = {
+ {3, 38400}, {5, 76700}, {8, 153400}, {12, 306900}
+};
+
+/* Sample rates are stored as { <register value>, <Hz value> } */
+static const struct {
+ u8 reg_val;
+ u16 samp_freq;
+} stk8ba50_samp_freq_table[] = {
+ {0x08, 14}, {0x09, 25}, {0x0A, 56}, {0x0B, 112},
+ {0x0C, 224}, {0x0D, 448}, {0x0E, 896}, {0x0F, 1792}
+};
+
+/* Used to map scan mask bits to their corresponding channel register. */
+static const int stk8ba50_channel_table[] = {
+ STK8BA50_REG_XOUT,
+ STK8BA50_REG_YOUT,
+ STK8BA50_REG_ZOUT
+};
+
+struct stk8ba50_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ int range;
+ u8 sample_rate_idx;
+ struct iio_trigger *dready_trig;
+ bool dready_trigger_on;
+ /* Ensure timestamp is naturally aligned */
+ struct {
+ s16 chans[3];
+ aligned_s64 timetamp;
+ } scan;
+};
+
+#define STK8BA50_ACCEL_CHANNEL(index, reg, axis) { \
+ .type = IIO_ACCEL, \
+ .address = reg, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 10, \
+ .storagebits = 16, \
+ .shift = STK8BA50_DATA_SHIFT, \
+ .endianness = IIO_CPU, \
+ }, \
+}
+
+static const struct iio_chan_spec stk8ba50_channels[] = {
+ STK8BA50_ACCEL_CHANNEL(0, STK8BA50_REG_XOUT, X),
+ STK8BA50_ACCEL_CHANNEL(1, STK8BA50_REG_YOUT, Y),
+ STK8BA50_ACCEL_CHANNEL(2, STK8BA50_REG_ZOUT, Z),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static IIO_CONST_ATTR(in_accel_scale_available, STK8BA50_SCALE_AVAIL);
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("14 25 56 112 224 448 896 1792");
+
+static struct attribute *stk8ba50_attributes[] = {
+ &iio_const_attr_in_accel_scale_available.dev_attr.attr,
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group stk8ba50_attribute_group = {
+ .attrs = stk8ba50_attributes
+};
+
+static int stk8ba50_read_accel(struct stk8ba50_data *data, u8 reg)
+{
+ int ret;
+ struct i2c_client *client = data->client;
+
+ ret = i2c_smbus_read_word_data(client, reg);
+ if (ret < 0) {
+ dev_err(&client->dev, "register read failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static int stk8ba50_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct stk8ba50_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (state)
+ ret = i2c_smbus_write_byte_data(data->client,
+ STK8BA50_REG_INTEN2, STK8BA50_DREADY_INT_MASK);
+ else
+ ret = i2c_smbus_write_byte_data(data->client,
+ STK8BA50_REG_INTEN2, 0x00);
+
+ if (ret < 0)
+ dev_err(&data->client->dev, "failed to set trigger state\n");
+ else
+ data->dready_trigger_on = state;
+
+ return ret;
+}
+
+static const struct iio_trigger_ops stk8ba50_trigger_ops = {
+ .set_trigger_state = stk8ba50_data_rdy_trigger_set_state,
+};
+
+static int stk8ba50_set_power(struct stk8ba50_data *data, bool mode)
+{
+ int ret;
+ u8 masked_reg;
+ struct i2c_client *client = data->client;
+
+ ret = i2c_smbus_read_byte_data(client, STK8BA50_REG_POWMODE);
+ if (ret < 0)
+ goto exit_err;
+
+ if (mode)
+ masked_reg = ret | STK8BA50_MODE_POWERBIT;
+ else
+ masked_reg = ret & (~STK8BA50_MODE_POWERBIT);
+
+ ret = i2c_smbus_write_byte_data(client, STK8BA50_REG_POWMODE,
+ masked_reg);
+ if (ret < 0)
+ goto exit_err;
+
+ return ret;
+
+exit_err:
+ dev_err(&client->dev, "failed to change sensor mode\n");
+ return ret;
+}
+
+static int stk8ba50_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct stk8ba50_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (iio_buffer_enabled(indio_dev))
+ return -EBUSY;
+ mutex_lock(&data->lock);
+ ret = stk8ba50_set_power(data, STK8BA50_MODE_NORMAL);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ return -EINVAL;
+ }
+ ret = stk8ba50_read_accel(data, chan->address);
+ if (ret < 0) {
+ stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND);
+ mutex_unlock(&data->lock);
+ return -EINVAL;
+ }
+ *val = sign_extend32(ret >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+ stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND);
+ mutex_unlock(&data->lock);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = stk8ba50_scale_table[data->range].scale_val;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = stk8ba50_samp_freq_table
+ [data->sample_rate_idx].samp_freq;
+ *val2 = 0;
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int stk8ba50_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int ret;
+ int i;
+ int index = -1;
+ struct stk8ba50_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ if (val != 0)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(stk8ba50_scale_table); i++)
+ if (val2 == stk8ba50_scale_table[i].scale_val) {
+ index = i;
+ break;
+ }
+ if (index < 0)
+ return -EINVAL;
+
+ ret = i2c_smbus_write_byte_data(data->client,
+ STK8BA50_REG_RANGE,
+ stk8ba50_scale_table[index].reg_val);
+ if (ret < 0)
+ dev_err(&data->client->dev,
+ "failed to set measurement range\n");
+ else
+ data->range = index;
+
+ return ret;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ for (i = 0; i < ARRAY_SIZE(stk8ba50_samp_freq_table); i++)
+ if (val == stk8ba50_samp_freq_table[i].samp_freq) {
+ index = i;
+ break;
+ }
+ if (index < 0)
+ return -EINVAL;
+
+ ret = i2c_smbus_write_byte_data(data->client,
+ STK8BA50_REG_BWSEL,
+ stk8ba50_samp_freq_table[index].reg_val);
+ if (ret < 0)
+ dev_err(&data->client->dev,
+ "failed to set sampling rate\n");
+ else
+ data->sample_rate_idx = index;
+
+ return ret;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info stk8ba50_info = {
+ .read_raw = stk8ba50_read_raw,
+ .write_raw = stk8ba50_write_raw,
+ .attrs = &stk8ba50_attribute_group,
+};
+
+static irqreturn_t stk8ba50_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct stk8ba50_data *data = iio_priv(indio_dev);
+ int bit, ret, i = 0;
+
+ mutex_lock(&data->lock);
+ /*
+ * Do a bulk read if all channels are requested,
+ * from 0x02 (XOUT1) to 0x07 (ZOUT2)
+ */
+ if (*(indio_dev->active_scan_mask) == STK8BA50_ALL_CHANNEL_MASK) {
+ ret = i2c_smbus_read_i2c_block_data(data->client,
+ STK8BA50_REG_XOUT,
+ STK8BA50_ALL_CHANNEL_SIZE,
+ (u8 *)data->scan.chans);
+ if (ret < STK8BA50_ALL_CHANNEL_SIZE) {
+ dev_err(&data->client->dev, "register read failed\n");
+ goto err;
+ }
+ } else {
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = stk8ba50_read_accel(data,
+ stk8ba50_channel_table[bit]);
+ if (ret < 0)
+ goto err;
+
+ data->scan.chans[i++] = ret;
+ }
+ }
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ pf->timestamp);
+err:
+ mutex_unlock(&data->lock);
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t stk8ba50_data_rdy_trig_poll(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct stk8ba50_data *data = iio_priv(indio_dev);
+
+ if (data->dready_trigger_on)
+ iio_trigger_poll(data->dready_trig);
+
+ return IRQ_HANDLED;
+}
+
+static int stk8ba50_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct stk8ba50_data *data = iio_priv(indio_dev);
+
+ return stk8ba50_set_power(data, STK8BA50_MODE_NORMAL);
+}
+
+static int stk8ba50_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct stk8ba50_data *data = iio_priv(indio_dev);
+
+ return stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND);
+}
+
+static const struct iio_buffer_setup_ops stk8ba50_buffer_setup_ops = {
+ .preenable = stk8ba50_buffer_preenable,
+ .postdisable = stk8ba50_buffer_postdisable,
+};
+
+static int stk8ba50_probe(struct i2c_client *client)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct stk8ba50_data *data;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ i2c_set_clientdata(client, indio_dev);
+ mutex_init(&data->lock);
+
+ indio_dev->info = &stk8ba50_info;
+ indio_dev->name = STK8BA50_DRIVER_NAME;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = stk8ba50_channels;
+ indio_dev->num_channels = ARRAY_SIZE(stk8ba50_channels);
+
+ /* Reset all registers on startup */
+ ret = i2c_smbus_write_byte_data(client,
+ STK8BA50_REG_SWRST, STK8BA50_RESET_CMD);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to reset sensor\n");
+ goto err_power_off;
+ }
+
+ /* The default range is +/-2g */
+ data->range = 0;
+
+ /* The default sampling rate is 1792 Hz (maximum) */
+ data->sample_rate_idx = STK8BA50_SR_1792HZ_IDX;
+
+ /* Set up interrupts */
+ ret = i2c_smbus_write_byte_data(client,
+ STK8BA50_REG_INTEN2, STK8BA50_DREADY_INT_MASK);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to set up interrupts\n");
+ goto err_power_off;
+ }
+ ret = i2c_smbus_write_byte_data(client,
+ STK8BA50_REG_INTMAP2, STK8BA50_DREADY_INT_MAP);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to set up interrupts\n");
+ goto err_power_off;
+ }
+
+ if (client->irq > 0) {
+ ret = devm_request_irq(&client->dev, client->irq,
+ stk8ba50_data_rdy_trig_poll,
+ IRQF_TRIGGER_RISING | IRQF_NO_THREAD,
+ "stk8ba50_event", indio_dev);
+ if (ret)
+ goto err_power_off;
+
+ data->dready_trig = devm_iio_trigger_alloc(&client->dev,
+ "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!data->dready_trig) {
+ ret = -ENOMEM;
+ goto err_power_off;
+ }
+
+ data->dready_trig->ops = &stk8ba50_trigger_ops;
+ iio_trigger_set_drvdata(data->dready_trig, indio_dev);
+ ret = iio_trigger_register(data->dready_trig);
+ if (ret) {
+ dev_err(&client->dev, "iio trigger register failed\n");
+ goto err_power_off;
+ }
+ }
+
+ ret = iio_triggered_buffer_setup(indio_dev,
+ iio_pollfunc_store_time,
+ stk8ba50_trigger_handler,
+ &stk8ba50_buffer_setup_ops);
+ if (ret < 0) {
+ dev_err(&client->dev, "iio triggered buffer setup failed\n");
+ goto err_trigger_unregister;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "device_register failed\n");
+ goto err_buffer_cleanup;
+ }
+
+ return ret;
+
+err_buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+err_trigger_unregister:
+ if (data->dready_trig)
+ iio_trigger_unregister(data->dready_trig);
+err_power_off:
+ stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND);
+ return ret;
+}
+
+static void stk8ba50_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct stk8ba50_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ iio_triggered_buffer_cleanup(indio_dev);
+
+ if (data->dready_trig)
+ iio_trigger_unregister(data->dready_trig);
+
+ stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND);
+}
+
+static int stk8ba50_suspend(struct device *dev)
+{
+ struct stk8ba50_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND);
+}
+
+static int stk8ba50_resume(struct device *dev)
+{
+ struct stk8ba50_data *data;
+
+ data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
+
+ return stk8ba50_set_power(data, STK8BA50_MODE_NORMAL);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(stk8ba50_pm_ops, stk8ba50_suspend,
+ stk8ba50_resume);
+
+static const struct i2c_device_id stk8ba50_i2c_id[] = {
+ { .name = "stk8ba50" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, stk8ba50_i2c_id);
+
+static const struct acpi_device_id stk8ba50_acpi_id[] = {
+ {"STK8BA50", 0},
+ { }
+};
+
+MODULE_DEVICE_TABLE(acpi, stk8ba50_acpi_id);
+
+static struct i2c_driver stk8ba50_driver = {
+ .driver = {
+ .name = "stk8ba50",
+ .pm = pm_sleep_ptr(&stk8ba50_pm_ops),
+ .acpi_match_table = stk8ba50_acpi_id,
+ },
+ .probe = stk8ba50_probe,
+ .remove = stk8ba50_remove,
+ .id_table = stk8ba50_i2c_id,
+};
+
+module_i2c_driver(stk8ba50_driver);
+
+MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>");
+MODULE_DESCRIPTION("STK8BA50 3-Axis Accelerometer driver");
+MODULE_LICENSE("GPL v2");