aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/iio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio')
-rw-r--r--drivers/iio/accel/Kconfig2
-rw-r--r--drivers/iio/accel/adxl345_core.c4
-rw-r--r--drivers/iio/accel/kxcjk-1013.c5
-rw-r--r--drivers/iio/accel/mma8452.c105
-rw-r--r--drivers/iio/accel/st_accel.h1
-rw-r--r--drivers/iio/accel/st_accel_core.c172
-rw-r--r--drivers/iio/accel/st_accel_i2c.c5
-rw-r--r--drivers/iio/accel/st_accel_spi.c5
-rw-r--r--drivers/iio/adc/Kconfig110
-rw-r--r--drivers/iio/adc/Makefile10
-rw-r--r--drivers/iio/adc/ad7124.c684
-rw-r--r--drivers/iio/adc/ad7476.c20
-rw-r--r--drivers/iio/adc/ad7606.c583
-rw-r--r--drivers/iio/adc/ad7606.h99
-rw-r--r--drivers/iio/adc/ad7606_par.c105
-rw-r--r--drivers/iio/adc/ad7606_spi.c82
-rw-r--r--drivers/iio/adc/ad7768-1.c655
-rw-r--r--drivers/iio/adc/ad7949.c347
-rw-r--r--drivers/iio/adc/ad_sigma_delta.c23
-rw-r--r--drivers/iio/adc/at91_adc.c28
-rw-r--r--drivers/iio/adc/axp288_adc.c76
-rw-r--r--drivers/iio/adc/exynos_adc.c33
-rw-r--r--drivers/iio/adc/ina2xx-adc.c2
-rw-r--r--drivers/iio/adc/ingenic-adc.c364
-rw-r--r--drivers/iio/adc/lpc32xx_adc.c15
-rw-r--r--drivers/iio/adc/max11100.c5
-rw-r--r--drivers/iio/adc/max9611.c5
-rw-r--r--drivers/iio/adc/meson_saradc.c276
-rw-r--r--drivers/iio/adc/npcm_adc.c335
-rw-r--r--drivers/iio/adc/qcom-pm8xxx-xoadc.c10
-rw-r--r--drivers/iio/adc/qcom-spmi-adc5.c58
-rw-r--r--drivers/iio/adc/rcar-gyroadc.c11
-rw-r--r--drivers/iio/adc/sc27xx_adc.c12
-rw-r--r--drivers/iio/adc/stm32-adc-core.c182
-rw-r--r--drivers/iio/adc/stm32-adc.c303
-rw-r--r--drivers/iio/adc/stmpe-adc.c363
-rw-r--r--drivers/iio/adc/ti-adc128s052.c37
-rw-r--r--drivers/iio/adc/ti-ads124s08.c376
-rw-r--r--drivers/iio/adc/ti-ads8688.c3
-rw-r--r--drivers/iio/adc/ti_am335x_adc.c5
-rw-r--r--drivers/iio/adc/xilinx-xadc-core.c7
-rw-r--r--drivers/iio/chemical/Kconfig35
-rw-r--r--drivers/iio/chemical/Makefile3
-rw-r--r--drivers/iio/chemical/atlas-ph-sensor.c7
-rw-r--r--drivers/iio/chemical/bme680.h6
-rw-r--r--drivers/iio/chemical/bme680_core.c54
-rw-r--r--drivers/iio/chemical/bme680_i2c.c28
-rw-r--r--drivers/iio/chemical/bme680_spi.c123
-rw-r--r--drivers/iio/chemical/pms7003.c340
-rw-r--r--drivers/iio/chemical/sgp30.c591
-rw-r--r--drivers/iio/chemical/sps30.c548
-rw-r--r--drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c7
-rw-r--r--drivers/iio/common/hid-sensors/hid-sensor-attributes.c2
-rw-r--r--drivers/iio/common/ssp_sensors/ssp_dev.c20
-rw-r--r--drivers/iio/common/st_sensors/st_sensors_core.c3
-rw-r--r--drivers/iio/common/st_sensors/st_sensors_trigger.c4
-rw-r--r--drivers/iio/dac/Kconfig25
-rw-r--r--drivers/iio/dac/Makefile2
-rw-r--r--drivers/iio/dac/ad5686-spi.c30
-rw-r--r--drivers/iio/dac/ad5686.c63
-rw-r--r--drivers/iio/dac/ad5686.h11
-rw-r--r--drivers/iio/dac/ad5696-i2c.c2
-rw-r--r--drivers/iio/dac/ad5758.c2
-rw-r--r--drivers/iio/dac/dpot-dac.c4
-rw-r--r--drivers/iio/dac/mcp4725.c1
-rw-r--r--drivers/iio/dac/ti-dac7311.c338
-rw-r--r--drivers/iio/dac/ti-dac7612.c184
-rw-r--r--drivers/iio/frequency/ad9523.c7
-rw-r--r--drivers/iio/gyro/bmg160_core.c6
-rw-r--r--drivers/iio/gyro/mpu3050-core.c8
-rw-r--r--drivers/iio/imu/bmi160/bmi160.h12
-rw-r--r--drivers/iio/imu/bmi160/bmi160_core.c347
-rw-r--r--drivers/iio/imu/bmi160/bmi160_i2c.c13
-rw-r--r--drivers/iio/imu/bmi160/bmi160_spi.c12
-rw-r--r--drivers/iio/imu/inv_mpu6050/Kconfig8
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c16
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_core.c31
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c6
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h8
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c12
-rw-r--r--drivers/iio/imu/st_lsm6dsx/Makefile3
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h167
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c165
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c283
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c777
-rw-r--r--drivers/iio/industrialio-buffer.c5
-rw-r--r--drivers/iio/industrialio-core.c12
-rw-r--r--drivers/iio/light/Kconfig23
-rw-r--r--drivers/iio/light/Makefile2
-rw-r--r--drivers/iio/light/isl29018.c46
-rw-r--r--drivers/iio/light/max44009.c555
-rw-r--r--drivers/iio/light/vcnl4035.c676
-rw-r--r--drivers/iio/magnetometer/Kconfig29
-rw-r--r--drivers/iio/magnetometer/Makefile4
-rw-r--r--drivers/iio/magnetometer/ak8975.c1
-rw-r--r--drivers/iio/magnetometer/mag3110.c94
-rw-r--r--drivers/iio/magnetometer/rm3100-core.c616
-rw-r--r--drivers/iio/magnetometer/rm3100-i2c.c54
-rw-r--r--drivers/iio/magnetometer/rm3100-spi.c64
-rw-r--r--drivers/iio/magnetometer/rm3100.h17
-rw-r--r--drivers/iio/magnetometer/st_magn.h1
-rw-r--r--drivers/iio/magnetometer/st_magn_core.c11
-rw-r--r--drivers/iio/magnetometer/st_magn_i2c.c5
-rw-r--r--drivers/iio/magnetometer/st_magn_spi.c7
-rw-r--r--drivers/iio/potentiometer/Kconfig12
-rw-r--r--drivers/iio/potentiometer/Makefile1
-rw-r--r--drivers/iio/potentiometer/mcp41010.c203
-rw-r--r--drivers/iio/potentiometer/mcp4131.c11
-rw-r--r--drivers/iio/potentiometer/tpl0102.c42
-rw-r--r--drivers/iio/pressure/Kconfig2
-rw-r--r--drivers/iio/pressure/st_pressure.h2
-rw-r--r--drivers/iio/pressure/st_pressure_core.c69
-rw-r--r--drivers/iio/pressure/st_pressure_i2c.c5
-rw-r--r--drivers/iio/pressure/st_pressure_spi.c5
-rw-r--r--drivers/iio/resolver/Kconfig10
-rw-r--r--drivers/iio/resolver/Makefile1
-rw-r--r--drivers/iio/resolver/ad2s90.c131
117 files changed, 11856 insertions, 697 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
index 7993a67bd351..898839ca164a 100644
--- a/drivers/iio/accel/Kconfig
+++ b/drivers/iio/accel/Kconfig
@@ -223,7 +223,7 @@ config IIO_ST_ACCEL_3AXIS
Say yes here to build support for STMicroelectronics accelerometers:
LSM303DLH, LSM303DLHC, LIS3DH, LSM330D, LSM330DL, LSM330DLC,
LIS331DLH, LSM303DL, LSM303DLM, LSM330, LIS2DH12, H3LIS331DL,
- LNG2DM
+ LNG2DM, LIS3DE
This driver can also be built as a module. If so, these modules
will be created:
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 780f87f72338..f03ed00685ea 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -150,8 +150,8 @@ static int adxl345_read_raw(struct iio_dev *indio_dev,
}
static int adxl345_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val, int val2, long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct adxl345_data *data = iio_priv(indio_dev);
s64 n;
diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c
index af53a1084ee5..50f3ff386bea 100644
--- a/drivers/iio/accel/kxcjk-1013.c
+++ b/drivers/iio/accel/kxcjk-1013.c
@@ -1437,6 +1437,8 @@ static int kxcjk1013_resume(struct device *dev)
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;
@@ -1489,8 +1491,11 @@ static const struct acpi_device_id kx_acpi_match[] = {
{"KXCJ1013", KXCJK1013},
{"KXCJ1008", KXCJ91008},
{"KXCJ9000", KXCJ91008},
+ {"KIOX0009", KXTJ21009},
{"KIOX000A", KXCJ91008},
+ {"KIOX010A", KXCJ91008}, /* KXCJ91008 inside the display of a 2-in-1 */
{"KXTJ1009", KXTJ21009},
+ {"KXJ2109", KXTJ21009},
{"SMO8500", KXCJ91008},
{ },
};
diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c
index 421a0a8a1379..302781126bc6 100644
--- a/drivers/iio/accel/mma8452.c
+++ b/drivers/iio/accel/mma8452.c
@@ -31,6 +31,7 @@
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
#define MMA8452_STATUS 0x00
#define MMA8452_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0))
@@ -107,6 +108,8 @@ struct mma8452_data {
u8 data_cfg;
const struct mma_chip_info *chip_info;
int sleep_val;
+ struct regulator *vdd_reg;
+ struct regulator *vddio_reg;
};
/**
@@ -1534,9 +1537,39 @@ static int mma8452_probe(struct i2c_client *client,
mutex_init(&data->lock);
data->chip_info = match->data;
+ data->vdd_reg = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(data->vdd_reg)) {
+ if (PTR_ERR(data->vdd_reg) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ dev_err(&client->dev, "failed to get VDD regulator!\n");
+ return PTR_ERR(data->vdd_reg);
+ }
+
+ data->vddio_reg = devm_regulator_get(&client->dev, "vddio");
+ if (IS_ERR(data->vddio_reg)) {
+ if (PTR_ERR(data->vddio_reg) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ dev_err(&client->dev, "failed to get VDDIO regulator!\n");
+ return PTR_ERR(data->vddio_reg);
+ }
+
+ ret = regulator_enable(data->vdd_reg);
+ if (ret) {
+ dev_err(&client->dev, "failed to enable VDD regulator!\n");
+ return ret;
+ }
+
+ ret = regulator_enable(data->vddio_reg);
+ if (ret) {
+ dev_err(&client->dev, "failed to enable VDDIO regulator!\n");
+ goto disable_regulator_vdd;
+ }
+
ret = i2c_smbus_read_byte_data(client, MMA8452_WHO_AM_I);
if (ret < 0)
- return ret;
+ goto disable_regulators;
switch (ret) {
case MMA8451_DEVICE_ID:
@@ -1549,7 +1582,8 @@ static int mma8452_probe(struct i2c_client *client,
break;
/* else: fall through */
default:
- return -ENODEV;
+ ret = -ENODEV;
+ goto disable_regulators;
}
dev_info(&client->dev, "registering %s accelerometer; ID 0x%x\n",
@@ -1566,13 +1600,13 @@ static int mma8452_probe(struct i2c_client *client,
ret = mma8452_reset(client);
if (ret < 0)
- return ret;
+ 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)
- return ret;
+ goto disable_regulators;
/*
* By default set transient threshold to max to avoid events if
@@ -1581,7 +1615,7 @@ static int mma8452_probe(struct i2c_client *client,
ret = i2c_smbus_write_byte_data(client, MMA8452_TRANSIENT_THS,
MMA8452_TRANSIENT_THS_MASK);
if (ret < 0)
- return ret;
+ goto disable_regulators;
if (client->irq) {
int irq2;
@@ -1595,7 +1629,7 @@ static int mma8452_probe(struct i2c_client *client,
MMA8452_CTRL_REG5,
data->chip_info->all_events);
if (ret < 0)
- return ret;
+ goto disable_regulators;
dev_dbg(&client->dev, "using interrupt line INT1\n");
}
@@ -1604,11 +1638,11 @@ static int mma8452_probe(struct i2c_client *client,
MMA8452_CTRL_REG4,
data->chip_info->enabled_events);
if (ret < 0)
- return ret;
+ goto disable_regulators;
ret = mma8452_trigger_setup(indio_dev);
if (ret < 0)
- return ret;
+ goto disable_regulators;
}
data->ctrl_reg1 = MMA8452_CTRL_ACTIVE |
@@ -1661,12 +1695,19 @@ buffer_cleanup:
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 int 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);
@@ -1678,6 +1719,9 @@ static int mma8452_remove(struct i2c_client *client)
mma8452_trigger_cleanup(indio_dev);
mma8452_standby(iio_priv(indio_dev));
+ regulator_disable(data->vddio_reg);
+ regulator_disable(data->vdd_reg);
+
return 0;
}
@@ -1696,6 +1740,18 @@ static int mma8452_runtime_suspend(struct device *dev)
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;
}
@@ -1705,9 +1761,22 @@ static int mma8452_runtime_resume(struct device *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)
- return ret;
+ goto runtime_resume_failed;
ret = mma8452_get_odr_index(data);
sleep_val = 1000 / mma8452_samp_freq[ret][0];
@@ -1717,25 +1786,17 @@ static int mma8452_runtime_resume(struct device *dev)
msleep_interruptible(sleep_val);
return 0;
-}
-#endif
-#ifdef CONFIG_PM_SLEEP
-static int mma8452_suspend(struct device *dev)
-{
- return mma8452_standby(iio_priv(i2c_get_clientdata(
- to_i2c_client(dev))));
-}
+runtime_resume_failed:
+ regulator_disable(data->vddio_reg);
+ regulator_disable(data->vdd_reg);
-static int mma8452_resume(struct device *dev)
-{
- return mma8452_active(iio_priv(i2c_get_clientdata(
- to_i2c_client(dev))));
+ return ret;
}
#endif
static const struct dev_pm_ops mma8452_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(mma8452_suspend, mma8452_resume)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(mma8452_runtime_suspend,
mma8452_runtime_resume, NULL)
};
diff --git a/drivers/iio/accel/st_accel.h b/drivers/iio/accel/st_accel.h
index 2f931e4837e5..fd53258656ca 100644
--- a/drivers/iio/accel/st_accel.h
+++ b/drivers/iio/accel/st_accel.h
@@ -56,6 +56,7 @@ enum st_accel_type {
#define LNG2DM_ACCEL_DEV_NAME "lng2dm"
#define LIS2DW12_ACCEL_DEV_NAME "lis2dw12"
#define LIS3DHH_ACCEL_DEV_NAME "lis3dhh"
+#define LIS3DE_ACCEL_DEV_NAME "lis3de"
/**
* struct st_sensors_platform_data - default accel platform data
diff --git a/drivers/iio/accel/st_accel_core.c b/drivers/iio/accel/st_accel_core.c
index 3e6fd5a8ac5b..a3c0916479fa 100644
--- a/drivers/iio/accel/st_accel_core.c
+++ b/drivers/iio/accel/st_accel_core.c
@@ -11,6 +11,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
+#include <linux/acpi.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/mutex.h>
@@ -103,6 +104,7 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
[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 = {
@@ -917,12 +919,167 @@ static const struct iio_trigger_ops st_accel_trigger_ops = {
#define ST_ACCEL_TRIGGER_OPS NULL
#endif
+static const struct iio_mount_matrix *
+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 mount_matrix_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, get_mount_matrix),
+ { },
+};
+
+/* 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 iio_chan_spec *channels)
+{
+#ifdef CONFIG_ACPI
+ 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;
+ 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(adata->dev);
+ if (!adev)
+ return 0;
+
+ /* 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 0;
+ } else if (ACPI_FAILURE(status)) {
+ dev_warn(&indio_dev->dev, "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 < 0 || 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 */
+ adata->mount_matrix = devm_kmalloc(&indio_dev->dev,
+ sizeof(*adata->mount_matrix),
+ GFP_KERNEL);
+ if (!adata->mount_matrix) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ 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;
+ }
+ }
+
+ /* Expose the mount matrix via ext_info */
+ for (i = 0; i < indio_dev->num_channels; i++)
+ channels[i].ext_info = mount_matrix_ext_info;
+
+ ret = 0;
+ dev_info(&indio_dev->dev, "computed mount matrix from ACPI\n");
+
+out:
+ kfree(buffer.pointer);
+ return ret;
+#else /* !CONFIG_ACPI */
+ return 0;
+#endif
+}
+
int st_accel_common_probe(struct iio_dev *indio_dev)
{
struct st_sensor_data *adata = iio_priv(indio_dev);
struct st_sensors_platform_data *pdata =
(struct st_sensors_platform_data *)adata->dev->platform_data;
int irq = adata->get_irq_data_ready(indio_dev);
+ struct iio_chan_spec *channels;
+ size_t channels_size;
int err;
indio_dev->modes = INDIO_DIRECT_MODE;
@@ -941,9 +1098,22 @@ int st_accel_common_probe(struct iio_dev *indio_dev)
adata->num_data_channels = ST_ACCEL_NUMBER_DATA_CHANNELS;
adata->multiread_bit = adata->sensor_settings->multi_read_bit;
- indio_dev->channels = adata->sensor_settings->ch;
indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS;
+ channels_size = indio_dev->num_channels * sizeof(struct iio_chan_spec);
+ channels = devm_kmemdup(&indio_dev->dev,
+ adata->sensor_settings->ch,
+ channels_size, GFP_KERNEL);
+ if (!channels) {
+ err = -ENOMEM;
+ goto st_accel_power_off;
+ }
+
+ if (apply_acpi_orientation(indio_dev, channels))
+ dev_warn(&indio_dev->dev,
+ "failed to apply ACPI orientation data: %d\n", err);
+
+ indio_dev->channels = channels;
adata->current_fullscale = (struct st_sensor_fullscale_avl *)
&adata->sensor_settings->fs.fs_avl[0];
adata->odr = adata->sensor_settings->odr.odr_avl[0].hz;
diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c
index 2ca5d1f6ade0..de8ae4327094 100644
--- a/drivers/iio/accel/st_accel_i2c.c
+++ b/drivers/iio/accel/st_accel_i2c.c
@@ -98,6 +98,10 @@ static const struct of_device_id st_accel_of_match[] = {
.compatible = "st,lis2dw12",
.data = LIS2DW12_ACCEL_DEV_NAME,
},
+ {
+ .compatible = "st,lis3de",
+ .data = LIS3DE_ACCEL_DEV_NAME,
+ },
{},
};
MODULE_DEVICE_TABLE(of, st_accel_of_match);
@@ -135,6 +139,7 @@ static const struct i2c_device_id st_accel_id_table[] = {
{ LIS331DL_ACCEL_DEV_NAME },
{ LIS3LV02DL_ACCEL_DEV_NAME },
{ LIS2DW12_ACCEL_DEV_NAME },
+ { LIS3DE_ACCEL_DEV_NAME },
{},
};
MODULE_DEVICE_TABLE(i2c, st_accel_id_table);
diff --git a/drivers/iio/accel/st_accel_spi.c b/drivers/iio/accel/st_accel_spi.c
index dcc9bd243a52..73bfb5d04e2b 100644
--- a/drivers/iio/accel/st_accel_spi.c
+++ b/drivers/iio/accel/st_accel_spi.c
@@ -90,6 +90,10 @@ static const struct of_device_id st_accel_of_match[] = {
.compatible = "st,lis3dhh",
.data = LIS3DHH_ACCEL_DEV_NAME,
},
+ {
+ .compatible = "st,lis3de",
+ .data = LIS3DE_ACCEL_DEV_NAME,
+ },
{}
};
MODULE_DEVICE_TABLE(of, st_accel_of_match);
@@ -143,6 +147,7 @@ static const struct spi_device_id st_accel_id_table[] = {
{ LIS3LV02DL_ACCEL_DEV_NAME },
{ LIS2DW12_ACCEL_DEV_NAME },
{ LIS3DHH_ACCEL_DEV_NAME },
+ { LIS3DE_ACCEL_DEV_NAME },
{},
};
MODULE_DEVICE_TABLE(spi, st_accel_id_table);
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index a52fea8749a9..76db6e5cc296 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -10,6 +10,17 @@ config AD_SIGMA_DELTA
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
+config AD7124
+ tristate "Analog Devices AD7124 and similar sigma-delta ADCs driver"
+ depends on SPI_MASTER
+ select AD_SIGMA_DELTA
+ help
+ Say yes here to build support for Analog Devices AD7124-4 and AD7124-8
+ SPI analog to digital converters (ADC).
+
+ To compile this driver as a module, choose M here: the module will be
+ called ad7124.
+
config AD7266
tristate "Analog Devices AD7265/AD7266 ADC driver"
depends on SPI_MASTER
@@ -46,18 +57,48 @@ config AD7298
module will be called ad7298.
config AD7476
- tristate "Analog Devices AD7476 and similar 1-channel ADCs driver"
+ tristate "Analog Devices AD7476 1-channel ADCs driver and other similar devices from AD an TI"
depends on SPI
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
- Say yes here to build support for Analog Devices AD7273, AD7274, AD7276,
- AD7277, AD7278, AD7475, AD7476, AD7477, AD7478, AD7466, AD7467, AD7468,
- AD7495, AD7910, AD7920, AD7920 SPI analog to digital converters (ADC).
+ Say yes here to build support for the following SPI analog to
+ digital converters (ADCs):
+ Analog Devices: AD7273, AD7274, AD7276, AD7277, AD7278, AD7475,
+ AD7476, AD7477, AD7478, AD7466, AD7467, AD7468, AD7495, AD7910,
+ AD7920.
+ Texas Instruments: ADS7866, ADS7867, ADS7868.
To compile this driver as a module, choose M here: the
module will be called ad7476.
+config AD7606
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config AD7606_IFACE_PARALLEL
+ tristate "Analog Devices AD7606 ADC driver with parallel interface support"
+ depends on HAS_IOMEM
+ select AD7606
+ help
+ Say yes here to build parallel interface support for Analog Devices:
+ ad7605-4, ad7606, ad7606-6, ad7606-4 analog to digital converters (ADC).
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad7606_parallel.
+
+config AD7606_IFACE_SPI
+ tristate "Analog Devices AD7606 ADC driver with spi interface support"
+ depends on SPI
+ select AD7606
+ help
+ Say yes here to build spi interface support for Analog Devices:
+ ad7605-4, ad7606, ad7606-6, ad7606-4 analog to digital converters (ADC).
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad7606_spi.
+
config AD7766
tristate "Analog Devices AD7766/AD7767 ADC driver"
depends on SPI_MASTER
@@ -70,6 +111,19 @@ config AD7766
To compile this driver as a module, choose M here: the module will be
called ad7766.
+config AD7768_1
+ tristate "Analog Devices AD7768-1 ADC driver"
+ depends on SPI
+ select IIO_BUFFER
+ select IIO_TRIGGER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to build support for Analog Devices AD7768-1 SPI
+ simultaneously sampling sigma-delta analog to digital converter (ADC).
+
+ To compile this driver as a module, choose M here: the module will be
+ called ad7768-1.
+
config AD7791
tristate "Analog Devices AD7791 ADC driver"
depends on SPI
@@ -116,6 +170,16 @@ config AD7923
To compile this driver as a module, choose M here: the
module will be called ad7923.
+config AD7949
+ tristate "Analog Devices AD7949 and similar ADCs driver"
+ depends on SPI
+ help
+ Say yes here to build support for Analog Devices
+ AD7949, AD7682, AD7689 8 Channel ADCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad7949.
+
config AD799X
tristate "Analog Devices AD799x ADC driver"
depends on I2C
@@ -274,7 +338,7 @@ config EP93XX_ADC
config EXYNOS_ADC
tristate "Exynos ADC driver support"
- depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST)
+ depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || ARCH_S5PV210 || (OF && COMPILE_TEST)
depends on HAS_IOMEM
help
Core support for the ADC block found in the Samsung EXYNOS series
@@ -346,6 +410,15 @@ config INA2XX_ADC
Say yes here to build support for TI INA2xx family of Power Monitors.
This driver is mutually exclusive with the HWMON version.
+config INGENIC_ADC
+ tristate "Ingenic JZ47xx SoCs ADC driver"
+ depends on MIPS || COMPILE_TEST
+ help
+ Say yes here to build support for the Ingenic JZ47xx SoCs ADC unit.
+
+ This driver can also be built as a module. If so, the module will be
+ called ingenic_adc.
+
config IMX7D_ADC
tristate "Freescale IMX7D ADC driver"
depends on ARCH_MXC || COMPILE_TEST
@@ -555,6 +628,16 @@ config NAU7802
To compile this driver as a module, choose M here: the
module will be called nau7802.
+config NPCM_ADC
+ tristate "Nuvoton NPCM ADC driver"
+ depends on ARCH_NPCM || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Say yes here to build support for Nuvoton NPCM ADC.
+
+ This driver can also be built as a module. If so, the module
+ will be called npcm_adc.
+
config PALMAS_GPADC
tristate "TI Palmas General Purpose ADC"
depends on MFD_PALMAS
@@ -734,6 +817,13 @@ config STM32_DFSDM_ADC
This driver can also be built as a module. If so, the module
will be called stm32-dfsdm-adc.
+config STMPE_ADC
+ tristate "STMicroelectronics STMPE ADC driver"
+ depends on OF && MFD_STMPE
+ help
+ Say yes here to build support for ST Microelectronics STMPE
+ built-in ADC block (stmpe811).
+
config STX104
tristate "Apex Embedded Systems STX104 driver"
depends on PC104 && X86
@@ -887,6 +977,16 @@ config TI_ADS8688
This driver can also be built as a module. If so, the module will be
called ti-ads8688.
+config TI_ADS124S08
+ tristate "Texas Instruments ADS124S08"
+ depends on SPI && OF
+ help
+ If you say yes here you get support for Texas Instruments ADS124S08
+ and ADS124S06 ADC chips
+
+ This driver can also be built as a module. If so, the module will be
+ called ti-ads124s08.
+
config TI_AM335X_ADC
tristate "TI's AM335X ADC driver"
depends on MFD_TI_AM335X_TSCADC && HAS_DMA
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index a6e6a0b659e2..6fcebd167524 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -5,15 +5,21 @@
# When adding new entries keep the list in alphabetical order
obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o
+obj-$(CONFIG_AD7124) += ad7124.o
obj-$(CONFIG_AD7266) += ad7266.o
obj-$(CONFIG_AD7291) += ad7291.o
obj-$(CONFIG_AD7298) += ad7298.o
obj-$(CONFIG_AD7923) += ad7923.o
obj-$(CONFIG_AD7476) += ad7476.o
+obj-$(CONFIG_AD7606_IFACE_PARALLEL) += ad7606_par.o
+obj-$(CONFIG_AD7606_IFACE_SPI) += ad7606_spi.o
+obj-$(CONFIG_AD7606) += ad7606.o
obj-$(CONFIG_AD7766) += ad7766.o
+obj-$(CONFIG_AD7768_1) += ad7768-1.o
obj-$(CONFIG_AD7791) += ad7791.o
obj-$(CONFIG_AD7793) += ad7793.o
obj-$(CONFIG_AD7887) += ad7887.o
+obj-$(CONFIG_AD7949) += ad7949.o
obj-$(CONFIG_AD799X) += ad799x.o
obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o
obj-$(CONFIG_AT91_ADC) += at91_adc.o
@@ -34,6 +40,7 @@ obj-$(CONFIG_HI8435) += hi8435.o
obj-$(CONFIG_HX711) += hx711.o
obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o
obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o
+obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o
obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
@@ -53,6 +60,7 @@ obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
obj-$(CONFIG_NAU7802) += nau7802.o
+obj-$(CONFIG_NPCM_ADC) += npcm_adc.o
obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
@@ -69,6 +77,7 @@ obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
obj-$(CONFIG_STM32_ADC) += stm32-adc.o
obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o
obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o
+obj-$(CONFIG_STMPE_ADC) += stmpe-adc.o
obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
obj-$(CONFIG_TI_ADC084S021) += ti-adc084s021.o
@@ -79,6 +88,7 @@ obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o
obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o
obj-$(CONFIG_TI_ADS7950) += ti-ads7950.o
obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o
+obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o
obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c
new file mode 100644
index 000000000000..7d5e5311d8de
--- /dev/null
+++ b/drivers/iio/adc/ad7124.c
@@ -0,0 +1,684 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * AD7124 SPI ADC driver
+ *
+ * Copyright 2018 Analog Devices Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/adc/ad_sigma_delta.h>
+#include <linux/iio/sysfs.h>
+
+/* AD7124 registers */
+#define AD7124_COMMS 0x00
+#define AD7124_STATUS 0x00
+#define AD7124_ADC_CONTROL 0x01
+#define AD7124_DATA 0x02
+#define AD7124_IO_CONTROL_1 0x03
+#define AD7124_IO_CONTROL_2 0x04
+#define AD7124_ID 0x05
+#define AD7124_ERROR 0x06
+#define AD7124_ERROR_EN 0x07
+#define AD7124_MCLK_COUNT 0x08
+#define AD7124_CHANNEL(x) (0x09 + (x))
+#define AD7124_CONFIG(x) (0x19 + (x))
+#define AD7124_FILTER(x) (0x21 + (x))
+#define AD7124_OFFSET(x) (0x29 + (x))
+#define AD7124_GAIN(x) (0x31 + (x))
+
+/* AD7124_STATUS */
+#define AD7124_STATUS_POR_FLAG_MSK BIT(4)
+
+/* AD7124_ADC_CONTROL */
+#define AD7124_ADC_CTRL_PWR_MSK GENMASK(7, 6)
+#define AD7124_ADC_CTRL_PWR(x) FIELD_PREP(AD7124_ADC_CTRL_PWR_MSK, x)
+#define AD7124_ADC_CTRL_MODE_MSK GENMASK(5, 2)
+#define AD7124_ADC_CTRL_MODE(x) FIELD_PREP(AD7124_ADC_CTRL_MODE_MSK, x)
+
+/* AD7124_CHANNEL_X */
+#define AD7124_CHANNEL_EN_MSK BIT(15)
+#define AD7124_CHANNEL_EN(x) FIELD_PREP(AD7124_CHANNEL_EN_MSK, x)
+#define AD7124_CHANNEL_SETUP_MSK GENMASK(14, 12)
+#define AD7124_CHANNEL_SETUP(x) FIELD_PREP(AD7124_CHANNEL_SETUP_MSK, x)
+#define AD7124_CHANNEL_AINP_MSK GENMASK(9, 5)
+#define AD7124_CHANNEL_AINP(x) FIELD_PREP(AD7124_CHANNEL_AINP_MSK, x)
+#define AD7124_CHANNEL_AINM_MSK GENMASK(4, 0)
+#define AD7124_CHANNEL_AINM(x) FIELD_PREP(AD7124_CHANNEL_AINM_MSK, x)
+
+/* AD7124_CONFIG_X */
+#define AD7124_CONFIG_BIPOLAR_MSK BIT(11)
+#define AD7124_CONFIG_BIPOLAR(x) FIELD_PREP(AD7124_CONFIG_BIPOLAR_MSK, x)
+#define AD7124_CONFIG_REF_SEL_MSK GENMASK(4, 3)
+#define AD7124_CONFIG_REF_SEL(x) FIELD_PREP(AD7124_CONFIG_REF_SEL_MSK, x)
+#define AD7124_CONFIG_PGA_MSK GENMASK(2, 0)
+#define AD7124_CONFIG_PGA(x) FIELD_PREP(AD7124_CONFIG_PGA_MSK, x)
+
+/* AD7124_FILTER_X */
+#define AD7124_FILTER_FS_MSK GENMASK(10, 0)
+#define AD7124_FILTER_FS(x) FIELD_PREP(AD7124_FILTER_FS_MSK, x)
+
+enum ad7124_ids {
+ ID_AD7124_4,
+ ID_AD7124_8,
+};
+
+enum ad7124_ref_sel {
+ AD7124_REFIN1,
+ AD7124_REFIN2,
+ AD7124_INT_REF,
+ AD7124_AVDD_REF,
+};
+
+enum ad7124_power_mode {
+ AD7124_LOW_POWER,
+ AD7124_MID_POWER,
+ AD7124_FULL_POWER,
+};
+
+static const unsigned int ad7124_gain[8] = {
+ 1, 2, 4, 8, 16, 32, 64, 128
+};
+
+static const int ad7124_master_clk_freq_hz[3] = {
+ [AD7124_LOW_POWER] = 76800,
+ [AD7124_MID_POWER] = 153600,
+ [AD7124_FULL_POWER] = 614400,
+};
+
+static const char * const ad7124_ref_names[] = {
+ [AD7124_REFIN1] = "refin1",
+ [AD7124_REFIN2] = "refin2",
+ [AD7124_INT_REF] = "int",
+ [AD7124_AVDD_REF] = "avdd",
+};
+
+struct ad7124_chip_info {
+ unsigned int num_inputs;
+};
+
+struct ad7124_channel_config {
+ enum ad7124_ref_sel refsel;
+ bool bipolar;
+ unsigned int ain;
+ unsigned int vref_mv;
+ unsigned int pga_bits;
+ unsigned int odr;
+};
+
+struct ad7124_state {
+ const struct ad7124_chip_info *chip_info;
+ struct ad_sigma_delta sd;
+ struct ad7124_channel_config channel_config[4];
+ struct regulator *vref[4];
+ struct clk *mclk;
+ unsigned int adc_control;
+ unsigned int num_channels;
+};
+
+static const struct iio_chan_spec ad7124_channel_template = {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .differential = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 24,
+ .storagebits = 32,
+ .shift = 8,
+ .endianness = IIO_BE,
+ },
+};
+
+static struct ad7124_chip_info ad7124_chip_info_tbl[] = {
+ [ID_AD7124_4] = {
+ .num_inputs = 8,
+ },
+ [ID_AD7124_8] = {
+ .num_inputs = 16,
+ },
+};
+
+static int ad7124_find_closest_match(const int *array,
+ unsigned int size, int val)
+{
+ int i, idx;
+ unsigned int diff_new, diff_old;
+
+ diff_old = U32_MAX;
+ idx = 0;
+
+ for (i = 0; i < size; i++) {
+ diff_new = abs(val - array[i]);
+ if (diff_new < diff_old) {
+ diff_old = diff_new;
+ idx = i;
+ }
+ }
+
+ return idx;
+}
+
+static int ad7124_spi_write_mask(struct ad7124_state *st,
+ unsigned int addr,
+ unsigned long mask,
+ unsigned int val,
+ unsigned int bytes)
+{
+ unsigned int readval;
+ int ret;
+
+ ret = ad_sd_read_reg(&st->sd, addr, bytes, &readval);
+ if (ret < 0)
+ return ret;
+
+ readval &= ~mask;
+ readval |= val;
+
+ return ad_sd_write_reg(&st->sd, addr, bytes, readval);
+}
+
+static int ad7124_set_mode(struct ad_sigma_delta *sd,
+ enum ad_sigma_delta_mode mode)
+{
+ struct ad7124_state *st = container_of(sd, struct ad7124_state, sd);
+
+ st->adc_control &= ~AD7124_ADC_CTRL_MODE_MSK;
+ st->adc_control |= AD7124_ADC_CTRL_MODE(mode);
+
+ return ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, st->adc_control);
+}
+
+static int ad7124_set_channel(struct ad_sigma_delta *sd, unsigned int channel)
+{
+ struct ad7124_state *st = container_of(sd, struct ad7124_state, sd);
+ unsigned int val;
+
+ val = st->channel_config[channel].ain | AD7124_CHANNEL_EN(1) |
+ AD7124_CHANNEL_SETUP(channel);
+
+ return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(channel), 2, val);
+}
+
+static const struct ad_sigma_delta_info ad7124_sigma_delta_info = {
+ .set_channel = ad7124_set_channel,
+ .set_mode = ad7124_set_mode,
+ .has_registers = true,
+ .addr_shift = 0,
+ .read_mask = BIT(6),
+ .data_reg = AD7124_DATA,
+};
+
+static int ad7124_set_channel_odr(struct ad7124_state *st,
+ unsigned int channel,
+ unsigned int odr)
+{
+ unsigned int fclk, odr_sel_bits;
+ int ret;
+
+ fclk = clk_get_rate(st->mclk);
+ /*
+ * FS[10:0] = fCLK / (fADC x 32) where:
+ * fADC is the output data rate
+ * fCLK is the master clock frequency
+ * FS[10:0] are the bits in the filter register
+ * FS[10:0] can have a value from 1 to 2047
+ */
+ odr_sel_bits = DIV_ROUND_CLOSEST(fclk, odr * 32);
+ if (odr_sel_bits < 1)
+ odr_sel_bits = 1;
+ else if (odr_sel_bits > 2047)
+ odr_sel_bits = 2047;
+
+ ret = ad7124_spi_write_mask(st, AD7124_FILTER(channel),
+ AD7124_FILTER_FS_MSK,
+ AD7124_FILTER_FS(odr_sel_bits), 3);
+ if (ret < 0)
+ return ret;
+ /* fADC = fCLK / (FS[10:0] x 32) */
+ st->channel_config[channel].odr =
+ DIV_ROUND_CLOSEST(fclk, odr_sel_bits * 32);
+
+ return 0;
+}
+
+static int ad7124_set_channel_gain(struct ad7124_state *st,
+ unsigned int channel,
+ unsigned int gain)
+{
+ unsigned int res;
+ int ret;
+
+ res = ad7124_find_closest_match(ad7124_gain,
+ ARRAY_SIZE(ad7124_gain), gain);
+ ret = ad7124_spi_write_mask(st, AD7124_CONFIG(channel),
+ AD7124_CONFIG_PGA_MSK,
+ AD7124_CONFIG_PGA(res), 2);
+ if (ret < 0)
+ return ret;
+
+ st->channel_config[channel].pga_bits = res;
+
+ return 0;
+}
+
+static int ad7124_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct ad7124_state *st = iio_priv(indio_dev);
+ int idx, ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ ret = ad_sigma_delta_single_conversion(indio_dev, chan, val);
+ if (ret < 0)
+ return ret;
+
+ /* After the conversion is performed, disable the channel */
+ ret = ad_sd_write_reg(&st->sd,
+ AD7124_CHANNEL(chan->address), 2,
+ st->channel_config[chan->address].ain |
+ AD7124_CHANNEL_EN(0));
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ idx = st->channel_config[chan->address].pga_bits;
+ *val = st->channel_config[chan->address].vref_mv;
+ if (st->channel_config[chan->address].bipolar)
+ *val2 = chan->scan_type.realbits - 1 + idx;
+ else
+ *val2 = chan->scan_type.realbits + idx;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_OFFSET:
+ if (st->channel_config[chan->address].bipolar)
+ *val = -(1 << (chan->scan_type.realbits - 1));
+ else
+ *val = 0;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = st->channel_config[chan->address].odr;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad7124_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct ad7124_state *st = iio_priv(indio_dev);
+ unsigned int res, gain, full_scale, vref;
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (val2 != 0)
+ return -EINVAL;
+
+ return ad7124_set_channel_odr(st, chan->address, val);
+ case IIO_CHAN_INFO_SCALE:
+ if (val != 0)
+ return -EINVAL;
+
+ if (st->channel_config[chan->address].bipolar)
+ full_scale = 1 << (chan->scan_type.realbits - 1);
+ else
+ full_scale = 1 << chan->scan_type.realbits;
+
+ vref = st->channel_config[chan->address].vref_mv * 1000000LL;
+ res = DIV_ROUND_CLOSEST(vref, full_scale);
+ gain = DIV_ROUND_CLOSEST(res, val2);
+
+ return ad7124_set_channel_gain(st, chan->address, gain);
+ default:
+ return -EINVAL;
+ }
+}
+
+static IIO_CONST_ATTR(in_voltage_scale_available,
+ "0.000001164 0.000002328 0.000004656 0.000009313 0.000018626 0.000037252 0.000074505 0.000149011 0.000298023");
+
+static struct attribute *ad7124_attributes[] = {
+ &iio_const_attr_in_voltage_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group ad7124_attrs_group = {
+ .attrs = ad7124_attributes,
+};
+
+static const struct iio_info ad7124_info = {
+ .read_raw = ad7124_read_raw,
+ .write_raw = ad7124_write_raw,
+ .validate_trigger = ad_sd_validate_trigger,
+ .attrs = &ad7124_attrs_group,
+};
+
+static int ad7124_soft_reset(struct ad7124_state *st)
+{
+ unsigned int readval, timeout;
+ int ret;
+
+ ret = ad_sd_reset(&st->sd, 64);
+ if (ret < 0)
+ return ret;
+
+ timeout = 100;
+ do {
+ ret = ad_sd_read_reg(&st->sd, AD7124_STATUS, 1, &readval);
+ if (ret < 0)
+ return ret;
+
+ if (!(readval & AD7124_STATUS_POR_FLAG_MSK))
+ return 0;
+
+ /* The AD7124 requires typically 2ms to power up and settle */
+ usleep_range(100, 2000);
+ } while (--timeout);
+
+ dev_err(&st->sd.spi->dev, "Soft reset failed\n");
+
+ return -EIO;
+}
+
+static int ad7124_init_channel_vref(struct ad7124_state *st,
+ unsigned int channel_number)
+{
+ unsigned int refsel = st->channel_config[channel_number].refsel;
+
+ switch (refsel) {
+ case AD7124_REFIN1:
+ case AD7124_REFIN2:
+ case AD7124_AVDD_REF:
+ if (IS_ERR(st->vref[refsel])) {
+ dev_err(&st->sd.spi->dev,
+ "Error, trying to use external voltage reference without a %s regulator.\n",
+ ad7124_ref_names[refsel]);
+ return PTR_ERR(st->vref[refsel]);
+ }
+ st->channel_config[channel_number].vref_mv =
+ regulator_get_voltage(st->vref[refsel]);
+ /* Conversion from uV to mV */
+ st->channel_config[channel_number].vref_mv /= 1000;
+ break;
+ case AD7124_INT_REF:
+ st->channel_config[channel_number].vref_mv = 2500;
+ break;
+ default:
+ dev_err(&st->sd.spi->dev, "Invalid reference %d\n", refsel);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ad7124_of_parse_channel_config(struct iio_dev *indio_dev,
+ struct device_node *np)
+{
+ struct ad7124_state *st = iio_priv(indio_dev);
+ struct device_node *child;
+ struct iio_chan_spec *chan;
+ unsigned int ain[2], channel = 0, tmp;
+ int ret;
+
+ st->num_channels = of_get_available_child_count(np);
+ if (!st->num_channels) {
+ dev_err(indio_dev->dev.parent, "no channel children\n");
+ return -ENODEV;
+ }
+
+ chan = devm_kcalloc(indio_dev->dev.parent, st->num_channels,
+ sizeof(*chan), GFP_KERNEL);
+ if (!chan)
+ return -ENOMEM;
+
+ indio_dev->channels = chan;
+ indio_dev->num_channels = st->num_channels;
+
+ for_each_available_child_of_node(np, child) {
+ ret = of_property_read_u32(child, "reg", &channel);
+ if (ret)
+ goto err;
+
+ ret = of_property_read_u32_array(child, "diff-channels",
+ ain, 2);
+ if (ret)
+ goto err;
+
+ if (ain[0] >= st->chip_info->num_inputs ||
+ ain[1] >= st->chip_info->num_inputs) {
+ dev_err(indio_dev->dev.parent,
+ "Input pin number out of range.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ st->channel_config[channel].ain = AD7124_CHANNEL_AINP(ain[0]) |
+ AD7124_CHANNEL_AINM(ain[1]);
+ st->channel_config[channel].bipolar =
+ of_property_read_bool(child, "bipolar");
+
+ ret = of_property_read_u32(child, "adi,reference-select", &tmp);
+ if (ret)
+ st->channel_config[channel].refsel = AD7124_INT_REF;
+ else
+ st->channel_config[channel].refsel = tmp;
+
+ *chan = ad7124_channel_template;
+ chan->address = channel;
+ chan->scan_index = channel;
+ chan->channel = ain[0];
+ chan->channel2 = ain[1];
+
+ chan++;
+ }
+
+ return 0;
+err:
+ of_node_put(child);
+
+ return ret;
+}
+
+static int ad7124_setup(struct ad7124_state *st)
+{
+ unsigned int val, fclk, power_mode;
+ int i, ret;
+
+ fclk = clk_get_rate(st->mclk);
+ if (!fclk)
+ return -EINVAL;
+
+ /* The power mode changes the master clock frequency */
+ power_mode = ad7124_find_closest_match(ad7124_master_clk_freq_hz,
+ ARRAY_SIZE(ad7124_master_clk_freq_hz),
+ fclk);
+ if (fclk != ad7124_master_clk_freq_hz[power_mode]) {
+ ret = clk_set_rate(st->mclk, fclk);
+ if (ret)
+ return ret;
+ }
+
+ /* Set the power mode */
+ st->adc_control &= ~AD7124_ADC_CTRL_PWR_MSK;
+ st->adc_control |= AD7124_ADC_CTRL_PWR(power_mode);
+ ret = ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, st->adc_control);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < st->num_channels; i++) {
+ val = st->channel_config[i].ain | AD7124_CHANNEL_SETUP(i);
+ ret = ad_sd_write_reg(&st->sd, AD7124_CHANNEL(i), 2, val);
+ if (ret < 0)
+ return ret;
+
+ ret = ad7124_init_channel_vref(st, i);
+ if (ret < 0)
+ return ret;
+
+ val = AD7124_CONFIG_BIPOLAR(st->channel_config[i].bipolar) |
+ AD7124_CONFIG_REF_SEL(st->channel_config[i].refsel);
+ ret = ad_sd_write_reg(&st->sd, AD7124_CONFIG(i), 2, val);
+ if (ret < 0)
+ return ret;
+ /*
+ * 9.38 SPS is the minimum output data rate supported
+ * regardless of the selected power mode. Round it up to 10 and
+ * set all the enabled channels to this default value.
+ */
+ ret = ad7124_set_channel_odr(st, i, 10);
+ }
+
+ return ret;
+}
+
+static int ad7124_probe(struct spi_device *spi)
+{
+ const struct spi_device_id *id;
+ struct ad7124_state *st;
+ struct iio_dev *indio_dev;
+ int i, ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ id = spi_get_device_id(spi);
+ st->chip_info = &ad7124_chip_info_tbl[id->driver_data];
+
+ ad_sd_init(&st->sd, indio_dev, spi, &ad7124_sigma_delta_info);
+
+ spi_set_drvdata(spi, indio_dev);
+
+ indio_dev->dev.parent = &spi->dev;
+ indio_dev->name = spi_get_device_id(spi)->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ad7124_info;
+
+ ret = ad7124_of_parse_channel_config(indio_dev, spi->dev.of_node);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(st->vref); i++) {
+ if (i == AD7124_INT_REF)
+ continue;
+
+ st->vref[i] = devm_regulator_get_optional(&spi->dev,
+ ad7124_ref_names[i]);
+ if (PTR_ERR(st->vref[i]) == -ENODEV)
+ continue;
+ else if (IS_ERR(st->vref[i]))
+ return PTR_ERR(st->vref[i]);
+
+ ret = regulator_enable(st->vref[i]);
+ if (ret)
+ return ret;
+ }
+
+ st->mclk = devm_clk_get(&spi->dev, "mclk");
+ if (IS_ERR(st->mclk)) {
+ ret = PTR_ERR(st->mclk);
+ goto error_regulator_disable;
+ }
+
+ ret = clk_prepare_enable(st->mclk);
+ if (ret < 0)
+ goto error_regulator_disable;
+
+ ret = ad7124_soft_reset(st);
+ if (ret < 0)
+ goto error_clk_disable_unprepare;
+
+ ret = ad7124_setup(st);
+ if (ret < 0)
+ goto error_clk_disable_unprepare;
+
+ ret = ad_sd_setup_buffer_and_trigger(indio_dev);
+ if (ret < 0)
+ goto error_clk_disable_unprepare;
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(&spi->dev, "Failed to register iio device\n");
+ goto error_remove_trigger;
+ }
+
+ return 0;
+
+error_remove_trigger:
+ ad_sd_cleanup_buffer_and_trigger(indio_dev);
+error_clk_disable_unprepare:
+ clk_disable_unprepare(st->mclk);
+error_regulator_disable:
+ for (i = ARRAY_SIZE(st->vref) - 1; i >= 0; i--) {
+ if (!IS_ERR_OR_NULL(st->vref[i]))
+ regulator_disable(st->vref[i]);
+ }
+
+ return ret;
+}
+
+static int ad7124_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct ad7124_state *st = iio_priv(indio_dev);
+ int i;
+
+ iio_device_unregister(indio_dev);
+ ad_sd_cleanup_buffer_and_trigger(indio_dev);
+ clk_disable_unprepare(st->mclk);
+
+ for (i = ARRAY_SIZE(st->vref) - 1; i >= 0; i--) {
+ if (!IS_ERR_OR_NULL(st->vref[i]))
+ regulator_disable(st->vref[i]);
+ }
+
+ return 0;
+}
+
+static const struct spi_device_id ad7124_id_table[] = {
+ { "ad7124-4", ID_AD7124_4 },
+ { "ad7124-8", ID_AD7124_8 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ad7124_id_table);
+
+static const struct of_device_id ad7124_of_match[] = {
+ { .compatible = "adi,ad7124-4" },
+ { .compatible = "adi,ad7124-8" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ad7124_of_match);
+
+static struct spi_driver ad71124_driver = {
+ .driver = {
+ .name = "ad7124",
+ .of_match_table = ad7124_of_match,
+ },
+ .probe = ad7124_probe,
+ .remove = ad7124_remove,
+ .id_table = ad7124_id_table,
+};
+module_spi_driver(ad71124_driver);
+
+MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7124 SPI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/ad7476.c b/drivers/iio/adc/ad7476.c
index 0549686b9ef8..76747488044b 100644
--- a/drivers/iio/adc/ad7476.c
+++ b/drivers/iio/adc/ad7476.c
@@ -59,6 +59,9 @@ enum ad7476_supported_device_ids {
ID_ADC081S,
ID_ADC101S,
ID_ADC121S,
+ ID_ADS7866,
+ ID_ADS7867,
+ ID_ADS7868,
};
static irqreturn_t ad7476_trigger_handler(int irq, void *p)
@@ -157,6 +160,8 @@ static int ad7476_read_raw(struct iio_dev *indio_dev,
#define AD7940_CHAN(bits) _AD7476_CHAN((bits), 15 - (bits), \
BIT(IIO_CHAN_INFO_RAW))
#define AD7091R_CHAN(bits) _AD7476_CHAN((bits), 16 - (bits), 0)
+#define ADS786X_CHAN(bits) _AD7476_CHAN((bits), 12 - (bits), \
+ BIT(IIO_CHAN_INFO_RAW))
static const struct ad7476_chip_info ad7476_chip_info_tbl[] = {
[ID_AD7091R] = {
@@ -209,6 +214,18 @@ static const struct ad7476_chip_info ad7476_chip_info_tbl[] = {
.channel[0] = ADC081S_CHAN(12),
.channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
},
+ [ID_ADS7866] = {
+ .channel[0] = ADS786X_CHAN(12),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ },
+ [ID_ADS7867] = {
+ .channel[0] = ADS786X_CHAN(10),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ },
+ [ID_ADS7868] = {
+ .channel[0] = ADS786X_CHAN(8),
+ .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1),
+ },
};
static const struct iio_info ad7476_info = {
@@ -314,6 +331,9 @@ static const struct spi_device_id ad7476_id[] = {
{"adc081s", ID_ADC081S},
{"adc101s", ID_ADC101S},
{"adc121s", ID_ADC121S},
+ {"ads7866", ID_ADS7866},
+ {"ads7867", ID_ADS7867},
+ {"ads7868", ID_ADS7868},
{}
};
MODULE_DEVICE_TABLE(spi, ad7476_id);
diff --git a/drivers/iio/adc/ad7606.c b/drivers/iio/adc/ad7606.c
new file mode 100644
index 000000000000..ebb8de03bbce
--- /dev/null
+++ b/drivers/iio/adc/ad7606.c
@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AD7606 SPI ADC driver
+ *
+ * Copyright 2011 Analog Devices Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/util_macros.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#include "ad7606.h"
+
+/*
+ * Scales are computed as 5000/32768 and 10000/32768 respectively,
+ * so that when applied to the raw values they provide mV values
+ */
+static const unsigned int scale_avail[2] = {
+ 152588, 305176
+};
+
+static const unsigned int ad7606_oversampling_avail[7] = {
+ 1, 2, 4, 8, 16, 32, 64,
+};
+
+static int ad7606_reset(struct ad7606_state *st)
+{
+ if (st->gpio_reset) {
+ gpiod_set_value(st->gpio_reset, 1);
+ ndelay(100); /* t_reset >= 100ns */
+ gpiod_set_value(st->gpio_reset, 0);
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+static int ad7606_read_samples(struct ad7606_state *st)
+{
+ unsigned int num = st->chip_info->num_channels;
+ u16 *data = st->data;
+ int ret;
+
+ /*
+ * The frstdata signal is set to high while and after reading the sample
+ * of the first channel and low for all other channels. This can be used
+ * to check that the incoming data is correctly aligned. During normal
+ * operation the data should never become unaligned, but some glitch or
+ * electrostatic discharge might cause an extra read or clock cycle.
+ * Monitoring the frstdata signal allows to recover from such failure
+ * situations.
+ */
+
+ if (st->gpio_frstdata) {
+ ret = st->bops->read_block(st->dev, 1, data);
+ if (ret)
+ return ret;
+
+ if (!gpiod_get_value(st->gpio_frstdata)) {
+ ad7606_reset(st);
+ return -EIO;
+ }
+
+ data++;
+ num--;
+ }
+
+ return st->bops->read_block(st->dev, num, data);
+}
+
+static irqreturn_t ad7606_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ad7606_state *st = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&st->lock);
+
+ ret = ad7606_read_samples(st);
+ if (ret == 0)
+ iio_push_to_buffers_with_timestamp(indio_dev, st->data,
+ iio_get_time_ns(indio_dev));
+
+ iio_trigger_notify_done(indio_dev->trig);
+ /* The rising edge of the CONVST signal starts a new conversion. */
+ gpiod_set_value(st->gpio_convst, 1);
+
+ mutex_unlock(&st->lock);
+
+ return IRQ_HANDLED;
+}
+
+static int ad7606_scan_direct(struct iio_dev *indio_dev, unsigned int ch)
+{
+ struct ad7606_state *st = iio_priv(indio_dev);
+ int ret;
+
+ gpiod_set_value(st->gpio_convst, 1);
+ ret = wait_for_completion_timeout(&st->completion,
+ msecs_to_jiffies(1000));
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ goto error_ret;
+ }
+
+ ret = ad7606_read_samples(st);
+ if (ret == 0)
+ ret = st->data[ch];
+
+error_ret:
+ gpiod_set_value(st->gpio_convst, 0);
+
+ return ret;
+}
+
+static int ad7606_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long m)
+{
+ int ret;
+ struct ad7606_state *st = iio_priv(indio_dev);
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = ad7606_scan_direct(indio_dev, chan->address);
+ iio_device_release_direct_mode(indio_dev);
+
+ if (ret < 0)
+ return ret;
+ *val = (short)ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = scale_avail[st->range];
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *val = st->oversampling;
+ return IIO_VAL_INT;
+ }
+ return -EINVAL;
+}
+
+static ssize_t in_voltage_scale_available_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i, len = 0;
+
+ for (i = 0; i < ARRAY_SIZE(scale_avail); i++)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ",
+ scale_avail[i]);
+
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR_RO(in_voltage_scale_available, 0);
+
+static int ad7606_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct ad7606_state *st = iio_priv(indio_dev);
+ DECLARE_BITMAP(values, 3);
+ int i;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ mutex_lock(&st->lock);
+ i = find_closest(val2, scale_avail, ARRAY_SIZE(scale_avail));
+ gpiod_set_value(st->gpio_range, i);
+ st->range = i;
+ mutex_unlock(&st->lock);
+
+ return 0;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ if (val2)
+ return -EINVAL;
+ i = find_closest(val, ad7606_oversampling_avail,
+ ARRAY_SIZE(ad7606_oversampling_avail));
+
+ values[0] = i;
+
+ mutex_lock(&st->lock);
+ gpiod_set_array_value(ARRAY_SIZE(values), st->gpio_os->desc,
+ st->gpio_os->info, values);
+ st->oversampling = ad7606_oversampling_avail[i];
+ mutex_unlock(&st->lock);
+
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static IIO_CONST_ATTR(oversampling_ratio_available, "1 2 4 8 16 32 64");
+
+static struct attribute *ad7606_attributes_os_and_range[] = {
+ &iio_dev_attr_in_voltage_scale_available.dev_attr.attr,
+ &iio_const_attr_oversampling_ratio_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group ad7606_attribute_group_os_and_range = {
+ .attrs = ad7606_attributes_os_and_range,
+};
+
+static struct attribute *ad7606_attributes_os[] = {
+ &iio_const_attr_oversampling_ratio_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group ad7606_attribute_group_os = {
+ .attrs = ad7606_attributes_os,
+};
+
+static struct attribute *ad7606_attributes_range[] = {
+ &iio_dev_attr_in_voltage_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group ad7606_attribute_group_range = {
+ .attrs = ad7606_attributes_range,
+};
+
+#define AD760X_CHANNEL(num, mask) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = num, \
+ .address = num, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
+ .info_mask_shared_by_all = mask, \
+ .scan_index = num, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_CPU, \
+ }, \
+}
+
+#define AD7605_CHANNEL(num) \
+ AD760X_CHANNEL(num, 0)
+
+#define AD7606_CHANNEL(num) \
+ AD760X_CHANNEL(num, BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO))
+
+static const struct iio_chan_spec ad7605_channels[] = {
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+ AD7605_CHANNEL(0),
+ AD7605_CHANNEL(1),
+ AD7605_CHANNEL(2),
+ AD7605_CHANNEL(3),
+};
+
+static const struct iio_chan_spec ad7606_channels[] = {
+ IIO_CHAN_SOFT_TIMESTAMP(8),
+ AD7606_CHANNEL(0),
+ AD7606_CHANNEL(1),
+ AD7606_CHANNEL(2),
+ AD7606_CHANNEL(3),
+ AD7606_CHANNEL(4),
+ AD7606_CHANNEL(5),
+ AD7606_CHANNEL(6),
+ AD7606_CHANNEL(7),
+};
+
+static const struct ad7606_chip_info ad7606_chip_info_tbl[] = {
+ /* More devices added in future */
+ [ID_AD7605_4] = {
+ .channels = ad7605_channels,
+ .num_channels = 5,
+ },
+ [ID_AD7606_8] = {
+ .channels = ad7606_channels,
+ .num_channels = 9,
+ .has_oversampling = true,
+ },
+ [ID_AD7606_6] = {
+ .channels = ad7606_channels,
+ .num_channels = 7,
+ .has_oversampling = true,
+ },
+ [ID_AD7606_4] = {
+ .channels = ad7606_channels,
+ .num_channels = 5,
+ .has_oversampling = true,
+ },
+};
+
+static int ad7606_request_gpios(struct ad7606_state *st)
+{
+ struct device *dev = st->dev;
+
+ st->gpio_convst = devm_gpiod_get(dev, "adi,conversion-start",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_convst))
+ return PTR_ERR(st->gpio_convst);
+
+ st->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_reset))
+ return PTR_ERR(st->gpio_reset);
+
+ st->gpio_range = devm_gpiod_get_optional(dev, "adi,range",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_range))
+ return PTR_ERR(st->gpio_range);
+
+ st->gpio_standby = devm_gpiod_get_optional(dev, "standby",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(st->gpio_standby))
+ return PTR_ERR(st->gpio_standby);
+
+ st->gpio_frstdata = devm_gpiod_get_optional(dev, "adi,first-data",
+ GPIOD_IN);
+ if (IS_ERR(st->gpio_frstdata))
+ return PTR_ERR(st->gpio_frstdata);
+
+ if (!st->chip_info->has_oversampling)
+ return 0;
+
+ st->gpio_os = devm_gpiod_get_array_optional(dev,
+ "adi,oversampling-ratio",
+ GPIOD_OUT_LOW);
+ return PTR_ERR_OR_ZERO(st->gpio_os);
+}
+
+/*
+ * The BUSY signal indicates when conversions are in progress, so when a rising
+ * edge of CONVST is applied, BUSY goes logic high and transitions low at the
+ * end of the entire conversion process. The falling edge of the BUSY signal
+ * triggers this interrupt.
+ */
+static irqreturn_t ad7606_interrupt(int irq, void *dev_id)
+{
+ struct iio_dev *indio_dev = dev_id;
+ struct ad7606_state *st = iio_priv(indio_dev);
+
+ if (iio_buffer_enabled(indio_dev)) {
+ gpiod_set_value(st->gpio_convst, 0);
+ iio_trigger_poll_chained(st->trig);
+ } else {
+ complete(&st->completion);
+ }
+
+ return IRQ_HANDLED;
+};
+
+static int ad7606_validate_trigger(struct iio_dev *indio_dev,
+ struct iio_trigger *trig)
+{
+ struct ad7606_state *st = iio_priv(indio_dev);
+
+ if (st->trig != trig)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ad7606_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad7606_state *st = iio_priv(indio_dev);
+
+ iio_triggered_buffer_postenable(indio_dev);
+ gpiod_set_value(st->gpio_convst, 1);
+
+ return 0;
+}
+
+static int ad7606_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad7606_state *st = iio_priv(indio_dev);
+
+ gpiod_set_value(st->gpio_convst, 0);
+
+ return iio_triggered_buffer_predisable(indio_dev);
+}
+
+static const struct iio_buffer_setup_ops ad7606_buffer_ops = {
+ .postenable = &ad7606_buffer_postenable,
+ .predisable = &ad7606_buffer_predisable,
+};
+
+static const struct iio_info ad7606_info_no_os_or_range = {
+ .read_raw = &ad7606_read_raw,
+ .validate_trigger = &ad7606_validate_trigger,
+};
+
+static const struct iio_info ad7606_info_os_and_range = {
+ .read_raw = &ad7606_read_raw,
+ .write_raw = &ad7606_write_raw,
+ .attrs = &ad7606_attribute_group_os_and_range,
+ .validate_trigger = &ad7606_validate_trigger,
+};
+
+static const struct iio_info ad7606_info_os = {
+ .read_raw = &ad7606_read_raw,
+ .write_raw = &ad7606_write_raw,
+ .attrs = &ad7606_attribute_group_os,
+ .validate_trigger = &ad7606_validate_trigger,
+};
+
+static const struct iio_info ad7606_info_range = {
+ .read_raw = &ad7606_read_raw,
+ .write_raw = &ad7606_write_raw,
+ .attrs = &ad7606_attribute_group_range,
+ .validate_trigger = &ad7606_validate_trigger,
+};
+
+static const struct iio_trigger_ops ad7606_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+};
+
+static void ad7606_regulator_disable(void *data)
+{
+ struct ad7606_state *st = data;
+
+ regulator_disable(st->reg);
+}
+
+int ad7606_probe(struct device *dev, int irq, void __iomem *base_address,
+ const char *name, unsigned int id,
+ const struct ad7606_bus_ops *bops)
+{
+ struct ad7606_state *st;
+ int ret;
+ struct iio_dev *indio_dev;
+
+ 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;
+ mutex_init(&st->lock);
+ st->bops = bops;
+ st->base_address = base_address;
+ /* tied to logic low, analog input range is +/- 5V */
+ st->range = 0;
+ st->oversampling = 1;
+
+ st->reg = devm_regulator_get(dev, "avcc");
+ if (IS_ERR(st->reg))
+ return PTR_ERR(st->reg);
+
+ ret = regulator_enable(st->reg);
+ if (ret) {
+ dev_err(dev, "Failed to enable specified AVcc supply\n");
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, ad7606_regulator_disable, st);
+ if (ret)
+ return ret;
+
+ st->chip_info = &ad7606_chip_info_tbl[id];
+
+ ret = ad7606_request_gpios(st);
+ if (ret)
+ return ret;
+
+ indio_dev->dev.parent = dev;
+ if (st->gpio_os) {
+ if (st->gpio_range)
+ indio_dev->info = &ad7606_info_os_and_range;
+ else
+ indio_dev->info = &ad7606_info_os;
+ } else {
+ if (st->gpio_range)
+ indio_dev->info = &ad7606_info_range;
+ else
+ indio_dev->info = &ad7606_info_no_os_or_range;
+ }
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->name = name;
+ indio_dev->channels = st->chip_info->channels;
+ indio_dev->num_channels = st->chip_info->num_channels;
+
+ init_completion(&st->completion);
+
+ ret = ad7606_reset(st);
+ if (ret)
+ dev_warn(st->dev, "failed to RESET: no RESET GPIO specified\n");
+
+ st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name, indio_dev->id);
+ if (!st->trig)
+ return -ENOMEM;
+
+ st->trig->ops = &ad7606_trigger_ops;
+ st->trig->dev.parent = dev;
+ iio_trigger_set_drvdata(st->trig, indio_dev);
+ ret = devm_iio_trigger_register(dev, st->trig);
+ if (ret)
+ return ret;
+
+ indio_dev->trig = iio_trigger_get(st->trig);
+
+ ret = devm_request_threaded_irq(dev, irq,
+ NULL,
+ &ad7606_interrupt,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ name, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &ad7606_trigger_handler,
+ &ad7606_buffer_ops);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_GPL(ad7606_probe);
+
+#ifdef CONFIG_PM_SLEEP
+
+static int ad7606_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ad7606_state *st = iio_priv(indio_dev);
+
+ if (st->gpio_standby) {
+ gpiod_set_value(st->gpio_range, 1);
+ gpiod_set_value(st->gpio_standby, 0);
+ }
+
+ return 0;
+}
+
+static int ad7606_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ad7606_state *st = iio_priv(indio_dev);
+
+ if (st->gpio_standby) {
+ gpiod_set_value(st->gpio_range, st->range);
+ gpiod_set_value(st->gpio_standby, 1);
+ ad7606_reset(st);
+ }
+
+ return 0;
+}
+
+SIMPLE_DEV_PM_OPS(ad7606_pm_ops, ad7606_suspend, ad7606_resume);
+EXPORT_SYMBOL_GPL(ad7606_pm_ops);
+
+#endif
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7606 ADC");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/ad7606.h b/drivers/iio/adc/ad7606.h
new file mode 100644
index 000000000000..5d12410f68e1
--- /dev/null
+++ b/drivers/iio/adc/ad7606.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AD7606 ADC driver
+ *
+ * Copyright 2011 Analog Devices Inc.
+ */
+
+#ifndef IIO_ADC_AD7606_H_
+#define IIO_ADC_AD7606_H_
+
+/**
+ * struct ad7606_chip_info - chip specific information
+ * @channels: channel specification
+ * @num_channels: number of channels
+ * @has_oversampling: whether the device has oversampling support
+ */
+struct ad7606_chip_info {
+ const struct iio_chan_spec *channels;
+ unsigned int num_channels;
+ bool has_oversampling;
+};
+
+/**
+ * struct ad7606_state - driver instance specific data
+ * @dev pointer to kernel device
+ * @chip_info entry in the table of chips that describes this device
+ * @reg regulator info for the the power supply of the device
+ * @bops bus operations (SPI or parallel)
+ * @range voltage range selection, selects which scale to apply
+ * @oversampling oversampling selection
+ * @base_address address from where to read data in parallel operation
+ * @lock protect sensor state from concurrent accesses to GPIOs
+ * @gpio_convst GPIO descriptor for conversion start signal (CONVST)
+ * @gpio_reset GPIO descriptor for device hard-reset
+ * @gpio_range GPIO descriptor for range selection
+ * @gpio_standby GPIO descriptor for stand-by signal (STBY),
+ * controls power-down mode of device
+ * @gpio_frstdata GPIO descriptor for reading from device when data
+ * is being read on the first channel
+ * @gpio_os GPIO descriptors to control oversampling on the device
+ * @complete completion to indicate end of conversion
+ * @trig The IIO trigger associated with the device.
+ * @data buffer for reading data from the device
+ */
+struct ad7606_state {
+ struct device *dev;
+ const struct ad7606_chip_info *chip_info;
+ struct regulator *reg;
+ const struct ad7606_bus_ops *bops;
+ unsigned int range;
+ unsigned int oversampling;
+ void __iomem *base_address;
+
+ struct mutex lock; /* protect sensor state */
+ struct gpio_desc *gpio_convst;
+ struct gpio_desc *gpio_reset;
+ struct gpio_desc *gpio_range;
+ struct gpio_desc *gpio_standby;
+ struct gpio_desc *gpio_frstdata;
+ struct gpio_descs *gpio_os;
+ struct iio_trigger *trig;
+ struct completion completion;
+
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ * 8 * 16-bit samples + 64-bit timestamp
+ */
+ unsigned short data[12] ____cacheline_aligned;
+};
+
+/**
+ * struct ad7606_bus_ops - driver bus operations
+ * @read_block function pointer for reading blocks of data
+ */
+struct ad7606_bus_ops {
+ /* more methods added in future? */
+ int (*read_block)(struct device *dev, int num, void *data);
+};
+
+int ad7606_probe(struct device *dev, int irq, void __iomem *base_address,
+ const char *name, unsigned int id,
+ const struct ad7606_bus_ops *bops);
+
+enum ad7606_supported_device_ids {
+ ID_AD7605_4,
+ ID_AD7606_8,
+ ID_AD7606_6,
+ ID_AD7606_4
+};
+
+#ifdef CONFIG_PM_SLEEP
+extern const struct dev_pm_ops ad7606_pm_ops;
+#define AD7606_PM_OPS (&ad7606_pm_ops)
+#else
+#define AD7606_PM_OPS NULL
+#endif
+
+#endif /* IIO_ADC_AD7606_H_ */
diff --git a/drivers/iio/adc/ad7606_par.c b/drivers/iio/adc/ad7606_par.c
new file mode 100644
index 000000000000..1b08028facde
--- /dev/null
+++ b/drivers/iio/adc/ad7606_par.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AD7606 Parallel Interface ADC driver
+ *
+ * Copyright 2011 Analog Devices Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/io.h>
+
+#include <linux/iio/iio.h>
+#include "ad7606.h"
+
+static int ad7606_par16_read_block(struct device *dev,
+ int count, void *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ad7606_state *st = iio_priv(indio_dev);
+
+ insw((unsigned long)st->base_address, buf, count);
+
+ return 0;
+}
+
+static const struct ad7606_bus_ops ad7606_par16_bops = {
+ .read_block = ad7606_par16_read_block,
+};
+
+static int ad7606_par8_read_block(struct device *dev,
+ int count, void *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ad7606_state *st = iio_priv(indio_dev);
+
+ insb((unsigned long)st->base_address, buf, count * 2);
+
+ return 0;
+}
+
+static const struct ad7606_bus_ops ad7606_par8_bops = {
+ .read_block = ad7606_par8_read_block,
+};
+
+static int ad7606_par_probe(struct platform_device *pdev)
+{
+ const struct platform_device_id *id = platform_get_device_id(pdev);
+ struct resource *res;
+ void __iomem *addr;
+ resource_size_t remap_size;
+ int irq;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq: %d\n", irq);
+ return irq;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ addr = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(addr))
+ return PTR_ERR(addr);
+
+ remap_size = resource_size(res);
+
+ return ad7606_probe(&pdev->dev, irq, addr,
+ id->name, id->driver_data,
+ remap_size > 1 ? &ad7606_par16_bops :
+ &ad7606_par8_bops);
+}
+
+static const struct platform_device_id ad7606_driver_ids[] = {
+ { .name = "ad7605-4", .driver_data = ID_AD7605_4, },
+ { .name = "ad7606-4", .driver_data = ID_AD7606_4, },
+ { .name = "ad7606-6", .driver_data = ID_AD7606_6, },
+ { .name = "ad7606-8", .driver_data = ID_AD7606_8, },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, ad7606_driver_ids);
+
+static const struct of_device_id ad7606_of_match[] = {
+ { .compatible = "adi,ad7605-4" },
+ { .compatible = "adi,ad7606-4" },
+ { .compatible = "adi,ad7606-6" },
+ { .compatible = "adi,ad7606-8" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ad7606_of_match);
+
+static struct platform_driver ad7606_driver = {
+ .probe = ad7606_par_probe,
+ .id_table = ad7606_driver_ids,
+ .driver = {
+ .name = "ad7606",
+ .pm = AD7606_PM_OPS,
+ .of_match_table = ad7606_of_match,
+ },
+};
+module_platform_driver(ad7606_driver);
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7606 ADC");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/ad7606_spi.c b/drivers/iio/adc/ad7606_spi.c
new file mode 100644
index 000000000000..4fd0ec36a086
--- /dev/null
+++ b/drivers/iio/adc/ad7606_spi.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AD7606 SPI ADC driver
+ *
+ * Copyright 2011 Analog Devices Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/err.h>
+
+#include <linux/iio/iio.h>
+#include "ad7606.h"
+
+#define MAX_SPI_FREQ_HZ 23500000 /* VDRIVE above 4.75 V */
+
+static int ad7606_spi_read_block(struct device *dev,
+ int count, void *buf)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ int i, ret;
+ unsigned short *data = buf;
+ __be16 *bdata = buf;
+
+ ret = spi_read(spi, buf, count * 2);
+ if (ret < 0) {
+ dev_err(&spi->dev, "SPI read error\n");
+ return ret;
+ }
+
+ for (i = 0; i < count; i++)
+ data[i] = be16_to_cpu(bdata[i]);
+
+ return 0;
+}
+
+static const struct ad7606_bus_ops ad7606_spi_bops = {
+ .read_block = ad7606_spi_read_block,
+};
+
+static int ad7606_spi_probe(struct spi_device *spi)
+{
+ const struct spi_device_id *id = spi_get_device_id(spi);
+
+ return ad7606_probe(&spi->dev, spi->irq, NULL,
+ id->name, id->driver_data,
+ &ad7606_spi_bops);
+}
+
+static const struct spi_device_id ad7606_id_table[] = {
+ { "ad7605-4", ID_AD7605_4 },
+ { "ad7606-4", ID_AD7606_4 },
+ { "ad7606-6", ID_AD7606_6 },
+ { "ad7606-8", ID_AD7606_8 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ad7606_id_table);
+
+static const struct of_device_id ad7606_of_match[] = {
+ { .compatible = "adi,ad7605-4" },
+ { .compatible = "adi,ad7606-4" },
+ { .compatible = "adi,ad7606-6" },
+ { .compatible = "adi,ad7606-8" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ad7606_of_match);
+
+static struct spi_driver ad7606_driver = {
+ .driver = {
+ .name = "ad7606",
+ .of_match_table = ad7606_of_match,
+ .pm = AD7606_PM_OPS,
+ },
+ .probe = ad7606_spi_probe,
+ .id_table = ad7606_id_table,
+};
+module_spi_driver(ad7606_driver);
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7606 ADC");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c
new file mode 100644
index 000000000000..0d132708c429
--- /dev/null
+++ b/drivers/iio/adc/ad7768-1.c
@@ -0,0 +1,655 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices AD7768-1 SPI ADC driver
+ *
+ * Copyright 2017 Analog Devices Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.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>
+
+/* AD7768 registers definition */
+#define AD7768_REG_CHIP_TYPE 0x3
+#define AD7768_REG_PROD_ID_L 0x4
+#define AD7768_REG_PROD_ID_H 0x5
+#define AD7768_REG_CHIP_GRADE 0x6
+#define AD7768_REG_SCRATCH_PAD 0x0A
+#define AD7768_REG_VENDOR_L 0x0C
+#define AD7768_REG_VENDOR_H 0x0D
+#define AD7768_REG_INTERFACE_FORMAT 0x14
+#define AD7768_REG_POWER_CLOCK 0x15
+#define AD7768_REG_ANALOG 0x16
+#define AD7768_REG_ANALOG2 0x17
+#define AD7768_REG_CONVERSION 0x18
+#define AD7768_REG_DIGITAL_FILTER 0x19
+#define AD7768_REG_SINC3_DEC_RATE_MSB 0x1A
+#define AD7768_REG_SINC3_DEC_RATE_LSB 0x1B
+#define AD7768_REG_DUTY_CYCLE_RATIO 0x1C
+#define AD7768_REG_SYNC_RESET 0x1D
+#define AD7768_REG_GPIO_CONTROL 0x1E
+#define AD7768_REG_GPIO_WRITE 0x1F
+#define AD7768_REG_GPIO_READ 0x20
+#define AD7768_REG_OFFSET_HI 0x21
+#define AD7768_REG_OFFSET_MID 0x22
+#define AD7768_REG_OFFSET_LO 0x23
+#define AD7768_REG_GAIN_HI 0x24
+#define AD7768_REG_GAIN_MID 0x25
+#define AD7768_REG_GAIN_LO 0x26
+#define AD7768_REG_SPI_DIAG_ENABLE 0x28
+#define AD7768_REG_ADC_DIAG_ENABLE 0x29
+#define AD7768_REG_DIG_DIAG_ENABLE 0x2A
+#define AD7768_REG_ADC_DATA 0x2C
+#define AD7768_REG_MASTER_STATUS 0x2D
+#define AD7768_REG_SPI_DIAG_STATUS 0x2E
+#define AD7768_REG_ADC_DIAG_STATUS 0x2F
+#define AD7768_REG_DIG_DIAG_STATUS 0x30
+#define AD7768_REG_MCLK_COUNTER 0x31
+
+/* AD7768_REG_POWER_CLOCK */
+#define AD7768_PWR_MCLK_DIV_MSK GENMASK(5, 4)
+#define AD7768_PWR_MCLK_DIV(x) FIELD_PREP(AD7768_PWR_MCLK_DIV_MSK, x)
+#define AD7768_PWR_PWRMODE_MSK GENMASK(1, 0)
+#define AD7768_PWR_PWRMODE(x) FIELD_PREP(AD7768_PWR_PWRMODE_MSK, x)
+
+/* AD7768_REG_DIGITAL_FILTER */
+#define AD7768_DIG_FIL_FIL_MSK GENMASK(6, 4)
+#define AD7768_DIG_FIL_FIL(x) FIELD_PREP(AD7768_DIG_FIL_FIL_MSK, x)
+#define AD7768_DIG_FIL_DEC_MSK GENMASK(2, 0)
+#define AD7768_DIG_FIL_DEC_RATE(x) FIELD_PREP(AD7768_DIG_FIL_DEC_MSK, x)
+
+/* AD7768_REG_CONVERSION */
+#define AD7768_CONV_MODE_MSK GENMASK(2, 0)
+#define AD7768_CONV_MODE(x) FIELD_PREP(AD7768_CONV_MODE_MSK, x)
+
+#define AD7768_RD_FLAG_MSK(x) (BIT(6) | ((x) & 0x3F))
+#define AD7768_WR_FLAG_MSK(x) ((x) & 0x3F)
+
+enum ad7768_conv_mode {
+ AD7768_CONTINUOUS,
+ AD7768_ONE_SHOT,
+ AD7768_SINGLE,
+ AD7768_PERIODIC,
+ AD7768_STANDBY
+};
+
+enum ad7768_pwrmode {
+ AD7768_ECO_MODE = 0,
+ AD7768_MED_MODE = 2,
+ AD7768_FAST_MODE = 3
+};
+
+enum ad7768_mclk_div {
+ AD7768_MCLK_DIV_16,
+ AD7768_MCLK_DIV_8,
+ AD7768_MCLK_DIV_4,
+ AD7768_MCLK_DIV_2
+};
+
+enum ad7768_dec_rate {
+ AD7768_DEC_RATE_32 = 0,
+ AD7768_DEC_RATE_64 = 1,
+ AD7768_DEC_RATE_128 = 2,
+ AD7768_DEC_RATE_256 = 3,
+ AD7768_DEC_RATE_512 = 4,
+ AD7768_DEC_RATE_1024 = 5,
+ AD7768_DEC_RATE_8 = 9,
+ AD7768_DEC_RATE_16 = 10
+};
+
+struct ad7768_clk_configuration {
+ enum ad7768_mclk_div mclk_div;
+ enum ad7768_dec_rate dec_rate;
+ unsigned int clk_div;
+ enum ad7768_pwrmode pwrmode;
+};
+
+static const struct ad7768_clk_configuration ad7768_clk_config[] = {
+ { AD7768_MCLK_DIV_2, AD7768_DEC_RATE_8, 16, AD7768_FAST_MODE },
+ { AD7768_MCLK_DIV_2, AD7768_DEC_RATE_16, 32, AD7768_FAST_MODE },
+ { AD7768_MCLK_DIV_2, AD7768_DEC_RATE_32, 64, AD7768_FAST_MODE },
+ { AD7768_MCLK_DIV_2, AD7768_DEC_RATE_64, 128, AD7768_FAST_MODE },
+ { AD7768_MCLK_DIV_2, AD7768_DEC_RATE_128, 256, AD7768_FAST_MODE },
+ { AD7768_MCLK_DIV_4, AD7768_DEC_RATE_128, 512, AD7768_MED_MODE },
+ { AD7768_MCLK_DIV_4, AD7768_DEC_RATE_256, 1024, AD7768_MED_MODE },
+ { AD7768_MCLK_DIV_4, AD7768_DEC_RATE_512, 2048, AD7768_MED_MODE },
+ { AD7768_MCLK_DIV_4, AD7768_DEC_RATE_1024, 4096, AD7768_MED_MODE },
+ { AD7768_MCLK_DIV_8, AD7768_DEC_RATE_1024, 8192, AD7768_MED_MODE },
+ { AD7768_MCLK_DIV_16, AD7768_DEC_RATE_1024, 16384, AD7768_ECO_MODE },
+};
+
+static const struct iio_chan_spec ad7768_channels[] = {
+ {
+ .type = IIO_VOLTAGE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .indexed = 1,
+ .channel = 0,
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 24,
+ .storagebits = 32,
+ .shift = 8,
+ .endianness = IIO_BE,
+ },
+ },
+};
+
+struct ad7768_state {
+ struct spi_device *spi;
+ struct regulator *vref;
+ struct mutex lock;
+ struct clk *mclk;
+ unsigned int mclk_freq;
+ unsigned int samp_freq;
+ struct completion completion;
+ struct iio_trigger *trig;
+ struct gpio_desc *gpio_sync_in;
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+ union {
+ __be32 d32;
+ u8 d8[2];
+ } data ____cacheline_aligned;
+};
+
+static int ad7768_spi_reg_read(struct ad7768_state *st, unsigned int addr,
+ unsigned int len)
+{
+ unsigned int shift;
+ int ret;
+
+ shift = 32 - (8 * len);
+ st->data.d8[0] = AD7768_RD_FLAG_MSK(addr);
+
+ ret = spi_write_then_read(st->spi, st->data.d8, 1,
+ &st->data.d32, len);
+ if (ret < 0)
+ return ret;
+
+ return (be32_to_cpu(st->data.d32) >> shift);
+}
+
+static int ad7768_spi_reg_write(struct ad7768_state *st,
+ unsigned int addr,
+ unsigned int val)
+{
+ st->data.d8[0] = AD7768_WR_FLAG_MSK(addr);
+ st->data.d8[1] = val & 0xFF;
+
+ return spi_write(st->spi, st->data.d8, 2);
+}
+
+static int ad7768_set_mode(struct ad7768_state *st,
+ enum ad7768_conv_mode mode)
+{
+ int regval;
+
+ regval = ad7768_spi_reg_read(st, AD7768_REG_CONVERSION, 1);
+ if (regval < 0)
+ return regval;
+
+ regval &= ~AD7768_CONV_MODE_MSK;
+ regval |= AD7768_CONV_MODE(mode);
+
+ return ad7768_spi_reg_write(st, AD7768_REG_CONVERSION, regval);
+}
+
+static int ad7768_scan_direct(struct iio_dev *indio_dev)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+ int readval, ret;
+
+ reinit_completion(&st->completion);
+
+ ret = ad7768_set_mode(st, AD7768_ONE_SHOT);
+ if (ret < 0)
+ return ret;
+
+ ret = wait_for_completion_timeout(&st->completion,
+ msecs_to_jiffies(1000));
+ if (!ret)
+ return -ETIMEDOUT;
+
+ readval = ad7768_spi_reg_read(st, AD7768_REG_ADC_DATA, 3);
+ if (readval < 0)
+ return readval;
+ /*
+ * Any SPI configuration of the AD7768-1 can only be
+ * performed in continuous conversion mode.
+ */
+ ret = ad7768_set_mode(st, AD7768_CONTINUOUS);
+ if (ret < 0)
+ return ret;
+
+ return readval;
+}
+
+static int ad7768_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg,
+ unsigned int writeval,
+ unsigned int *readval)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&st->lock);
+ if (readval) {
+ ret = ad7768_spi_reg_read(st, reg, 1);
+ if (ret < 0)
+ goto err_unlock;
+ *readval = ret;
+ ret = 0;
+ } else {
+ ret = ad7768_spi_reg_write(st, reg, writeval);
+ }
+err_unlock:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int ad7768_set_dig_fil(struct ad7768_state *st,
+ enum ad7768_dec_rate dec_rate)
+{
+ unsigned int mode;
+ int ret;
+
+ if (dec_rate == AD7768_DEC_RATE_8 || dec_rate == AD7768_DEC_RATE_16)
+ mode = AD7768_DIG_FIL_FIL(dec_rate);
+ else
+ mode = AD7768_DIG_FIL_DEC_RATE(dec_rate);
+
+ ret = ad7768_spi_reg_write(st, AD7768_REG_DIGITAL_FILTER, mode);
+ if (ret < 0)
+ return ret;
+
+ /* A sync-in pulse is required every time the filter dec rate changes */
+ gpiod_set_value(st->gpio_sync_in, 1);
+ gpiod_set_value(st->gpio_sync_in, 0);
+
+ return 0;
+}
+
+static int ad7768_set_freq(struct ad7768_state *st,
+ unsigned int freq)
+{
+ unsigned int diff_new, diff_old, pwr_mode, i, idx;
+ int res, ret;
+
+ diff_old = U32_MAX;
+ idx = 0;
+
+ res = DIV_ROUND_CLOSEST(st->mclk_freq, freq);
+
+ /* Find the closest match for the desired sampling frequency */
+ for (i = 0; i < ARRAY_SIZE(ad7768_clk_config); i++) {
+ diff_new = abs(res - ad7768_clk_config[i].clk_div);
+ if (diff_new < diff_old) {
+ diff_old = diff_new;
+ idx = i;
+ }
+ }
+
+ /*
+ * Set both the mclk_div and pwrmode with a single write to the
+ * POWER_CLOCK register
+ */
+ pwr_mode = AD7768_PWR_MCLK_DIV(ad7768_clk_config[idx].mclk_div) |
+ AD7768_PWR_PWRMODE(ad7768_clk_config[idx].pwrmode);
+ ret = ad7768_spi_reg_write(st, AD7768_REG_POWER_CLOCK, pwr_mode);
+ if (ret < 0)
+ return ret;
+
+ ret = ad7768_set_dig_fil(st, ad7768_clk_config[idx].dec_rate);
+ if (ret < 0)
+ return ret;
+
+ st->samp_freq = DIV_ROUND_CLOSEST(st->mclk_freq,
+ ad7768_clk_config[idx].clk_div);
+
+ return 0;
+}
+
+static ssize_t ad7768_sampling_freq_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct ad7768_state *st = iio_priv(indio_dev);
+ unsigned int freq;
+ int i, len = 0;
+
+ for (i = 0; i < ARRAY_SIZE(ad7768_clk_config); i++) {
+ freq = DIV_ROUND_CLOSEST(st->mclk_freq,
+ ad7768_clk_config[i].clk_div);
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", freq);
+ }
+
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(ad7768_sampling_freq_avail);
+
+static int ad7768_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+ int scale_uv, ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = ad7768_scan_direct(indio_dev);
+ if (ret >= 0)
+ *val = ret;
+
+ iio_device_release_direct_mode(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ scale_uv = regulator_get_voltage(st->vref);
+ if (scale_uv < 0)
+ return scale_uv;
+
+ *val = (scale_uv * 2) / 1000;
+ *val2 = chan->scan_type.realbits;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = st->samp_freq;
+
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int ad7768_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad7768_set_freq(st, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static struct attribute *ad7768_attributes[] = {
+ &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group ad7768_group = {
+ .attrs = ad7768_attributes,
+};
+
+static const struct iio_info ad7768_info = {
+ .attrs = &ad7768_group,
+ .read_raw = &ad7768_read_raw,
+ .write_raw = &ad7768_write_raw,
+ .debugfs_reg_access = &ad7768_reg_access,
+};
+
+static int ad7768_setup(struct ad7768_state *st)
+{
+ int ret;
+
+ /*
+ * Two writes to the SPI_RESET[1:0] bits are required to initiate
+ * a software reset. The bits must first be set to 11, and then
+ * to 10. When the sequence is detected, the reset occurs.
+ * See the datasheet, page 70.
+ */
+ ret = ad7768_spi_reg_write(st, AD7768_REG_SYNC_RESET, 0x3);
+ if (ret)
+ return ret;
+
+ ret = ad7768_spi_reg_write(st, AD7768_REG_SYNC_RESET, 0x2);
+ if (ret)
+ return ret;
+
+ st->gpio_sync_in = devm_gpiod_get(&st->spi->dev, "adi,sync-in",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(st->gpio_sync_in))
+ return PTR_ERR(st->gpio_sync_in);
+
+ /* Set the default sampling frequency to 32000 kSPS */
+ return ad7768_set_freq(st, 32000);
+}
+
+static irqreturn_t ad7768_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ad7768_state *st = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&st->lock);
+
+ ret = spi_read(st->spi, &st->data.d32, 3);
+ if (ret < 0)
+ goto err_unlock;
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &st->data.d32,
+ iio_get_time_ns(indio_dev));
+
+ iio_trigger_notify_done(indio_dev->trig);
+err_unlock:
+ mutex_unlock(&st->lock);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ad7768_interrupt(int irq, void *dev_id)
+{
+ struct iio_dev *indio_dev = dev_id;
+ struct ad7768_state *st = iio_priv(indio_dev);
+
+ if (iio_buffer_enabled(indio_dev))
+ iio_trigger_poll(st->trig);
+ else
+ complete(&st->completion);
+
+ return IRQ_HANDLED;
+};
+
+static int ad7768_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+
+ iio_triggered_buffer_postenable(indio_dev);
+ /*
+ * Write a 1 to the LSB of the INTERFACE_FORMAT register to enter
+ * continuous read mode. Subsequent data reads do not require an
+ * initial 8-bit write to query the ADC_DATA register.
+ */
+ return ad7768_spi_reg_write(st, AD7768_REG_INTERFACE_FORMAT, 0x01);
+}
+
+static int ad7768_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad7768_state *st = iio_priv(indio_dev);
+ int ret;
+
+ /*
+ * To exit continuous read mode, perform a single read of the ADC_DATA
+ * reg (0x2C), which allows further configuration of the device.
+ */
+ ret = ad7768_spi_reg_read(st, AD7768_REG_ADC_DATA, 3);
+ if (ret < 0)
+ return ret;
+
+ return iio_triggered_buffer_predisable(indio_dev);
+}
+
+static const struct iio_buffer_setup_ops ad7768_buffer_ops = {
+ .postenable = &ad7768_buffer_postenable,
+ .predisable = &ad7768_buffer_predisable,
+};
+
+static const struct iio_trigger_ops ad7768_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+};
+
+static void ad7768_regulator_disable(void *data)
+{
+ struct ad7768_state *st = data;
+
+ regulator_disable(st->vref);
+}
+
+static void ad7768_clk_disable(void *data)
+{
+ struct ad7768_state *st = data;
+
+ clk_disable_unprepare(st->mclk);
+}
+
+static int ad7768_probe(struct spi_device *spi)
+{
+ struct ad7768_state *st;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+
+ st->vref = devm_regulator_get(&spi->dev, "vref");
+ if (IS_ERR(st->vref))
+ return PTR_ERR(st->vref);
+
+ ret = regulator_enable(st->vref);
+ if (ret) {
+ dev_err(&spi->dev, "Failed to enable specified vref supply\n");
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(&spi->dev, ad7768_regulator_disable, st);
+ if (ret)
+ return ret;
+
+ st->mclk = devm_clk_get(&spi->dev, "mclk");
+ if (IS_ERR(st->mclk))
+ return PTR_ERR(st->mclk);
+
+ ret = clk_prepare_enable(st->mclk);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(&spi->dev, ad7768_clk_disable, st);
+ if (ret)
+ return ret;
+
+ st->mclk_freq = clk_get_rate(st->mclk);
+
+ spi_set_drvdata(spi, indio_dev);
+ mutex_init(&st->lock);
+
+ indio_dev->channels = ad7768_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ad7768_channels);
+ indio_dev->dev.parent = &spi->dev;
+ indio_dev->name = spi_get_device_id(spi)->name;
+ indio_dev->info = &ad7768_info;
+ indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_TRIGGERED;
+
+ ret = ad7768_setup(st);
+ if (ret < 0) {
+ dev_err(&spi->dev, "AD7768 setup failed\n");
+ return ret;
+ }
+
+ st->trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d",
+ indio_dev->name, indio_dev->id);
+ if (!st->trig)
+ return -ENOMEM;
+
+ st->trig->ops = &ad7768_trigger_ops;
+ st->trig->dev.parent = &spi->dev;
+ iio_trigger_set_drvdata(st->trig, indio_dev);
+ ret = devm_iio_trigger_register(&spi->dev, st->trig);
+ if (ret)
+ return ret;
+
+ indio_dev->trig = iio_trigger_get(st->trig);
+
+ init_completion(&st->completion);
+
+ ret = devm_request_irq(&spi->dev, spi->irq,
+ &ad7768_interrupt,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &ad7768_trigger_handler,
+ &ad7768_buffer_ops);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct spi_device_id ad7768_id_table[] = {
+ { "ad7768-1", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ad7768_id_table);
+
+static const struct of_device_id ad7768_of_match[] = {
+ { .compatible = "adi,ad7768-1" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ad7768_of_match);
+
+static struct spi_driver ad7768_driver = {
+ .driver = {
+ .name = "ad7768-1",
+ .of_match_table = ad7768_of_match,
+ },
+ .probe = ad7768_probe,
+ .id_table = ad7768_id_table,
+};
+module_spi_driver(ad7768_driver);
+
+MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7768-1 ADC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/ad7949.c b/drivers/iio/adc/ad7949.c
new file mode 100644
index 000000000000..ac0ffff6c5ae
--- /dev/null
+++ b/drivers/iio/adc/ad7949.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0
+/* ad7949.c - Analog Devices ADC driver 14/16 bits 4/8 channels
+ *
+ * Copyright (C) 2018 CMC NV
+ *
+ * http://www.analog.com/media/en/technical-documentation/data-sheets/AD7949.pdf
+ */
+
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#define AD7949_MASK_CHANNEL_SEL GENMASK(9, 7)
+#define AD7949_MASK_TOTAL GENMASK(13, 0)
+#define AD7949_OFFSET_CHANNEL_SEL 7
+#define AD7949_CFG_READ_BACK 0x1
+#define AD7949_CFG_REG_SIZE_BITS 14
+
+enum {
+ ID_AD7949 = 0,
+ ID_AD7682,
+ ID_AD7689,
+};
+
+struct ad7949_adc_spec {
+ u8 num_channels;
+ u8 resolution;
+};
+
+static const struct ad7949_adc_spec ad7949_adc_spec[] = {
+ [ID_AD7949] = { .num_channels = 8, .resolution = 14 },
+ [ID_AD7682] = { .num_channels = 4, .resolution = 16 },
+ [ID_AD7689] = { .num_channels = 8, .resolution = 16 },
+};
+
+/**
+ * struct ad7949_adc_chip - AD ADC chip
+ * @lock: protects write sequences
+ * @vref: regulator generating Vref
+ * @iio_dev: reference to iio structure
+ * @spi: reference to spi structure
+ * @resolution: resolution of the chip
+ * @cfg: copy of the configuration register
+ * @current_channel: current channel in use
+ * @buffer: buffer to send / receive data to / from device
+ */
+struct ad7949_adc_chip {
+ struct mutex lock;
+ struct regulator *vref;
+ struct iio_dev *indio_dev;
+ struct spi_device *spi;
+ u8 resolution;
+ u16 cfg;
+ unsigned int current_channel;
+ u32 buffer ____cacheline_aligned;
+};
+
+static bool ad7949_spi_cfg_is_read_back(struct ad7949_adc_chip *ad7949_adc)
+{
+ if (!(ad7949_adc->cfg & AD7949_CFG_READ_BACK))
+ return true;
+
+ return false;
+}
+
+static int ad7949_spi_bits_per_word(struct ad7949_adc_chip *ad7949_adc)
+{
+ int ret = ad7949_adc->resolution;
+
+ if (ad7949_spi_cfg_is_read_back(ad7949_adc))
+ ret += AD7949_CFG_REG_SIZE_BITS;
+
+ return ret;
+}
+
+static int ad7949_spi_write_cfg(struct ad7949_adc_chip *ad7949_adc, u16 val,
+ u16 mask)
+{
+ int ret;
+ int bits_per_word = ad7949_spi_bits_per_word(ad7949_adc);
+ int shift = bits_per_word - AD7949_CFG_REG_SIZE_BITS;
+ struct spi_message msg;
+ struct spi_transfer tx[] = {
+ {
+ .tx_buf = &ad7949_adc->buffer,
+ .len = 4,
+ .bits_per_word = bits_per_word,
+ },
+ };
+
+ ad7949_adc->cfg = (val & mask) | (ad7949_adc->cfg & ~mask);
+ ad7949_adc->buffer = ad7949_adc->cfg << shift;
+ spi_message_init_with_transfers(&msg, tx, 1);
+ ret = spi_sync(ad7949_adc->spi, &msg);
+
+ /*
+ * This delay is to avoid a new request before the required time to
+ * send a new command to the device
+ */
+ udelay(2);
+ return ret;
+}
+
+static int ad7949_spi_read_channel(struct ad7949_adc_chip *ad7949_adc, int *val,
+ unsigned int channel)
+{
+ int ret;
+ int bits_per_word = ad7949_spi_bits_per_word(ad7949_adc);
+ int mask = GENMASK(ad7949_adc->resolution, 0);
+ struct spi_message msg;
+ struct spi_transfer tx[] = {
+ {
+ .rx_buf = &ad7949_adc->buffer,
+ .len = 4,
+ .bits_per_word = bits_per_word,
+ },
+ };
+
+ ret = ad7949_spi_write_cfg(ad7949_adc,
+ channel << AD7949_OFFSET_CHANNEL_SEL,
+ AD7949_MASK_CHANNEL_SEL);
+ if (ret)
+ return ret;
+
+ ad7949_adc->buffer = 0;
+ spi_message_init_with_transfers(&msg, tx, 1);
+ ret = spi_sync(ad7949_adc->spi, &msg);
+ if (ret)
+ return ret;
+
+ /*
+ * This delay is to avoid a new request before the required time to
+ * send a new command to the device
+ */
+ udelay(2);
+
+ ad7949_adc->current_channel = channel;
+
+ if (ad7949_spi_cfg_is_read_back(ad7949_adc))
+ *val = (ad7949_adc->buffer >> AD7949_CFG_REG_SIZE_BITS) & mask;
+ else
+ *val = ad7949_adc->buffer & mask;
+
+ return 0;
+}
+
+#define AD7949_ADC_CHANNEL(chan) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = (chan), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec ad7949_adc_channels[] = {
+ AD7949_ADC_CHANNEL(0),
+ AD7949_ADC_CHANNEL(1),
+ AD7949_ADC_CHANNEL(2),
+ AD7949_ADC_CHANNEL(3),
+ AD7949_ADC_CHANNEL(4),
+ AD7949_ADC_CHANNEL(5),
+ AD7949_ADC_CHANNEL(6),
+ AD7949_ADC_CHANNEL(7),
+};
+
+static int ad7949_spi_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ad7949_adc_chip *ad7949_adc = iio_priv(indio_dev);
+ int ret;
+
+ if (!val)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&ad7949_adc->lock);
+ ret = ad7949_spi_read_channel(ad7949_adc, val, chan->channel);
+ mutex_unlock(&ad7949_adc->lock);
+
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ ret = regulator_get_voltage(ad7949_adc->vref);
+ if (ret < 0)
+ return ret;
+
+ *val = ret / 5000;
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int ad7949_spi_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval,
+ unsigned int *readval)
+{
+ struct ad7949_adc_chip *ad7949_adc = iio_priv(indio_dev);
+ int ret = 0;
+
+ if (readval)
+ *readval = ad7949_adc->cfg;
+ else
+ ret = ad7949_spi_write_cfg(ad7949_adc,
+ writeval & AD7949_MASK_TOTAL, AD7949_MASK_TOTAL);
+
+ return ret;
+}
+
+static const struct iio_info ad7949_spi_info = {
+ .read_raw = ad7949_spi_read_raw,
+ .debugfs_reg_access = ad7949_spi_reg_access,
+};
+
+static int ad7949_spi_init(struct ad7949_adc_chip *ad7949_adc)
+{
+ int ret;
+ int val;
+
+ /* Sequencer disabled, CFG readback disabled, IN0 as default channel */
+ ad7949_adc->current_channel = 0;
+ ret = ad7949_spi_write_cfg(ad7949_adc, 0x3C79, AD7949_MASK_TOTAL);
+
+ /*
+ * Do two dummy conversions to apply the first configuration setting.
+ * Required only after the start up of the device.
+ */
+ ad7949_spi_read_channel(ad7949_adc, &val, ad7949_adc->current_channel);
+ ad7949_spi_read_channel(ad7949_adc, &val, ad7949_adc->current_channel);
+
+ return ret;
+}
+
+static int ad7949_spi_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ const struct ad7949_adc_spec *spec;
+ struct ad7949_adc_chip *ad7949_adc;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*ad7949_adc));
+ if (!indio_dev) {
+ dev_err(dev, "can not allocate iio device\n");
+ return -ENOMEM;
+ }
+
+ indio_dev->dev.parent = dev;
+ indio_dev->dev.of_node = dev->of_node;
+ indio_dev->info = &ad7949_spi_info;
+ indio_dev->name = spi_get_device_id(spi)->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = ad7949_adc_channels;
+ spi_set_drvdata(spi, indio_dev);
+
+ ad7949_adc = iio_priv(indio_dev);
+ ad7949_adc->indio_dev = indio_dev;
+ ad7949_adc->spi = spi;
+
+ spec = &ad7949_adc_spec[spi_get_device_id(spi)->driver_data];
+ indio_dev->num_channels = spec->num_channels;
+ ad7949_adc->resolution = spec->resolution;
+
+ ad7949_adc->vref = devm_regulator_get(dev, "vref");
+ if (IS_ERR(ad7949_adc->vref)) {
+ dev_err(dev, "fail to request regulator\n");
+ return PTR_ERR(ad7949_adc->vref);
+ }
+
+ ret = regulator_enable(ad7949_adc->vref);
+ if (ret < 0) {
+ dev_err(dev, "fail to enable regulator\n");
+ return ret;
+ }
+
+ mutex_init(&ad7949_adc->lock);
+
+ ret = ad7949_spi_init(ad7949_adc);
+ if (ret) {
+ dev_err(dev, "enable to init this device: %d\n", ret);
+ goto err;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(dev, "fail to register iio device: %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ mutex_destroy(&ad7949_adc->lock);
+ regulator_disable(ad7949_adc->vref);
+
+ return ret;
+}
+
+static int ad7949_spi_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct ad7949_adc_chip *ad7949_adc = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ mutex_destroy(&ad7949_adc->lock);
+ regulator_disable(ad7949_adc->vref);
+
+ return 0;
+}
+
+static const struct of_device_id ad7949_spi_of_id[] = {
+ { .compatible = "adi,ad7949" },
+ { .compatible = "adi,ad7682" },
+ { .compatible = "adi,ad7689" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad7949_spi_of_id);
+
+static const struct spi_device_id ad7949_spi_id[] = {
+ { "ad7949", ID_AD7949 },
+ { "ad7682", ID_AD7682 },
+ { "ad7689", ID_AD7689 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad7949_spi_id);
+
+static struct spi_driver ad7949_spi_driver = {
+ .driver = {
+ .name = "ad7949",
+ .of_match_table = ad7949_spi_of_id,
+ },
+ .probe = ad7949_spi_probe,
+ .remove = ad7949_spi_remove,
+ .id_table = ad7949_spi_id,
+};
+module_spi_driver(ad7949_spi_driver);
+
+MODULE_AUTHOR("Charles-Antoine Couret <charles-antoine.couret@essensium.com>");
+MODULE_DESCRIPTION("Analog Devices 14/16-bit 8-channel ADC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c
index fc9510716ac7..54d9978b2740 100644
--- a/drivers/iio/adc/ad_sigma_delta.c
+++ b/drivers/iio/adc/ad_sigma_delta.c
@@ -121,6 +121,7 @@ static int ad_sd_read_reg_raw(struct ad_sigma_delta *sigma_delta,
if (sigma_delta->info->has_registers) {
data[0] = reg << sigma_delta->info->addr_shift;
data[0] |= sigma_delta->info->read_mask;
+ data[0] |= sigma_delta->comm;
spi_message_add_tail(&t[0], &m);
}
spi_message_add_tail(&t[1], &m);
@@ -278,6 +279,7 @@ int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev,
{
struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev);
unsigned int sample, raw_sample;
+ unsigned int data_reg;
int ret = 0;
if (iio_buffer_enabled(indio_dev))
@@ -305,7 +307,12 @@ int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev,
if (ret < 0)
goto out;
- ret = ad_sd_read_reg(sigma_delta, AD_SD_REG_DATA,
+ if (sigma_delta->info->data_reg != 0)
+ data_reg = sigma_delta->info->data_reg;
+ else
+ data_reg = AD_SD_REG_DATA;
+
+ ret = ad_sd_read_reg(sigma_delta, data_reg,
DIV_ROUND_UP(chan->scan_type.realbits + chan->scan_type.shift, 8),
&raw_sample);
@@ -392,6 +399,7 @@ static irqreturn_t ad_sd_trigger_handler(int irq, void *p)
struct iio_dev *indio_dev = pf->indio_dev;
struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev);
unsigned int reg_size;
+ unsigned int data_reg;
uint8_t data[16];
int ret;
@@ -401,18 +409,23 @@ static irqreturn_t ad_sd_trigger_handler(int irq, void *p)
indio_dev->channels[0].scan_type.shift;
reg_size = DIV_ROUND_UP(reg_size, 8);
+ if (sigma_delta->info->data_reg != 0)
+ data_reg = sigma_delta->info->data_reg;
+ else
+ data_reg = AD_SD_REG_DATA;
+
switch (reg_size) {
case 4:
case 2:
case 1:
- ret = ad_sd_read_reg_raw(sigma_delta, AD_SD_REG_DATA,
- reg_size, &data[0]);
+ ret = ad_sd_read_reg_raw(sigma_delta, data_reg, reg_size,
+ &data[0]);
break;
case 3:
/* We store 24 bit samples in a 32 bit word. Keep the upper
* byte set to zero. */
- ret = ad_sd_read_reg_raw(sigma_delta, AD_SD_REG_DATA,
- reg_size, &data[1]);
+ ret = ad_sd_read_reg_raw(sigma_delta, data_reg, reg_size,
+ &data[1]);
break;
}
diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c
index 75d2f73582a3..596841a3c4db 100644
--- a/drivers/iio/adc/at91_adc.c
+++ b/drivers/iio/adc/at91_adc.c
@@ -704,23 +704,29 @@ static int at91_adc_read_raw(struct iio_dev *idev,
ret = wait_event_interruptible_timeout(st->wq_data_avail,
st->done,
msecs_to_jiffies(1000));
- if (ret == 0)
- ret = -ETIMEDOUT;
- if (ret < 0) {
- mutex_unlock(&st->lock);
- return ret;
- }
-
- *val = st->last_value;
+ /* Disable interrupts, regardless if adc conversion was
+ * successful or not
+ */
at91_adc_writel(st, AT91_ADC_CHDR,
AT91_ADC_CH(chan->channel));
at91_adc_writel(st, AT91_ADC_IDR, BIT(chan->channel));
- st->last_value = 0;
- st->done = false;
+ if (ret > 0) {
+ /* a valid conversion took place */
+ *val = st->last_value;
+ st->last_value = 0;
+ st->done = false;
+ ret = IIO_VAL_INT;
+ } else if (ret == 0) {
+ /* conversion timeout */
+ dev_err(&idev->dev, "ADC Channel %d timeout.\n",
+ chan->channel);
+ ret = -ETIMEDOUT;
+ }
+
mutex_unlock(&st->lock);
- return IIO_VAL_INT;
+ return ret;
case IIO_CHAN_INFO_SCALE:
*val = st->vref_mv;
diff --git a/drivers/iio/adc/axp288_adc.c b/drivers/iio/adc/axp288_adc.c
index 031d568b4972..4e339cfd0c54 100644
--- a/drivers/iio/adc/axp288_adc.c
+++ b/drivers/iio/adc/axp288_adc.c
@@ -27,9 +27,18 @@
#include <linux/iio/machine.h>
#include <linux/iio/driver.h>
-#define AXP288_ADC_EN_MASK 0xF1
-#define AXP288_ADC_TS_PIN_GPADC 0xF2
-#define AXP288_ADC_TS_PIN_ON 0xF3
+/*
+ * This mask enables all ADCs except for the battery temp-sensor (TS), that is
+ * left as-is to avoid breaking charging on devices without a temp-sensor.
+ */
+#define AXP288_ADC_EN_MASK 0xF0
+#define AXP288_ADC_TS_ENABLE 0x01
+
+#define AXP288_ADC_TS_CURRENT_ON_OFF_MASK GENMASK(1, 0)
+#define AXP288_ADC_TS_CURRENT_OFF (0 << 0)
+#define AXP288_ADC_TS_CURRENT_ON_WHEN_CHARGING (1 << 0)
+#define AXP288_ADC_TS_CURRENT_ON_ONDEMAND (2 << 0)
+#define AXP288_ADC_TS_CURRENT_ON (3 << 0)
enum axp288_adc_id {
AXP288_ADC_TS,
@@ -44,6 +53,7 @@ enum axp288_adc_id {
struct axp288_adc_info {
int irq;
struct regmap *regmap;
+ bool ts_enabled;
};
static const struct iio_chan_spec axp288_adc_channels[] = {
@@ -115,21 +125,33 @@ static int axp288_adc_read_channel(int *val, unsigned long address,
return IIO_VAL_INT;
}
-static int axp288_adc_set_ts(struct regmap *regmap, unsigned int mode,
- unsigned long address)
+/*
+ * The current-source used for the battery temp-sensor (TS) is shared
+ * with the GPADC. For proper fuel-gauge and charger operation the TS
+ * current-source needs to be permanently on. But to read the GPADC we
+ * need to temporary switch the TS current-source to ondemand, so that
+ * the GPADC can use it, otherwise we will always read an all 0 value.
+ */
+static int axp288_adc_set_ts(struct axp288_adc_info *info,
+ unsigned int mode, unsigned long address)
{
int ret;
- /* channels other than GPADC do not need to switch TS pin */
+ /* No need to switch the current-source if the TS pin is disabled */
+ if (!info->ts_enabled)
+ return 0;
+
+ /* Channels other than GPADC do not need the current source */
if (address != AXP288_GP_ADC_H)
return 0;
- ret = regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, mode);
+ ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
+ AXP288_ADC_TS_CURRENT_ON_OFF_MASK, mode);
if (ret)
return ret;
/* When switching to the GPADC pin give things some time to settle */
- if (mode == AXP288_ADC_TS_PIN_GPADC)
+ if (mode == AXP288_ADC_TS_CURRENT_ON_ONDEMAND)
usleep_range(6000, 10000);
return 0;
@@ -145,14 +167,14 @@ static int axp288_adc_read_raw(struct iio_dev *indio_dev,
mutex_lock(&indio_dev->mlock);
switch (mask) {
case IIO_CHAN_INFO_RAW:
- if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_GPADC,
+ if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON_ONDEMAND,
chan->address)) {
dev_err(&indio_dev->dev, "GPADC mode\n");
ret = -EINVAL;
break;
}
ret = axp288_adc_read_channel(val, chan->address, info->regmap);
- if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_ON,
+ if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON,
chan->address))
dev_err(&indio_dev->dev, "TS pin restore\n");
break;
@@ -164,13 +186,35 @@ static int axp288_adc_read_raw(struct iio_dev *indio_dev,
return ret;
}
-static int axp288_adc_set_state(struct regmap *regmap)
+static int axp288_adc_initialize(struct axp288_adc_info *info)
{
- /* ADC should be always enabled for internal FG to function */
- if (regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, AXP288_ADC_TS_PIN_ON))
- return -EIO;
+ int ret, adc_enable_val;
+
+ /*
+ * Determine if the TS pin is enabled and set the TS current-source
+ * accordingly.
+ */
+ ret = regmap_read(info->regmap, AXP20X_ADC_EN1, &adc_enable_val);
+ if (ret)
+ return ret;
+
+ if (adc_enable_val & AXP288_ADC_TS_ENABLE) {
+ info->ts_enabled = true;
+ ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
+ AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
+ AXP288_ADC_TS_CURRENT_ON);
+ } else {
+ info->ts_enabled = false;
+ ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
+ AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
+ AXP288_ADC_TS_CURRENT_OFF);
+ }
+ if (ret)
+ return ret;
- return regmap_write(regmap, AXP20X_ADC_EN1, AXP288_ADC_EN_MASK);
+ /* Turn on the ADC for all channels except TS, leave TS as is */
+ return regmap_update_bits(info->regmap, AXP20X_ADC_EN1,
+ AXP288_ADC_EN_MASK, AXP288_ADC_EN_MASK);
}
static const struct iio_info axp288_adc_iio_info = {
@@ -200,7 +244,7 @@ static int axp288_adc_probe(struct platform_device *pdev)
* Set ADC to enabled state at all time, including system suspend.
* otherwise internal fuel gauge functionality may be affected.
*/
- ret = axp288_adc_set_state(axp20x->regmap);
+ ret = axp288_adc_initialize(info);
if (ret) {
dev_err(&pdev->dev, "unable to enable ADC device\n");
return ret;
diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c
index f10443f92e4c..1ca2c4d39f87 100644
--- a/drivers/iio/adc/exynos_adc.c
+++ b/drivers/iio/adc/exynos_adc.c
@@ -115,6 +115,8 @@
#define MAX_ADC_V2_CHANNELS 10
#define MAX_ADC_V1_CHANNELS 8
#define MAX_EXYNOS3250_ADC_CHANNELS 2
+#define MAX_EXYNOS4212_ADC_CHANNELS 4
+#define MAX_S5PV210_ADC_CHANNELS 10
/* Bit definitions common for ADC_V1 and ADC_V2 */
#define ADC_CON_EN_START (1u << 0)
@@ -270,6 +272,19 @@ static void exynos_adc_v1_start_conv(struct exynos_adc *info,
writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs));
}
+/* Exynos4212 and 4412 is like ADCv1 but with four channels only */
+static const struct exynos_adc_data exynos4212_adc_data = {
+ .num_channels = MAX_EXYNOS4212_ADC_CHANNELS,
+ .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */
+ .needs_adc_phy = true,
+ .phy_offset = EXYNOS_ADCV1_PHY_OFFSET,
+
+ .init_hw = exynos_adc_v1_init_hw,
+ .exit_hw = exynos_adc_v1_exit_hw,
+ .clear_irq = exynos_adc_v1_clear_irq,
+ .start_conv = exynos_adc_v1_start_conv,
+};
+
static const struct exynos_adc_data exynos_adc_v1_data = {
.num_channels = MAX_ADC_V1_CHANNELS,
.mask = ADC_DATX_MASK, /* 12 bit ADC resolution */
@@ -282,6 +297,16 @@ static const struct exynos_adc_data exynos_adc_v1_data = {
.start_conv = exynos_adc_v1_start_conv,
};
+static const struct exynos_adc_data exynos_adc_s5pv210_data = {
+ .num_channels = MAX_S5PV210_ADC_CHANNELS,
+ .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */
+
+ .init_hw = exynos_adc_v1_init_hw,
+ .exit_hw = exynos_adc_v1_exit_hw,
+ .clear_irq = exynos_adc_v1_clear_irq,
+ .start_conv = exynos_adc_v1_start_conv,
+};
+
static void exynos_adc_s3c2416_start_conv(struct exynos_adc *info,
unsigned long addr)
{
@@ -479,6 +504,12 @@ static const struct of_device_id exynos_adc_match[] = {
.compatible = "samsung,s3c6410-adc",
.data = &exynos_adc_s3c64xx_data,
}, {
+ .compatible = "samsung,s5pv210-adc",
+ .data = &exynos_adc_s5pv210_data,
+ }, {
+ .compatible = "samsung,exynos4212-adc",
+ .data = &exynos4212_adc_data,
+ }, {
.compatible = "samsung,exynos-adc-v1",
.data = &exynos_adc_v1_data,
}, {
@@ -915,7 +946,7 @@ static int exynos_adc_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct exynos_adc *info = iio_priv(indio_dev);
- if (IS_REACHABLE(CONFIG_INPUT)) {
+ if (IS_REACHABLE(CONFIG_INPUT) && info->input) {
free_irq(info->tsirq, info);
input_unregister_device(info->input);
}
diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c
index d1239624187d..bdd7cba6f6b0 100644
--- a/drivers/iio/adc/ina2xx-adc.c
+++ b/drivers/iio/adc/ina2xx-adc.c
@@ -250,6 +250,7 @@ static int ina2xx_read_raw(struct iio_dev *indio_dev,
*val2 = chip->shunt_resistor_uohm;
return IIO_VAL_FRACTIONAL;
}
+ return -EINVAL;
case IIO_CHAN_INFO_HARDWAREGAIN:
switch (chan->address) {
@@ -262,6 +263,7 @@ static int ina2xx_read_raw(struct iio_dev *indio_dev,
*val = chip->range_vbus == 32 ? 1 : 2;
return IIO_VAL_INT;
}
+ return -EINVAL;
}
return -EINVAL;
diff --git a/drivers/iio/adc/ingenic-adc.c b/drivers/iio/adc/ingenic-adc.c
new file mode 100644
index 000000000000..6ee1673deb0d
--- /dev/null
+++ b/drivers/iio/adc/ingenic-adc.c
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ADC driver for the Ingenic JZ47xx SoCs
+ * Copyright (c) 2019 Artur Rojek <contact@artur-rojek.eu>
+ *
+ * based on drivers/mfd/jz4740-adc.c
+ */
+
+#include <dt-bindings/iio/adc/ingenic,adc.h>
+#include <linux/clk.h>
+#include <linux/iio/iio.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#define JZ_ADC_REG_ENABLE 0x00
+#define JZ_ADC_REG_CFG 0x04
+#define JZ_ADC_REG_CTRL 0x08
+#define JZ_ADC_REG_STATUS 0x0c
+#define JZ_ADC_REG_ADTCH 0x18
+#define JZ_ADC_REG_ADBDAT 0x1c
+#define JZ_ADC_REG_ADSDAT 0x20
+
+#define JZ_ADC_REG_CFG_BAT_MD BIT(4)
+
+#define JZ_ADC_AUX_VREF 3300
+#define JZ_ADC_AUX_VREF_BITS 12
+#define JZ_ADC_BATTERY_LOW_VREF 2500
+#define JZ_ADC_BATTERY_LOW_VREF_BITS 12
+#define JZ4725B_ADC_BATTERY_HIGH_VREF 7500
+#define JZ4725B_ADC_BATTERY_HIGH_VREF_BITS 10
+#define JZ4740_ADC_BATTERY_HIGH_VREF (7500 * 0.986)
+#define JZ4740_ADC_BATTERY_HIGH_VREF_BITS 12
+
+struct ingenic_adc_soc_data {
+ unsigned int battery_high_vref;
+ unsigned int battery_high_vref_bits;
+ const int *battery_raw_avail;
+ size_t battery_raw_avail_size;
+ const int *battery_scale_avail;
+ size_t battery_scale_avail_size;
+};
+
+struct ingenic_adc {
+ void __iomem *base;
+ struct clk *clk;
+ struct mutex lock;
+ const struct ingenic_adc_soc_data *soc_data;
+ bool low_vref_mode;
+};
+
+static void ingenic_adc_set_config(struct ingenic_adc *adc,
+ uint32_t mask,
+ uint32_t val)
+{
+ uint32_t cfg;
+
+ clk_enable(adc->clk);
+ mutex_lock(&adc->lock);
+
+ cfg = readl(adc->base + JZ_ADC_REG_CFG) & ~mask;
+ cfg |= val;
+ writel(cfg, adc->base + JZ_ADC_REG_CFG);
+
+ mutex_unlock(&adc->lock);
+ clk_disable(adc->clk);
+}
+
+static void ingenic_adc_enable(struct ingenic_adc *adc,
+ int engine,
+ bool enabled)
+{
+ u8 val;
+
+ mutex_lock(&adc->lock);
+ val = readb(adc->base + JZ_ADC_REG_ENABLE);
+
+ if (enabled)
+ val |= BIT(engine);
+ else
+ val &= ~BIT(engine);
+
+ writeb(val, adc->base + JZ_ADC_REG_ENABLE);
+ mutex_unlock(&adc->lock);
+}
+
+static int ingenic_adc_capture(struct ingenic_adc *adc,
+ int engine)
+{
+ u8 val;
+ int ret;
+
+ ingenic_adc_enable(adc, engine, true);
+ ret = readb_poll_timeout(adc->base + JZ_ADC_REG_ENABLE, val,
+ !(val & BIT(engine)), 250, 1000);
+ if (ret)
+ ingenic_adc_enable(adc, engine, false);
+
+ return ret;
+}
+
+static int ingenic_adc_write_raw(struct iio_dev *iio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long m)
+{
+ struct ingenic_adc *adc = iio_priv(iio_dev);
+
+ switch (m) {
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->channel) {
+ case INGENIC_ADC_BATTERY:
+ if (val > JZ_ADC_BATTERY_LOW_VREF) {
+ ingenic_adc_set_config(adc,
+ JZ_ADC_REG_CFG_BAT_MD,
+ 0);
+ adc->low_vref_mode = false;
+ } else {
+ ingenic_adc_set_config(adc,
+ JZ_ADC_REG_CFG_BAT_MD,
+ JZ_ADC_REG_CFG_BAT_MD);
+ adc->low_vref_mode = true;
+ }
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static const int jz4725b_adc_battery_raw_avail[] = {
+ 0, 1, (1 << JZ_ADC_BATTERY_LOW_VREF_BITS) - 1,
+};
+
+static const int jz4725b_adc_battery_scale_avail[] = {
+ JZ4725B_ADC_BATTERY_HIGH_VREF, JZ4725B_ADC_BATTERY_HIGH_VREF_BITS,
+ JZ_ADC_BATTERY_LOW_VREF, JZ_ADC_BATTERY_LOW_VREF_BITS,
+};
+
+static const int jz4740_adc_battery_raw_avail[] = {
+ 0, 1, (1 << JZ_ADC_BATTERY_LOW_VREF_BITS) - 1,
+};
+
+static const int jz4740_adc_battery_scale_avail[] = {
+ JZ4740_ADC_BATTERY_HIGH_VREF, JZ4740_ADC_BATTERY_HIGH_VREF_BITS,
+ JZ_ADC_BATTERY_LOW_VREF, JZ_ADC_BATTERY_LOW_VREF_BITS,
+};
+
+static const struct ingenic_adc_soc_data jz4725b_adc_soc_data = {
+ .battery_high_vref = JZ4725B_ADC_BATTERY_HIGH_VREF,
+ .battery_high_vref_bits = JZ4725B_ADC_BATTERY_HIGH_VREF_BITS,
+ .battery_raw_avail = jz4725b_adc_battery_raw_avail,
+ .battery_raw_avail_size = ARRAY_SIZE(jz4725b_adc_battery_raw_avail),
+ .battery_scale_avail = jz4725b_adc_battery_scale_avail,
+ .battery_scale_avail_size = ARRAY_SIZE(jz4725b_adc_battery_scale_avail),
+};
+
+static const struct ingenic_adc_soc_data jz4740_adc_soc_data = {
+ .battery_high_vref = JZ4740_ADC_BATTERY_HIGH_VREF,
+ .battery_high_vref_bits = JZ4740_ADC_BATTERY_HIGH_VREF_BITS,
+ .battery_raw_avail = jz4740_adc_battery_raw_avail,
+ .battery_raw_avail_size = ARRAY_SIZE(jz4740_adc_battery_raw_avail),
+ .battery_scale_avail = jz4740_adc_battery_scale_avail,
+ .battery_scale_avail_size = ARRAY_SIZE(jz4740_adc_battery_scale_avail),
+};
+
+static int ingenic_adc_read_avail(struct iio_dev *iio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals,
+ int *type,
+ int *length,
+ long m)
+{
+ struct ingenic_adc *adc = iio_priv(iio_dev);
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ *type = IIO_VAL_INT;
+ *length = adc->soc_data->battery_raw_avail_size;
+ *vals = adc->soc_data->battery_raw_avail;
+ return IIO_AVAIL_RANGE;
+ case IIO_CHAN_INFO_SCALE:
+ *type = IIO_VAL_FRACTIONAL_LOG2;
+ *length = adc->soc_data->battery_scale_avail_size;
+ *vals = adc->soc_data->battery_scale_avail;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ };
+}
+
+static int ingenic_adc_read_raw(struct iio_dev *iio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long m)
+{
+ struct ingenic_adc *adc = iio_priv(iio_dev);
+ int ret;
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ clk_enable(adc->clk);
+ ret = ingenic_adc_capture(adc, chan->channel);
+ if (ret) {
+ clk_disable(adc->clk);
+ return ret;
+ }
+
+ switch (chan->channel) {
+ case INGENIC_ADC_AUX:
+ *val = readw(adc->base + JZ_ADC_REG_ADSDAT);
+ break;
+ case INGENIC_ADC_BATTERY:
+ *val = readw(adc->base + JZ_ADC_REG_ADBDAT);
+ break;
+ }
+
+ clk_disable(adc->clk);
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->channel) {
+ case INGENIC_ADC_AUX:
+ *val = JZ_ADC_AUX_VREF;
+ *val2 = JZ_ADC_AUX_VREF_BITS;
+ break;
+ case INGENIC_ADC_BATTERY:
+ if (adc->low_vref_mode) {
+ *val = JZ_ADC_BATTERY_LOW_VREF;
+ *val2 = JZ_ADC_BATTERY_LOW_VREF_BITS;
+ } else {
+ *val = adc->soc_data->battery_high_vref;
+ *val2 = adc->soc_data->battery_high_vref_bits;
+ }
+ break;
+ }
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+}
+
+static void ingenic_adc_clk_cleanup(void *data)
+{
+ clk_unprepare(data);
+}
+
+static const struct iio_info ingenic_adc_info = {
+ .write_raw = ingenic_adc_write_raw,
+ .read_raw = ingenic_adc_read_raw,
+ .read_avail = ingenic_adc_read_avail,
+};
+
+static const struct iio_chan_spec ingenic_channels[] = {
+ {
+ .extend_name = "aux",
+ .type = IIO_VOLTAGE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .indexed = 1,
+ .channel = INGENIC_ADC_AUX,
+ },
+ {
+ .extend_name = "battery",
+ .type = IIO_VOLTAGE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .indexed = 1,
+ .channel = INGENIC_ADC_BATTERY,
+ },
+};
+
+static int ingenic_adc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct iio_dev *iio_dev;
+ struct ingenic_adc *adc;
+ struct resource *mem_base;
+ const struct ingenic_adc_soc_data *soc_data;
+ int ret;
+
+ soc_data = device_get_match_data(dev);
+ if (!soc_data)
+ return -EINVAL;
+
+ iio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
+ if (!iio_dev)
+ return -ENOMEM;
+
+ adc = iio_priv(iio_dev);
+ mutex_init(&adc->lock);
+ adc->soc_data = soc_data;
+
+ mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ adc->base = devm_ioremap_resource(dev, mem_base);
+ if (IS_ERR(adc->base)) {
+ dev_err(dev, "Unable to ioremap mmio resource\n");
+ return PTR_ERR(adc->base);
+ }
+
+ adc->clk = devm_clk_get(dev, "adc");
+ if (IS_ERR(adc->clk)) {
+ dev_err(dev, "Unable to get clock\n");
+ return PTR_ERR(adc->clk);
+ }
+
+ ret = clk_prepare_enable(adc->clk);
+ if (ret) {
+ dev_err(dev, "Failed to enable clock\n");
+ return ret;
+ }
+
+ /* Put hardware in a known passive state. */
+ writeb(0x00, adc->base + JZ_ADC_REG_ENABLE);
+ writeb(0xff, adc->base + JZ_ADC_REG_CTRL);
+ clk_disable(adc->clk);
+
+ ret = devm_add_action_or_reset(dev, ingenic_adc_clk_cleanup, adc->clk);
+ if (ret) {
+ dev_err(dev, "Unable to add action\n");
+ return ret;
+ }
+
+ iio_dev->dev.parent = dev;
+ iio_dev->name = "jz-adc";
+ iio_dev->modes = INDIO_DIRECT_MODE;
+ iio_dev->channels = ingenic_channels;
+ iio_dev->num_channels = ARRAY_SIZE(ingenic_channels);
+ iio_dev->info = &ingenic_adc_info;
+
+ ret = devm_iio_device_register(dev, iio_dev);
+ if (ret)
+ dev_err(dev, "Unable to register IIO device\n");
+
+ return ret;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ingenic_adc_of_match[] = {
+ { .compatible = "ingenic,jz4725b-adc", .data = &jz4725b_adc_soc_data, },
+ { .compatible = "ingenic,jz4740-adc", .data = &jz4740_adc_soc_data, },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ingenic_adc_of_match);
+#endif
+
+static struct platform_driver ingenic_adc_driver = {
+ .driver = {
+ .name = "ingenic-adc",
+ .of_match_table = of_match_ptr(ingenic_adc_of_match),
+ },
+ .probe = ingenic_adc_probe,
+};
+module_platform_driver(ingenic_adc_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/lpc32xx_adc.c b/drivers/iio/adc/lpc32xx_adc.c
index 20b36690fa4f..e361c1532a75 100644
--- a/drivers/iio/adc/lpc32xx_adc.c
+++ b/drivers/iio/adc/lpc32xx_adc.c
@@ -1,23 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0+
/*
* lpc32xx_adc.c - Support for ADC in LPC32XX
*
* 3-channel, 10-bit ADC
*
* Copyright (C) 2011, 2012 Roland Stigge <stigge@antcom.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
diff --git a/drivers/iio/adc/max11100.c b/drivers/iio/adc/max11100.c
index af59ab2e650c..3440539cfdba 100644
--- a/drivers/iio/adc/max11100.c
+++ b/drivers/iio/adc/max11100.c
@@ -1,13 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* iio/adc/max11100.c
* Maxim max11100 ADC Driver with IIO interface
*
* Copyright (C) 2016-17 Renesas Electronics Corporation
* Copyright (C) 2016-17 Jacopo Mondi
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
#include <linux/delay.h>
#include <linux/kernel.h>
diff --git a/drivers/iio/adc/max9611.c b/drivers/iio/adc/max9611.c
index 643a4e66eb80..917223d5ff5b 100644
--- a/drivers/iio/adc/max9611.c
+++ b/drivers/iio/adc/max9611.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* iio/adc/max9611.c
*
@@ -5,10 +6,6 @@
* 12-bit ADC interface.
*
* Copyright (C) 2017 Jacopo Mondi
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
/*
diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c
index 028ccd218f82..f8600fbcdfe3 100644
--- a/drivers/iio/adc/meson_saradc.c
+++ b/drivers/iio/adc/meson_saradc.c
@@ -18,6 +18,7 @@
#include <linux/io.h>
#include <linux/iio/iio.h>
#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
@@ -25,6 +26,7 @@
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
+#include <linux/mfd/syscon.h>
#define MESON_SAR_ADC_REG0 0x00
#define MESON_SAR_ADC_REG0_PANEL_DETECT BIT(31)
@@ -165,6 +167,17 @@
#define MESON_SAR_ADC_MAX_FIFO_SIZE 32
#define MESON_SAR_ADC_TIMEOUT 100 /* ms */
+#define MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL 6
+#define MESON_SAR_ADC_TEMP_OFFSET 27
+
+/* temperature sensor calibration information in eFuse */
+#define MESON_SAR_ADC_EFUSE_BYTES 4
+#define MESON_SAR_ADC_EFUSE_BYTE3_UPPER_ADC_VAL GENMASK(6, 0)
+#define MESON_SAR_ADC_EFUSE_BYTE3_IS_CALIBRATED BIT(7)
+
+#define MESON_HHI_DPLL_TOP_0 0x318
+#define MESON_HHI_DPLL_TOP_0_TSC_BIT4 BIT(9)
+
/* for use with IIO_VAL_INT_PLUS_MICRO */
#define MILLION 1000000
@@ -175,16 +188,25 @@
.address = _chan, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
BIT(IIO_CHAN_INFO_AVERAGE_RAW), \
- .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
- BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBBIAS) | \
BIT(IIO_CHAN_INFO_CALIBSCALE), \
.datasheet_name = "SAR_ADC_CH"#_chan, \
}
-/*
- * TODO: the hardware supports IIO_TEMP for channel 6 as well which is
- * currently not supported by this driver.
- */
+#define MESON_SAR_ADC_TEMP_CHAN(_chan) { \
+ .type = IIO_TEMP, \
+ .channel = _chan, \
+ .address = MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_AVERAGE_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE), \
+ .datasheet_name = "TEMP_SENSOR", \
+}
+
static const struct iio_chan_spec meson_sar_adc_iio_channels[] = {
MESON_SAR_ADC_CHAN(0),
MESON_SAR_ADC_CHAN(1),
@@ -197,6 +219,19 @@ static const struct iio_chan_spec meson_sar_adc_iio_channels[] = {
IIO_CHAN_SOFT_TIMESTAMP(8),
};
+static const struct iio_chan_spec meson_sar_adc_and_temp_iio_channels[] = {
+ MESON_SAR_ADC_CHAN(0),
+ MESON_SAR_ADC_CHAN(1),
+ MESON_SAR_ADC_CHAN(2),
+ MESON_SAR_ADC_CHAN(3),
+ MESON_SAR_ADC_CHAN(4),
+ MESON_SAR_ADC_CHAN(5),
+ MESON_SAR_ADC_CHAN(6),
+ MESON_SAR_ADC_CHAN(7),
+ MESON_SAR_ADC_TEMP_CHAN(8),
+ IIO_CHAN_SOFT_TIMESTAMP(9),
+};
+
enum meson_sar_adc_avg_mode {
NO_AVERAGING = 0x0,
MEAN_AVERAGING = 0x1,
@@ -225,6 +260,9 @@ struct meson_sar_adc_param {
u32 bandgap_reg;
unsigned int resolution;
const struct regmap_config *regmap_config;
+ u8 temperature_trimming_bits;
+ unsigned int temperature_multiplier;
+ unsigned int temperature_divider;
};
struct meson_sar_adc_data {
@@ -246,6 +284,10 @@ struct meson_sar_adc_priv {
struct completion done;
int calibbias;
int calibscale;
+ struct regmap *tsc_regmap;
+ bool temperature_sensor_calibrated;
+ u8 temperature_sensor_coefficient;
+ u16 temperature_sensor_adc_val;
};
static const struct regmap_config meson_sar_adc_regmap_config_gxbb = {
@@ -389,9 +431,16 @@ static void meson_sar_adc_enable_channel(struct iio_dev *indio_dev,
MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK,
regval);
- if (chan->address == 6)
- regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
- MESON_SAR_ADC_DELTA_10_TEMP_SEL, 0);
+ if (chan->address == MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL) {
+ if (chan->type == IIO_TEMP)
+ regval = MESON_SAR_ADC_DELTA_10_TEMP_SEL;
+ else
+ regval = 0;
+
+ regmap_update_bits(priv->regmap,
+ MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TEMP_SEL, regval);
+ }
}
static void meson_sar_adc_set_chan7_mux(struct iio_dev *indio_dev,
@@ -506,8 +555,12 @@ static int meson_sar_adc_get_sample(struct iio_dev *indio_dev,
enum meson_sar_adc_num_samples avg_samples,
int *val)
{
+ struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
int ret;
+ if (chan->type == IIO_TEMP && !priv->temperature_sensor_calibrated)
+ return -ENOTSUPP;
+
ret = meson_sar_adc_lock(indio_dev);
if (ret)
return ret;
@@ -555,17 +608,31 @@ static int meson_sar_adc_iio_info_read_raw(struct iio_dev *indio_dev,
break;
case IIO_CHAN_INFO_SCALE:
- ret = regulator_get_voltage(priv->vref);
- if (ret < 0) {
- dev_err(indio_dev->dev.parent,
- "failed to get vref voltage: %d\n", ret);
- return ret;
+ if (chan->type == IIO_VOLTAGE) {
+ ret = regulator_get_voltage(priv->vref);
+ if (ret < 0) {
+ dev_err(indio_dev->dev.parent,
+ "failed to get vref voltage: %d\n",
+ ret);
+ return ret;
+ }
+
+ *val = ret / 1000;
+ *val2 = priv->param->resolution;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ } else if (chan->type == IIO_TEMP) {
+ /* SoC specific multiplier and divider */
+ *val = priv->param->temperature_multiplier;
+ *val2 = priv->param->temperature_divider;
+
+ /* celsius to millicelsius */
+ *val *= 1000;
+
+ return IIO_VAL_FRACTIONAL;
+ } else {
+ return -EINVAL;
}
- *val = ret / 1000;
- *val2 = priv->param->resolution;
- return IIO_VAL_FRACTIONAL_LOG2;
-
case IIO_CHAN_INFO_CALIBBIAS:
*val = priv->calibbias;
return IIO_VAL_INT;
@@ -575,6 +642,13 @@ static int meson_sar_adc_iio_info_read_raw(struct iio_dev *indio_dev,
*val2 = priv->calibscale % MILLION;
return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = DIV_ROUND_CLOSEST(MESON_SAR_ADC_TEMP_OFFSET *
+ priv->param->temperature_divider,
+ priv->param->temperature_multiplier);
+ *val -= priv->temperature_sensor_adc_val;
+ return IIO_VAL_INT;
+
default:
return -EINVAL;
}
@@ -587,8 +661,11 @@ static int meson_sar_adc_clk_init(struct iio_dev *indio_dev,
struct clk_init_data init;
const char *clk_parents[1];
- init.name = devm_kasprintf(&indio_dev->dev, GFP_KERNEL, "%pOF#adc_div",
- indio_dev->dev.of_node);
+ init.name = devm_kasprintf(&indio_dev->dev, GFP_KERNEL, "%s#adc_div",
+ dev_name(indio_dev->dev.parent));
+ if (!init.name)
+ return -ENOMEM;
+
init.flags = 0;
init.ops = &clk_divider_ops;
clk_parents[0] = __clk_get_name(priv->clkin);
@@ -606,8 +683,11 @@ static int meson_sar_adc_clk_init(struct iio_dev *indio_dev,
if (WARN_ON(IS_ERR(priv->adc_div_clk)))
return PTR_ERR(priv->adc_div_clk);
- init.name = devm_kasprintf(&indio_dev->dev, GFP_KERNEL, "%pOF#adc_en",
- indio_dev->dev.of_node);
+ init.name = devm_kasprintf(&indio_dev->dev, GFP_KERNEL, "%s#adc_en",
+ dev_name(indio_dev->dev.parent));
+ if (!init.name)
+ return -ENOMEM;
+
init.flags = CLK_SET_RATE_PARENT;
init.ops = &clk_gate_ops;
clk_parents[0] = __clk_get_name(priv->adc_div_clk);
@@ -625,6 +705,74 @@ static int meson_sar_adc_clk_init(struct iio_dev *indio_dev,
return 0;
}
+static int meson_sar_adc_temp_sensor_init(struct iio_dev *indio_dev)
+{
+ struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
+ u8 *buf, trimming_bits, trimming_mask, upper_adc_val;
+ struct nvmem_cell *temperature_calib;
+ size_t read_len;
+ int ret;
+
+ temperature_calib = devm_nvmem_cell_get(&indio_dev->dev,
+ "temperature_calib");
+ if (IS_ERR(temperature_calib)) {
+ ret = PTR_ERR(temperature_calib);
+
+ /*
+ * leave the temperature sensor disabled if no calibration data
+ * was passed via nvmem-cells.
+ */
+ if (ret == -ENODEV)
+ return 0;
+
+ if (ret != -EPROBE_DEFER)
+ dev_err(indio_dev->dev.parent,
+ "failed to get temperature_calib cell\n");
+
+ return ret;
+ }
+
+ priv->tsc_regmap =
+ syscon_regmap_lookup_by_phandle(indio_dev->dev.parent->of_node,
+ "amlogic,hhi-sysctrl");
+ if (IS_ERR(priv->tsc_regmap)) {
+ dev_err(indio_dev->dev.parent,
+ "failed to get amlogic,hhi-sysctrl regmap\n");
+ return PTR_ERR(priv->tsc_regmap);
+ }
+
+ read_len = MESON_SAR_ADC_EFUSE_BYTES;
+ buf = nvmem_cell_read(temperature_calib, &read_len);
+ if (IS_ERR(buf)) {
+ dev_err(indio_dev->dev.parent,
+ "failed to read temperature_calib cell\n");
+ return PTR_ERR(buf);
+ } else if (read_len != MESON_SAR_ADC_EFUSE_BYTES) {
+ kfree(buf);
+ dev_err(indio_dev->dev.parent,
+ "invalid read size of temperature_calib cell\n");
+ return -EINVAL;
+ }
+
+ trimming_bits = priv->param->temperature_trimming_bits;
+ trimming_mask = BIT(trimming_bits) - 1;
+
+ priv->temperature_sensor_calibrated =
+ buf[3] & MESON_SAR_ADC_EFUSE_BYTE3_IS_CALIBRATED;
+ priv->temperature_sensor_coefficient = buf[2] & trimming_mask;
+
+ upper_adc_val = FIELD_GET(MESON_SAR_ADC_EFUSE_BYTE3_UPPER_ADC_VAL,
+ buf[3]);
+
+ priv->temperature_sensor_adc_val = buf[2];
+ priv->temperature_sensor_adc_val |= upper_adc_val << BITS_PER_BYTE;
+ priv->temperature_sensor_adc_val >>= trimming_bits;
+
+ kfree(buf);
+
+ return 0;
+}
+
static int meson_sar_adc_init(struct iio_dev *indio_dev)
{
struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
@@ -649,10 +797,12 @@ static int meson_sar_adc_init(struct iio_dev *indio_dev)
meson_sar_adc_stop_sample_engine(indio_dev);
- /* update the channel 6 MUX to select the temperature sensor */
+ /*
+ * disable this bit as seems to be only relevant for Meson6 (based
+ * on the vendor driver), which we don't support at the moment.
+ */
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
- MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL,
- MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL);
+ MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL, 0);
/* disable all channels by default */
regmap_write(priv->regmap, MESON_SAR_ADC_CHAN_LIST, 0x0);
@@ -709,6 +859,45 @@ static int meson_sar_adc_init(struct iio_dev *indio_dev)
regval |= MESON_SAR_ADC_AUX_SW_XP_DRIVE_SW;
regmap_write(priv->regmap, MESON_SAR_ADC_AUX_SW, regval);
+ if (priv->temperature_sensor_calibrated) {
+ regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TS_REVE1,
+ MESON_SAR_ADC_DELTA_10_TS_REVE1);
+ regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TS_REVE0,
+ MESON_SAR_ADC_DELTA_10_TS_REVE0);
+
+ /*
+ * set bits [3:0] of the TSC (temperature sensor coefficient)
+ * to get the correct values when reading the temperature.
+ */
+ regval = FIELD_PREP(MESON_SAR_ADC_DELTA_10_TS_C_MASK,
+ priv->temperature_sensor_coefficient);
+ regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TS_C_MASK, regval);
+
+ if (priv->param->temperature_trimming_bits == 5) {
+ if (priv->temperature_sensor_coefficient & BIT(4))
+ regval = MESON_HHI_DPLL_TOP_0_TSC_BIT4;
+ else
+ regval = 0;
+
+ /*
+ * bit [4] (the 5th bit when starting to count at 1)
+ * of the TSC is located in the HHI register area.
+ */
+ regmap_update_bits(priv->tsc_regmap,
+ MESON_HHI_DPLL_TOP_0,
+ MESON_HHI_DPLL_TOP_0_TSC_BIT4,
+ regval);
+ }
+ } else {
+ regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TS_REVE1, 0);
+ regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
+ MESON_SAR_ADC_DELTA_10_TS_REVE0, 0);
+ }
+
ret = clk_set_parent(priv->adc_sel_clk, priv->clkin);
if (ret) {
dev_err(indio_dev->dev.parent,
@@ -894,6 +1083,20 @@ static const struct meson_sar_adc_param meson_sar_adc_meson8_param = {
.bandgap_reg = MESON_SAR_ADC_DELTA_10,
.regmap_config = &meson_sar_adc_regmap_config_meson8,
.resolution = 10,
+ .temperature_trimming_bits = 4,
+ .temperature_multiplier = 18 * 10000,
+ .temperature_divider = 1024 * 10 * 85,
+};
+
+static const struct meson_sar_adc_param meson_sar_adc_meson8b_param = {
+ .has_bl30_integration = false,
+ .clock_rate = 1150000,
+ .bandgap_reg = MESON_SAR_ADC_DELTA_10,
+ .regmap_config = &meson_sar_adc_regmap_config_meson8,
+ .resolution = 10,
+ .temperature_trimming_bits = 5,
+ .temperature_multiplier = 10,
+ .temperature_divider = 32,
};
static const struct meson_sar_adc_param meson_sar_adc_gxbb_param = {
@@ -918,12 +1121,12 @@ static const struct meson_sar_adc_data meson_sar_adc_meson8_data = {
};
static const struct meson_sar_adc_data meson_sar_adc_meson8b_data = {
- .param = &meson_sar_adc_meson8_param,
+ .param = &meson_sar_adc_meson8b_param,
.name = "meson-meson8b-saradc",
};
static const struct meson_sar_adc_data meson_sar_adc_meson8m2_data = {
- .param = &meson_sar_adc_meson8_param,
+ .param = &meson_sar_adc_meson8b_param,
.name = "meson-meson8m2-saradc",
};
@@ -1009,9 +1212,6 @@ static int meson_sar_adc_probe(struct platform_device *pdev)
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &meson_sar_adc_iio_info;
- indio_dev->channels = meson_sar_adc_iio_channels;
- indio_dev->num_channels = ARRAY_SIZE(meson_sar_adc_iio_channels);
-
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
@@ -1078,6 +1278,22 @@ static int meson_sar_adc_probe(struct platform_device *pdev)
priv->calibscale = MILLION;
+ if (priv->param->temperature_trimming_bits) {
+ ret = meson_sar_adc_temp_sensor_init(indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ if (priv->temperature_sensor_calibrated) {
+ indio_dev->channels = meson_sar_adc_and_temp_iio_channels;
+ indio_dev->num_channels =
+ ARRAY_SIZE(meson_sar_adc_and_temp_iio_channels);
+ } else {
+ indio_dev->channels = meson_sar_adc_iio_channels;
+ indio_dev->num_channels =
+ ARRAY_SIZE(meson_sar_adc_iio_channels);
+ }
+
ret = meson_sar_adc_init(indio_dev);
if (ret)
goto err;
diff --git a/drivers/iio/adc/npcm_adc.c b/drivers/iio/adc/npcm_adc.c
new file mode 100644
index 000000000000..9e25bbec9c70
--- /dev/null
+++ b/drivers/iio/adc/npcm_adc.c
@@ -0,0 +1,335 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2019 Nuvoton Technology corporation.
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/io.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+
+struct npcm_adc {
+ bool int_status;
+ u32 adc_sample_hz;
+ struct device *dev;
+ void __iomem *regs;
+ struct clk *adc_clk;
+ wait_queue_head_t wq;
+ struct regulator *vref;
+ struct regmap *rst_regmap;
+};
+
+/* NPCM7xx reset module */
+#define NPCM7XX_IPSRST1_OFFSET 0x020
+#define NPCM7XX_IPSRST1_ADC_RST BIT(27)
+
+/* ADC registers */
+#define NPCM_ADCCON 0x00
+#define NPCM_ADCDATA 0x04
+
+/* ADCCON Register Bits */
+#define NPCM_ADCCON_ADC_INT_EN BIT(21)
+#define NPCM_ADCCON_REFSEL BIT(19)
+#define NPCM_ADCCON_ADC_INT_ST BIT(18)
+#define NPCM_ADCCON_ADC_EN BIT(17)
+#define NPCM_ADCCON_ADC_RST BIT(16)
+#define NPCM_ADCCON_ADC_CONV BIT(13)
+
+#define NPCM_ADCCON_CH_MASK GENMASK(27, 24)
+#define NPCM_ADCCON_CH(x) ((x) << 24)
+#define NPCM_ADCCON_DIV_SHIFT 1
+#define NPCM_ADCCON_DIV_MASK GENMASK(8, 1)
+#define NPCM_ADC_DATA_MASK(x) ((x) & GENMASK(9, 0))
+
+#define NPCM_ADC_ENABLE (NPCM_ADCCON_ADC_EN | NPCM_ADCCON_ADC_INT_EN)
+
+/* ADC General Definition */
+#define NPCM_RESOLUTION_BITS 10
+#define NPCM_INT_VREF_MV 2000
+
+#define NPCM_ADC_CHAN(ch) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = ch, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+}
+
+static const struct iio_chan_spec npcm_adc_iio_channels[] = {
+ NPCM_ADC_CHAN(0),
+ NPCM_ADC_CHAN(1),
+ NPCM_ADC_CHAN(2),
+ NPCM_ADC_CHAN(3),
+ NPCM_ADC_CHAN(4),
+ NPCM_ADC_CHAN(5),
+ NPCM_ADC_CHAN(6),
+ NPCM_ADC_CHAN(7),
+};
+
+static irqreturn_t npcm_adc_isr(int irq, void *data)
+{
+ u32 regtemp;
+ struct iio_dev *indio_dev = data;
+ struct npcm_adc *info = iio_priv(indio_dev);
+
+ regtemp = ioread32(info->regs + NPCM_ADCCON);
+ if (regtemp & NPCM_ADCCON_ADC_INT_ST) {
+ iowrite32(regtemp, info->regs + NPCM_ADCCON);
+ wake_up_interruptible(&info->wq);
+ info->int_status = true;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int npcm_adc_read(struct npcm_adc *info, int *val, u8 channel)
+{
+ int ret;
+ u32 regtemp;
+
+ /* Select ADC channel */
+ regtemp = ioread32(info->regs + NPCM_ADCCON);
+ regtemp &= ~NPCM_ADCCON_CH_MASK;
+ info->int_status = false;
+ iowrite32(regtemp | NPCM_ADCCON_CH(channel) |
+ NPCM_ADCCON_ADC_CONV, info->regs + NPCM_ADCCON);
+
+ ret = wait_event_interruptible_timeout(info->wq, info->int_status,
+ msecs_to_jiffies(10));
+ if (ret == 0) {
+ regtemp = ioread32(info->regs + NPCM_ADCCON);
+ if ((regtemp & NPCM_ADCCON_ADC_CONV) && info->rst_regmap) {
+ /* if conversion failed - reset ADC module */
+ regmap_write(info->rst_regmap, NPCM7XX_IPSRST1_OFFSET,
+ NPCM7XX_IPSRST1_ADC_RST);
+ msleep(100);
+ regmap_write(info->rst_regmap, NPCM7XX_IPSRST1_OFFSET,
+ 0x0);
+ msleep(100);
+
+ /* Enable ADC and start conversion module */
+ iowrite32(NPCM_ADC_ENABLE | NPCM_ADCCON_ADC_CONV,
+ info->regs + NPCM_ADCCON);
+ dev_err(info->dev, "RESET ADC Complete\n");
+ }
+ return -ETIMEDOUT;
+ }
+ if (ret < 0)
+ return ret;
+
+ *val = NPCM_ADC_DATA_MASK(ioread32(info->regs + NPCM_ADCDATA));
+
+ return 0;
+}
+
+static int npcm_adc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ int ret;
+ int vref_uv;
+ struct npcm_adc *info = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&indio_dev->mlock);
+ ret = npcm_adc_read(info, val, chan->channel);
+ mutex_unlock(&indio_dev->mlock);
+ if (ret) {
+ dev_err(info->dev, "NPCM ADC read failed\n");
+ return ret;
+ }
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ if (info->vref) {
+ vref_uv = regulator_get_voltage(info->vref);
+ *val = vref_uv / 1000;
+ } else {
+ *val = NPCM_INT_VREF_MV;
+ }
+ *val2 = NPCM_RESOLUTION_BITS;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = info->adc_sample_hz;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct iio_info npcm_adc_iio_info = {
+ .read_raw = &npcm_adc_read_raw,
+};
+
+static const struct of_device_id npcm_adc_match[] = {
+ { .compatible = "nuvoton,npcm750-adc", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, npcm_adc_match);
+
+static int npcm_adc_probe(struct platform_device *pdev)
+{
+ int ret;
+ int irq;
+ u32 div;
+ u32 reg_con;
+ struct resource *res;
+ struct npcm_adc *info;
+ struct iio_dev *indio_dev;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = pdev->dev.of_node;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
+ if (!indio_dev)
+ return -ENOMEM;
+ info = iio_priv(indio_dev);
+
+ info->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ info->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(info->regs))
+ return PTR_ERR(info->regs);
+
+ info->adc_clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(info->adc_clk)) {
+ dev_warn(&pdev->dev, "ADC clock failed: can't read clk\n");
+ return PTR_ERR(info->adc_clk);
+ }
+
+ /* calculate ADC clock sample rate */
+ reg_con = ioread32(info->regs + NPCM_ADCCON);
+ div = reg_con & NPCM_ADCCON_DIV_MASK;
+ div = div >> NPCM_ADCCON_DIV_SHIFT;
+ info->adc_sample_hz = clk_get_rate(info->adc_clk) / ((div + 1) * 2);
+
+ if (of_device_is_compatible(np, "nuvoton,npcm750-adc")) {
+ info->rst_regmap = syscon_regmap_lookup_by_compatible
+ ("nuvoton,npcm750-rst");
+ if (IS_ERR(info->rst_regmap)) {
+ dev_err(&pdev->dev, "Failed to find nuvoton,npcm750-rst\n");
+ ret = PTR_ERR(info->rst_regmap);
+ goto err_disable_clk;
+ }
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ dev_err(dev, "failed getting interrupt resource\n");
+ ret = -EINVAL;
+ goto err_disable_clk;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, npcm_adc_isr, 0,
+ "NPCM_ADC", indio_dev);
+ if (ret < 0) {
+ dev_err(dev, "failed requesting interrupt\n");
+ goto err_disable_clk;
+ }
+
+ reg_con = ioread32(info->regs + NPCM_ADCCON);
+ info->vref = devm_regulator_get_optional(&pdev->dev, "vref");
+ if (!IS_ERR(info->vref)) {
+ ret = regulator_enable(info->vref);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't enable ADC reference voltage\n");
+ goto err_disable_clk;
+ }
+
+ iowrite32(reg_con & ~NPCM_ADCCON_REFSEL,
+ info->regs + NPCM_ADCCON);
+ } else {
+ /*
+ * Any error which is not ENODEV indicates the regulator
+ * has been specified and so is a failure case.
+ */
+ if (PTR_ERR(info->vref) != -ENODEV) {
+ ret = PTR_ERR(info->vref);
+ goto err_disable_clk;
+ }
+
+ /* Use internal reference */
+ iowrite32(reg_con | NPCM_ADCCON_REFSEL,
+ info->regs + NPCM_ADCCON);
+ }
+
+ init_waitqueue_head(&info->wq);
+
+ reg_con = ioread32(info->regs + NPCM_ADCCON);
+ reg_con |= NPCM_ADC_ENABLE;
+
+ /* Enable the ADC Module */
+ iowrite32(reg_con, info->regs + NPCM_ADCCON);
+
+ /* Start ADC conversion */
+ iowrite32(reg_con | NPCM_ADCCON_ADC_CONV, info->regs + NPCM_ADCCON);
+
+ platform_set_drvdata(pdev, indio_dev);
+ indio_dev->name = dev_name(&pdev->dev);
+ indio_dev->dev.parent = &pdev->dev;
+ indio_dev->info = &npcm_adc_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = npcm_adc_iio_channels;
+ indio_dev->num_channels = ARRAY_SIZE(npcm_adc_iio_channels);
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Couldn't register the device.\n");
+ goto err_iio_register;
+ }
+
+ pr_info("NPCM ADC driver probed\n");
+
+ return 0;
+
+err_iio_register:
+ iowrite32(reg_con & ~NPCM_ADCCON_ADC_EN, info->regs + NPCM_ADCCON);
+ if (!IS_ERR(info->vref))
+ regulator_disable(info->vref);
+err_disable_clk:
+ clk_disable_unprepare(info->adc_clk);
+
+ return ret;
+}
+
+static int npcm_adc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct npcm_adc *info = iio_priv(indio_dev);
+ u32 regtemp;
+
+ iio_device_unregister(indio_dev);
+
+ regtemp = ioread32(info->regs + NPCM_ADCCON);
+ iowrite32(regtemp & ~NPCM_ADCCON_ADC_EN, info->regs + NPCM_ADCCON);
+ if (!IS_ERR(info->vref))
+ regulator_disable(info->vref);
+ clk_disable_unprepare(info->adc_clk);
+
+ return 0;
+}
+
+static struct platform_driver npcm_adc_driver = {
+ .probe = npcm_adc_probe,
+ .remove = npcm_adc_remove,
+ .driver = {
+ .name = "npcm_adc",
+ .of_match_table = npcm_adc_match,
+ },
+};
+
+module_platform_driver(npcm_adc_driver);
+
+MODULE_DESCRIPTION("Nuvoton NPCM ADC Driver");
+MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/qcom-pm8xxx-xoadc.c b/drivers/iio/adc/qcom-pm8xxx-xoadc.c
index c30c002f1fef..4735f8a1ca9d 100644
--- a/drivers/iio/adc/qcom-pm8xxx-xoadc.c
+++ b/drivers/iio/adc/qcom-pm8xxx-xoadc.c
@@ -423,18 +423,14 @@ static irqreturn_t pm8xxx_eoc_irq(int irq, void *d)
static struct pm8xxx_chan_info *
pm8xxx_get_channel(struct pm8xxx_xoadc *adc, u8 chan)
{
- struct pm8xxx_chan_info *ch;
int i;
for (i = 0; i < adc->nchans; i++) {
- ch = &adc->chans[i];
+ struct pm8xxx_chan_info *ch = &adc->chans[i];
if (ch->hwchan->amux_channel == chan)
- break;
+ return ch;
}
- if (i == adc->nchans)
- return NULL;
-
- return ch;
+ return NULL;
}
static int pm8xxx_read_channel_rsv(struct pm8xxx_xoadc *adc,
diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
index f9af6b082916..6a866cc187f7 100644
--- a/drivers/iio/adc/qcom-spmi-adc5.c
+++ b/drivers/iio/adc/qcom-spmi-adc5.c
@@ -423,6 +423,7 @@ struct adc5_channels {
enum vadc_scale_fn_type scale_fn_type;
};
+/* In these definitions, _pre refers to an index into adc5_prescale_ratios. */
#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale) \
{ \
.datasheet_name = _dname, \
@@ -443,63 +444,63 @@ struct adc5_channels {
_pre, _scale) \
static const struct adc5_channels adc5_chans_pmic[ADC5_MAX_CHANNEL] = {
- [ADC5_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 1,
+ [ADC5_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 0,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 1,
+ [ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 3,
+ [ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 3,
+ [ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 1,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 1,
+ [ADC5_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0,
SCALE_HW_CALIB_PMIC_THERM)
- [ADC5_USB_IN_I] = ADC5_CHAN_VOLT("usb_in_i_uv", 1,
+ [ADC5_USB_IN_I] = ADC5_CHAN_VOLT("usb_in_i_uv", 0,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_USB_IN_V_16] = ADC5_CHAN_VOLT("usb_in_v_div_16", 16,
+ [ADC5_USB_IN_V_16] = ADC5_CHAN_VOLT("usb_in_v_div_16", 8,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_CHG_TEMP] = ADC5_CHAN_TEMP("chg_temp", 1,
+ [ADC5_CHG_TEMP] = ADC5_CHAN_TEMP("chg_temp", 0,
SCALE_HW_CALIB_PM5_CHG_TEMP)
/* Charger prescales SBUx and MID_CHG to fit within 1.8V upper unit */
- [ADC5_SBUx] = ADC5_CHAN_VOLT("chg_sbux", 3,
+ [ADC5_SBUx] = ADC5_CHAN_VOLT("chg_sbux", 1,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_MID_CHG_DIV6] = ADC5_CHAN_VOLT("chg_mid_chg", 6,
+ [ADC5_MID_CHG_DIV6] = ADC5_CHAN_VOLT("chg_mid_chg", 3,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm", 1,
+ [ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm", 0,
SCALE_HW_CALIB_XOTHERM)
- [ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
+ [ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 0,
SCALE_HW_CALIB_THERM_100K_PULLUP)
- [ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 1,
+ [ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 0,
SCALE_HW_CALIB_THERM_100K_PULLUP)
- [ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
+ [ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 0,
SCALE_HW_CALIB_THERM_100K_PULLUP)
- [ADC5_AMUX_THM2] = ADC5_CHAN_TEMP("amux_thm2", 1,
+ [ADC5_AMUX_THM2] = ADC5_CHAN_TEMP("amux_thm2", 0,
SCALE_HW_CALIB_PM5_SMB_TEMP)
};
static const struct adc5_channels adc5_chans_rev2[ADC5_MAX_CHANNEL] = {
- [ADC5_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 1,
+ [ADC5_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 0,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 1,
+ [ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 3,
+ [ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 3,
+ [ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 1,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_VCOIN] = ADC5_CHAN_VOLT("vcoin", 3,
+ [ADC5_VCOIN] = ADC5_CHAN_VOLT("vcoin", 1,
SCALE_HW_CALIB_DEFAULT)
- [ADC5_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 1,
+ [ADC5_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0,
SCALE_HW_CALIB_PMIC_THERM)
- [ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
+ [ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 0,
SCALE_HW_CALIB_THERM_100K_PULLUP)
- [ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 1,
+ [ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 0,
SCALE_HW_CALIB_THERM_100K_PULLUP)
- [ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
+ [ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 0,
SCALE_HW_CALIB_THERM_100K_PULLUP)
- [ADC5_AMUX_THM4_100K_PU] = ADC5_CHAN_TEMP("amux_thm4_100k_pu", 1,
+ [ADC5_AMUX_THM4_100K_PU] = ADC5_CHAN_TEMP("amux_thm4_100k_pu", 0,
SCALE_HW_CALIB_THERM_100K_PULLUP)
- [ADC5_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_100k_pu", 1,
+ [ADC5_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_100k_pu", 0,
SCALE_HW_CALIB_THERM_100K_PULLUP)
- [ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm_100k_pu", 1,
+ [ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm_100k_pu", 0,
SCALE_HW_CALIB_THERM_100K_PULLUP)
};
@@ -558,6 +559,9 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
return ret;
}
prop->prescale = ret;
+ } else {
+ prop->prescale =
+ adc->data->adc_chans[prop->channel].prescale_index;
}
ret = of_property_read_u32(node, "qcom,hw-settle-time", &value);
diff --git a/drivers/iio/adc/rcar-gyroadc.c b/drivers/iio/adc/rcar-gyroadc.c
index 4e982b51bcda..2c0d0316d149 100644
--- a/drivers/iio/adc/rcar-gyroadc.c
+++ b/drivers/iio/adc/rcar-gyroadc.c
@@ -1,17 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0+
/*
* Renesas R-Car GyroADC driver
*
* Copyright 2016 Marek Vasut <marek.vasut@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*/
#include <linux/module.h>
diff --git a/drivers/iio/adc/sc27xx_adc.c b/drivers/iio/adc/sc27xx_adc.c
index 7940b23dcad9..f7f7a18904b4 100644
--- a/drivers/iio/adc/sc27xx_adc.c
+++ b/drivers/iio/adc/sc27xx_adc.c
@@ -52,6 +52,9 @@
/* Timeout (ms) for the trylock of hardware spinlocks */
#define SC27XX_ADC_HWLOCK_TIMEOUT 5000
+/* Timeout (ms) for ADC data conversion according to ADC datasheet */
+#define SC27XX_ADC_RDY_TIMEOUT 100
+
/* Maximum ADC channel number */
#define SC27XX_ADC_CHANNEL_MAX 32
@@ -223,7 +226,14 @@ static int sc27xx_adc_read(struct sc27xx_adc_data *data, int channel,
if (ret)
goto disable_adc;
- wait_for_completion(&data->completion);
+ ret = wait_for_completion_timeout(&data->completion,
+ msecs_to_jiffies(SC27XX_ADC_RDY_TIMEOUT));
+ if (!ret) {
+ dev_err(data->dev, "read ADC data timeout\n");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = 0;
+ }
disable_adc:
regmap_update_bits(data->regmap, data->base + SC27XX_ADC_CTL,
diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c
index ca432e7b6ff1..2327ec18b40c 100644
--- a/drivers/iio/adc/stm32-adc-core.c
+++ b/drivers/iio/adc/stm32-adc-core.c
@@ -16,6 +16,7 @@
#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
@@ -48,15 +49,19 @@
#define STM32H7_CKMODE_SHIFT 16
#define STM32H7_CKMODE_MASK GENMASK(17, 16)
+#define STM32_ADC_CORE_SLEEP_DELAY_MS 2000
+
/**
* stm32_adc_common_regs - stm32 common registers, compatible dependent data
* @csr: common status register offset
+ * @ccr: common control register offset
* @eoc1: adc1 end of conversion flag in @csr
* @eoc2: adc2 end of conversion flag in @csr
* @eoc3: adc3 end of conversion flag in @csr
*/
struct stm32_adc_common_regs {
u32 csr;
+ u32 ccr;
u32 eoc1_msk;
u32 eoc2_msk;
u32 eoc3_msk;
@@ -85,6 +90,7 @@ struct stm32_adc_priv_cfg {
* @vref: regulator reference
* @cfg: compatible configuration data
* @common: common data for all ADC instances
+ * @ccr_bak: backup CCR in low power mode
*/
struct stm32_adc_priv {
int irq[STM32_ADC_MAX_ADCS];
@@ -94,6 +100,7 @@ struct stm32_adc_priv {
struct regulator *vref;
const struct stm32_adc_priv_cfg *cfg;
struct stm32_adc_common common;
+ u32 ccr_bak;
};
static struct stm32_adc_priv *to_stm32_adc_priv(struct stm32_adc_common *com)
@@ -265,6 +272,7 @@ out:
/* STM32F4 common registers definitions */
static const struct stm32_adc_common_regs stm32f4_adc_common_regs = {
.csr = STM32F4_ADC_CSR,
+ .ccr = STM32F4_ADC_CCR,
.eoc1_msk = STM32F4_EOC1,
.eoc2_msk = STM32F4_EOC2,
.eoc3_msk = STM32F4_EOC3,
@@ -273,6 +281,7 @@ static const struct stm32_adc_common_regs stm32f4_adc_common_regs = {
/* STM32H7 common registers definitions */
static const struct stm32_adc_common_regs stm32h7_adc_common_regs = {
.csr = STM32H7_ADC_CSR,
+ .ccr = STM32H7_ADC_CCR,
.eoc1_msk = STM32H7_EOC_MST,
.eoc2_msk = STM32H7_EOC_SLV,
};
@@ -379,6 +388,61 @@ static void stm32_adc_irq_remove(struct platform_device *pdev,
}
}
+static int stm32_adc_core_hw_start(struct device *dev)
+{
+ struct stm32_adc_common *common = dev_get_drvdata(dev);
+ struct stm32_adc_priv *priv = to_stm32_adc_priv(common);
+ int ret;
+
+ ret = regulator_enable(priv->vref);
+ if (ret < 0) {
+ dev_err(dev, "vref enable failed\n");
+ return ret;
+ }
+
+ if (priv->bclk) {
+ ret = clk_prepare_enable(priv->bclk);
+ if (ret < 0) {
+ dev_err(dev, "bus clk enable failed\n");
+ goto err_regulator_disable;
+ }
+ }
+
+ if (priv->aclk) {
+ ret = clk_prepare_enable(priv->aclk);
+ if (ret < 0) {
+ dev_err(dev, "adc clk enable failed\n");
+ goto err_bclk_disable;
+ }
+ }
+
+ writel_relaxed(priv->ccr_bak, priv->common.base + priv->cfg->regs->ccr);
+
+ return 0;
+
+err_bclk_disable:
+ if (priv->bclk)
+ clk_disable_unprepare(priv->bclk);
+err_regulator_disable:
+ regulator_disable(priv->vref);
+
+ return ret;
+}
+
+static void stm32_adc_core_hw_stop(struct device *dev)
+{
+ struct stm32_adc_common *common = dev_get_drvdata(dev);
+ struct stm32_adc_priv *priv = to_stm32_adc_priv(common);
+
+ /* Backup CCR that may be lost (depends on power state to achieve) */
+ priv->ccr_bak = readl_relaxed(priv->common.base + priv->cfg->regs->ccr);
+ if (priv->aclk)
+ clk_disable_unprepare(priv->aclk);
+ if (priv->bclk)
+ clk_disable_unprepare(priv->bclk);
+ regulator_disable(priv->vref);
+}
+
static int stm32_adc_probe(struct platform_device *pdev)
{
struct stm32_adc_priv *priv;
@@ -393,6 +457,7 @@ static int stm32_adc_probe(struct platform_device *pdev)
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
+ platform_set_drvdata(pdev, &priv->common);
priv->cfg = (const struct stm32_adc_priv_cfg *)
of_match_device(dev->driver->of_match_table, dev)->data;
@@ -410,67 +475,51 @@ static int stm32_adc_probe(struct platform_device *pdev)
return ret;
}
- ret = regulator_enable(priv->vref);
- if (ret < 0) {
- dev_err(&pdev->dev, "vref enable failed\n");
- return ret;
- }
-
- ret = regulator_get_voltage(priv->vref);
- if (ret < 0) {
- dev_err(&pdev->dev, "vref get voltage failed, %d\n", ret);
- goto err_regulator_disable;
- }
- priv->common.vref_mv = ret / 1000;
- dev_dbg(&pdev->dev, "vref+=%dmV\n", priv->common.vref_mv);
-
priv->aclk = devm_clk_get(&pdev->dev, "adc");
if (IS_ERR(priv->aclk)) {
ret = PTR_ERR(priv->aclk);
- if (ret == -ENOENT) {
- priv->aclk = NULL;
- } else {
+ if (ret != -ENOENT) {
dev_err(&pdev->dev, "Can't get 'adc' clock\n");
- goto err_regulator_disable;
- }
- }
-
- if (priv->aclk) {
- ret = clk_prepare_enable(priv->aclk);
- if (ret < 0) {
- dev_err(&pdev->dev, "adc clk enable failed\n");
- goto err_regulator_disable;
+ return ret;
}
+ priv->aclk = NULL;
}
priv->bclk = devm_clk_get(&pdev->dev, "bus");
if (IS_ERR(priv->bclk)) {
ret = PTR_ERR(priv->bclk);
- if (ret == -ENOENT) {
- priv->bclk = NULL;
- } else {
+ if (ret != -ENOENT) {
dev_err(&pdev->dev, "Can't get 'bus' clock\n");
- goto err_aclk_disable;
+ return ret;
}
+ priv->bclk = NULL;
}
- if (priv->bclk) {
- ret = clk_prepare_enable(priv->bclk);
- if (ret < 0) {
- dev_err(&pdev->dev, "adc clk enable failed\n");
- goto err_aclk_disable;
- }
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_set_autosuspend_delay(dev, STM32_ADC_CORE_SLEEP_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_enable(dev);
+
+ ret = stm32_adc_core_hw_start(dev);
+ if (ret)
+ goto err_pm_stop;
+
+ ret = regulator_get_voltage(priv->vref);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "vref get voltage failed, %d\n", ret);
+ goto err_hw_stop;
}
+ priv->common.vref_mv = ret / 1000;
+ dev_dbg(&pdev->dev, "vref+=%dmV\n", priv->common.vref_mv);
ret = priv->cfg->clk_sel(pdev, priv);
if (ret < 0)
- goto err_bclk_disable;
+ goto err_hw_stop;
ret = stm32_adc_irq_probe(pdev, priv);
if (ret < 0)
- goto err_bclk_disable;
-
- platform_set_drvdata(pdev, &priv->common);
+ goto err_hw_stop;
ret = of_platform_populate(np, NULL, NULL, &pdev->dev);
if (ret < 0) {
@@ -478,21 +527,19 @@ static int stm32_adc_probe(struct platform_device *pdev)
goto err_irq_remove;
}
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
return 0;
err_irq_remove:
stm32_adc_irq_remove(pdev, priv);
-
-err_bclk_disable:
- if (priv->bclk)
- clk_disable_unprepare(priv->bclk);
-
-err_aclk_disable:
- if (priv->aclk)
- clk_disable_unprepare(priv->aclk);
-
-err_regulator_disable:
- regulator_disable(priv->vref);
+err_hw_stop:
+ stm32_adc_core_hw_stop(dev);
+err_pm_stop:
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
return ret;
}
@@ -502,17 +549,39 @@ static int stm32_adc_remove(struct platform_device *pdev)
struct stm32_adc_common *common = platform_get_drvdata(pdev);
struct stm32_adc_priv *priv = to_stm32_adc_priv(common);
+ pm_runtime_get_sync(&pdev->dev);
of_platform_depopulate(&pdev->dev);
stm32_adc_irq_remove(pdev, priv);
- if (priv->bclk)
- clk_disable_unprepare(priv->bclk);
- if (priv->aclk)
- clk_disable_unprepare(priv->aclk);
- regulator_disable(priv->vref);
+ stm32_adc_core_hw_stop(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
return 0;
}
+#if defined(CONFIG_PM)
+static int stm32_adc_core_runtime_suspend(struct device *dev)
+{
+ stm32_adc_core_hw_stop(dev);
+
+ return 0;
+}
+
+static int stm32_adc_core_runtime_resume(struct device *dev)
+{
+ return stm32_adc_core_hw_start(dev);
+}
+#endif
+
+static const struct dev_pm_ops stm32_adc_core_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(stm32_adc_core_runtime_suspend,
+ stm32_adc_core_runtime_resume,
+ NULL)
+};
+
static const struct stm32_adc_priv_cfg stm32f4_adc_priv_cfg = {
.regs = &stm32f4_adc_common_regs,
.clk_sel = stm32f4_adc_clk_sel,
@@ -552,6 +621,7 @@ static struct platform_driver stm32_adc_driver = {
.driver = {
.name = "stm32-adc-core",
.of_match_table = stm32_adc_of_match,
+ .pm = &stm32_adc_core_pm_ops,
},
};
module_platform_driver(stm32_adc_driver);
diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
index 378411853d75..205e1699f954 100644
--- a/drivers/iio/adc/stm32-adc.c
+++ b/drivers/iio/adc/stm32-adc.c
@@ -22,6 +22,7 @@
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/of_device.h>
@@ -148,6 +149,7 @@ enum stm32h7_adc_dmngt {
#define STM32_ADC_MAX_SMP 7 /* SMPx range is [0..7] */
#define STM32_ADC_TIMEOUT_US 100000
#define STM32_ADC_TIMEOUT (msecs_to_jiffies(STM32_ADC_TIMEOUT_US / 1000))
+#define STM32_ADC_HW_STOP_DELAY_MS 100
#define STM32_DMA_BUFFER_SIZE PAGE_SIZE
@@ -199,11 +201,13 @@ struct stm32_adc_trig_info {
* @calfact_s: Calibration offset for single ended channels
* @calfact_d: Calibration offset in differential
* @lincalfact: Linearity calibration factor
+ * @calibrated: Indicates calibration status
*/
struct stm32_adc_calib {
u32 calfact_s;
u32 calfact_d;
u32 lincalfact[STM32H7_LINCALFACT_NUM];
+ bool calibrated;
};
/**
@@ -251,7 +255,6 @@ struct stm32_adc;
* @trigs: external trigger sources
* @clk_required: clock is required
* @has_vregready: vregready status flag presence
- * @selfcalib: optional routine for self-calibration
* @prepare: optional prepare routine (power-up, enable)
* @start_conv: routine to start conversions
* @stop_conv: routine to stop conversions
@@ -264,7 +267,6 @@ struct stm32_adc_cfg {
struct stm32_adc_trig_info *trigs;
bool clk_required;
bool has_vregready;
- int (*selfcalib)(struct stm32_adc *);
int (*prepare)(struct stm32_adc *);
void (*start_conv)(struct stm32_adc *, bool dma);
void (*stop_conv)(struct stm32_adc *);
@@ -623,6 +625,47 @@ static void stm32_adc_set_res(struct stm32_adc *adc)
stm32_adc_writel(adc, res->reg, val);
}
+static int stm32_adc_hw_stop(struct device *dev)
+{
+ struct stm32_adc *adc = dev_get_drvdata(dev);
+
+ if (adc->cfg->unprepare)
+ adc->cfg->unprepare(adc);
+
+ if (adc->clk)
+ clk_disable_unprepare(adc->clk);
+
+ return 0;
+}
+
+static int stm32_adc_hw_start(struct device *dev)
+{
+ struct stm32_adc *adc = dev_get_drvdata(dev);
+ int ret;
+
+ if (adc->clk) {
+ ret = clk_prepare_enable(adc->clk);
+ if (ret)
+ return ret;
+ }
+
+ stm32_adc_set_res(adc);
+
+ if (adc->cfg->prepare) {
+ ret = adc->cfg->prepare(adc);
+ if (ret)
+ goto err_clk_dis;
+ }
+
+ return 0;
+
+err_clk_dis:
+ if (adc->clk)
+ clk_disable_unprepare(adc->clk);
+
+ return ret;
+}
+
/**
* stm32f4_adc_start_conv() - Start conversions for regular channels.
* @adc: stm32 adc instance
@@ -777,6 +820,7 @@ static void stm32h7_adc_disable(struct stm32_adc *adc)
/**
* stm32h7_adc_read_selfcalib() - read calibration shadow regs, save result
* @adc: stm32 adc instance
+ * Note: Must be called once ADC is enabled, so LINCALRDYW[1..6] are writable
*/
static int stm32h7_adc_read_selfcalib(struct stm32_adc *adc)
{
@@ -784,11 +828,6 @@ static int stm32h7_adc_read_selfcalib(struct stm32_adc *adc)
int i, ret;
u32 lincalrdyw_mask, val;
- /* Enable adc so LINCALRDYW1..6 bits are writable */
- ret = stm32h7_adc_enable(adc);
- if (ret)
- return ret;
-
/* Read linearity calibration */
lincalrdyw_mask = STM32H7_LINCALRDYW6;
for (i = STM32H7_LINCALFACT_NUM - 1; i >= 0; i--) {
@@ -801,7 +840,7 @@ static int stm32h7_adc_read_selfcalib(struct stm32_adc *adc)
100, STM32_ADC_TIMEOUT_US);
if (ret) {
dev_err(&indio_dev->dev, "Failed to read calfact\n");
- goto disable;
+ return ret;
}
val = stm32_adc_readl(adc, STM32H7_ADC_CALFACT2);
@@ -817,11 +856,9 @@ static int stm32h7_adc_read_selfcalib(struct stm32_adc *adc)
adc->cal.calfact_s >>= STM32H7_CALFACT_S_SHIFT;
adc->cal.calfact_d = (val & STM32H7_CALFACT_D_MASK);
adc->cal.calfact_d >>= STM32H7_CALFACT_D_SHIFT;
+ adc->cal.calibrated = true;
-disable:
- stm32h7_adc_disable(adc);
-
- return ret;
+ return 0;
}
/**
@@ -898,9 +935,9 @@ static int stm32h7_adc_restore_selfcalib(struct stm32_adc *adc)
#define STM32H7_ADC_CALIB_TIMEOUT_US 100000
/**
- * stm32h7_adc_selfcalib() - Procedure to calibrate ADC (from power down)
+ * stm32h7_adc_selfcalib() - Procedure to calibrate ADC
* @adc: stm32 adc instance
- * Exit from power down, calibrate ADC, then return to power down.
+ * Note: Must be called once ADC is out of power down.
*/
static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
{
@@ -908,9 +945,8 @@ static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
int ret;
u32 val;
- ret = stm32h7_adc_exit_pwr_down(adc);
- if (ret)
- return ret;
+ if (adc->cal.calibrated)
+ return true;
/*
* Select calibration mode:
@@ -927,7 +963,7 @@ static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
STM32H7_ADC_CALIB_TIMEOUT_US);
if (ret) {
dev_err(&indio_dev->dev, "calibration failed\n");
- goto pwr_dwn;
+ goto out;
}
/*
@@ -944,18 +980,13 @@ static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
STM32H7_ADC_CALIB_TIMEOUT_US);
if (ret) {
dev_err(&indio_dev->dev, "calibration failed\n");
- goto pwr_dwn;
+ goto out;
}
+out:
stm32_adc_clr_bits(adc, STM32H7_ADC_CR,
STM32H7_ADCALDIF | STM32H7_ADCALLIN);
- /* Read calibration result for future reference */
- ret = stm32h7_adc_read_selfcalib(adc);
-
-pwr_dwn:
- stm32h7_adc_enter_pwr_down(adc);
-
return ret;
}
@@ -972,19 +1003,28 @@ pwr_dwn:
*/
static int stm32h7_adc_prepare(struct stm32_adc *adc)
{
- int ret;
+ int calib, ret;
ret = stm32h7_adc_exit_pwr_down(adc);
if (ret)
return ret;
+ ret = stm32h7_adc_selfcalib(adc);
+ if (ret < 0)
+ goto pwr_dwn;
+ calib = ret;
+
stm32_adc_writel(adc, STM32H7_ADC_DIFSEL, adc->difsel);
ret = stm32h7_adc_enable(adc);
if (ret)
goto pwr_dwn;
- ret = stm32h7_adc_restore_selfcalib(adc);
+ /* Either restore or read calibration result for future reference */
+ if (calib)
+ ret = stm32h7_adc_restore_selfcalib(adc);
+ else
+ ret = stm32h7_adc_read_selfcalib(adc);
if (ret)
goto disable;
@@ -1174,6 +1214,7 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev,
int *res)
{
struct stm32_adc *adc = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
const struct stm32_adc_regspec *regs = adc->cfg->regs;
long timeout;
u32 val;
@@ -1183,10 +1224,10 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev,
adc->bufi = 0;
- if (adc->cfg->prepare) {
- ret = adc->cfg->prepare(adc);
- if (ret)
- return ret;
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ return ret;
}
/* Apply sampling time settings */
@@ -1224,8 +1265,8 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev,
stm32_adc_conv_irq_disable(adc);
- if (adc->cfg->unprepare)
- adc->cfg->unprepare(adc);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
return ret;
}
@@ -1333,15 +1374,22 @@ static int stm32_adc_update_scan_mode(struct iio_dev *indio_dev,
const unsigned long *scan_mask)
{
struct stm32_adc *adc = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
int ret;
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ return ret;
+ }
+
adc->num_conv = bitmap_weight(scan_mask, indio_dev->masklength);
ret = stm32_adc_conf_scan_seq(indio_dev, scan_mask);
- if (ret)
- return ret;
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
- return 0;
+ return ret;
}
static int stm32_adc_of_xlate(struct iio_dev *indio_dev,
@@ -1371,12 +1419,23 @@ static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev,
unsigned *readval)
{
struct stm32_adc *adc = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
+ int ret;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ return ret;
+ }
if (!readval)
stm32_adc_writel(adc, reg, writeval);
else
*readval = stm32_adc_readl(adc, reg);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
return 0;
}
@@ -1459,21 +1518,22 @@ static int stm32_adc_dma_start(struct iio_dev *indio_dev)
return 0;
}
-static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
+static int __stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
{
struct stm32_adc *adc = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
int ret;
- if (adc->cfg->prepare) {
- ret = adc->cfg->prepare(adc);
- if (ret)
- return ret;
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ return ret;
}
ret = stm32_adc_set_trig(indio_dev, indio_dev->trig);
if (ret) {
dev_err(&indio_dev->dev, "Can't set trigger\n");
- goto err_unprepare;
+ goto err_pm_put;
}
ret = stm32_adc_dma_start(indio_dev);
@@ -1482,10 +1542,6 @@ static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
goto err_clr_trig;
}
- ret = iio_triggered_buffer_postenable(indio_dev);
- if (ret < 0)
- goto err_stop_dma;
-
/* Reset adc buffer index */
adc->bufi = 0;
@@ -1496,39 +1552,58 @@ static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
return 0;
-err_stop_dma:
- if (adc->dma_chan)
- dmaengine_terminate_all(adc->dma_chan);
err_clr_trig:
stm32_adc_set_trig(indio_dev, NULL);
-err_unprepare:
- if (adc->cfg->unprepare)
- adc->cfg->unprepare(adc);
+err_pm_put:
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
return ret;
}
-static int stm32_adc_buffer_predisable(struct iio_dev *indio_dev)
+static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
{
- struct stm32_adc *adc = iio_priv(indio_dev);
int ret;
+ ret = iio_triggered_buffer_postenable(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ ret = __stm32_adc_buffer_postenable(indio_dev);
+ if (ret < 0)
+ iio_triggered_buffer_predisable(indio_dev);
+
+ return ret;
+}
+
+static void __stm32_adc_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct stm32_adc *adc = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
+
adc->cfg->stop_conv(adc);
if (!adc->dma_chan)
stm32_adc_conv_irq_disable(adc);
- ret = iio_triggered_buffer_predisable(indio_dev);
- if (ret < 0)
- dev_err(&indio_dev->dev, "predisable failed\n");
-
if (adc->dma_chan)
dmaengine_terminate_all(adc->dma_chan);
if (stm32_adc_set_trig(indio_dev, NULL))
dev_err(&indio_dev->dev, "Can't clear trigger\n");
- if (adc->cfg->unprepare)
- adc->cfg->unprepare(adc);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+}
+
+static int stm32_adc_buffer_predisable(struct iio_dev *indio_dev)
+{
+ int ret;
+
+ __stm32_adc_buffer_predisable(indio_dev);
+
+ ret = iio_triggered_buffer_predisable(indio_dev);
+ if (ret < 0)
+ dev_err(&indio_dev->dev, "predisable failed\n");
return ret;
}
@@ -1867,32 +1942,17 @@ static int stm32_adc_probe(struct platform_device *pdev)
}
}
- if (adc->clk) {
- ret = clk_prepare_enable(adc->clk);
- if (ret < 0) {
- dev_err(&pdev->dev, "clk enable failed\n");
- return ret;
- }
- }
-
ret = stm32_adc_of_get_resolution(indio_dev);
if (ret < 0)
- goto err_clk_disable;
- stm32_adc_set_res(adc);
-
- if (adc->cfg->selfcalib) {
- ret = adc->cfg->selfcalib(adc);
- if (ret)
- goto err_clk_disable;
- }
+ return ret;
ret = stm32_adc_chan_of_init(indio_dev);
if (ret < 0)
- goto err_clk_disable;
+ return ret;
ret = stm32_adc_dma_request(indio_dev);
if (ret < 0)
- goto err_clk_disable;
+ return ret;
ret = iio_triggered_buffer_setup(indio_dev,
&iio_pollfunc_store_time,
@@ -1903,15 +1963,35 @@ static int stm32_adc_probe(struct platform_device *pdev)
goto err_dma_disable;
}
+ /* Get stm32-adc-core PM online */
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_set_autosuspend_delay(dev, STM32_ADC_HW_STOP_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_enable(dev);
+
+ ret = stm32_adc_hw_start(dev);
+ if (ret)
+ goto err_buffer_cleanup;
+
ret = iio_device_register(indio_dev);
if (ret) {
dev_err(&pdev->dev, "iio dev register failed\n");
- goto err_buffer_cleanup;
+ goto err_hw_stop;
}
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
return 0;
+err_hw_stop:
+ stm32_adc_hw_stop(dev);
+
err_buffer_cleanup:
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
iio_triggered_buffer_cleanup(indio_dev);
err_dma_disable:
@@ -1921,9 +2001,6 @@ err_dma_disable:
adc->rx_buf, adc->rx_dma_buf);
dma_release_channel(adc->dma_chan);
}
-err_clk_disable:
- if (adc->clk)
- clk_disable_unprepare(adc->clk);
return ret;
}
@@ -1933,7 +2010,12 @@ static int stm32_adc_remove(struct platform_device *pdev)
struct stm32_adc *adc = platform_get_drvdata(pdev);
struct iio_dev *indio_dev = iio_priv_to_dev(adc);
+ pm_runtime_get_sync(&pdev->dev);
iio_device_unregister(indio_dev);
+ stm32_adc_hw_stop(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
iio_triggered_buffer_cleanup(indio_dev);
if (adc->dma_chan) {
dma_free_coherent(adc->dma_chan->device->dev,
@@ -1941,12 +2023,62 @@ static int stm32_adc_remove(struct platform_device *pdev)
adc->rx_buf, adc->rx_dma_buf);
dma_release_channel(adc->dma_chan);
}
- if (adc->clk)
- clk_disable_unprepare(adc->clk);
return 0;
}
+#if defined(CONFIG_PM_SLEEP)
+static int stm32_adc_suspend(struct device *dev)
+{
+ struct stm32_adc *adc = dev_get_drvdata(dev);
+ struct iio_dev *indio_dev = iio_priv_to_dev(adc);
+
+ if (iio_buffer_enabled(indio_dev))
+ __stm32_adc_buffer_predisable(indio_dev);
+
+ return pm_runtime_force_suspend(dev);
+}
+
+static int stm32_adc_resume(struct device *dev)
+{
+ struct stm32_adc *adc = dev_get_drvdata(dev);
+ struct iio_dev *indio_dev = iio_priv_to_dev(adc);
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret < 0)
+ return ret;
+
+ if (!iio_buffer_enabled(indio_dev))
+ return 0;
+
+ ret = stm32_adc_update_scan_mode(indio_dev,
+ indio_dev->active_scan_mask);
+ if (ret < 0)
+ return ret;
+
+ return __stm32_adc_buffer_postenable(indio_dev);
+}
+#endif
+
+#if defined(CONFIG_PM)
+static int stm32_adc_runtime_suspend(struct device *dev)
+{
+ return stm32_adc_hw_stop(dev);
+}
+
+static int stm32_adc_runtime_resume(struct device *dev)
+{
+ return stm32_adc_hw_start(dev);
+}
+#endif
+
+static const struct dev_pm_ops stm32_adc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(stm32_adc_suspend, stm32_adc_resume)
+ SET_RUNTIME_PM_OPS(stm32_adc_runtime_suspend, stm32_adc_runtime_resume,
+ NULL)
+};
+
static const struct stm32_adc_cfg stm32f4_adc_cfg = {
.regs = &stm32f4_adc_regspec,
.adc_info = &stm32f4_adc_info,
@@ -1961,7 +2093,6 @@ static const struct stm32_adc_cfg stm32h7_adc_cfg = {
.regs = &stm32h7_adc_regspec,
.adc_info = &stm32h7_adc_info,
.trigs = stm32h7_adc_trigs,
- .selfcalib = stm32h7_adc_selfcalib,
.start_conv = stm32h7_adc_start_conv,
.stop_conv = stm32h7_adc_stop_conv,
.prepare = stm32h7_adc_prepare,
@@ -1974,7 +2105,6 @@ static const struct stm32_adc_cfg stm32mp1_adc_cfg = {
.adc_info = &stm32h7_adc_info,
.trigs = stm32h7_adc_trigs,
.has_vregready = true,
- .selfcalib = stm32h7_adc_selfcalib,
.start_conv = stm32h7_adc_start_conv,
.stop_conv = stm32h7_adc_stop_conv,
.prepare = stm32h7_adc_prepare,
@@ -1996,6 +2126,7 @@ static struct platform_driver stm32_adc_driver = {
.driver = {
.name = "stm32-adc",
.of_match_table = stm32_adc_of_match,
+ .pm = &stm32_adc_pm_ops,
},
};
module_platform_driver(stm32_adc_driver);
diff --git a/drivers/iio/adc/stmpe-adc.c b/drivers/iio/adc/stmpe-adc.c
new file mode 100644
index 000000000000..37f4b74a5d32
--- /dev/null
+++ b/drivers/iio/adc/stmpe-adc.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * STMicroelectronics STMPE811 IIO ADC Driver
+ *
+ * 4 channel, 10/12-bit ADC
+ *
+ * Copyright (C) 2013-2018 Toradex AG <stefan.agner@toradex.com>
+ */
+
+#include <linux/completion.h>
+#include <linux/err.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/stmpe.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+
+#define STMPE_REG_INT_STA 0x0B
+#define STMPE_REG_ADC_INT_EN 0x0E
+#define STMPE_REG_ADC_INT_STA 0x0F
+
+#define STMPE_REG_ADC_CTRL1 0x20
+#define STMPE_REG_ADC_CTRL2 0x21
+#define STMPE_REG_ADC_CAPT 0x22
+#define STMPE_REG_ADC_DATA_CH(channel) (0x30 + 2 * (channel))
+
+#define STMPE_REG_TEMP_CTRL 0x60
+#define STMPE_TEMP_CTRL_ENABLE BIT(0)
+#define STMPE_TEMP_CTRL_ACQ BIT(1)
+#define STMPE_TEMP_CTRL_THRES_EN BIT(3)
+#define STMPE_START_ONE_TEMP_CONV (STMPE_TEMP_CTRL_ENABLE | \
+ STMPE_TEMP_CTRL_ACQ | \
+ STMPE_TEMP_CTRL_THRES_EN)
+#define STMPE_REG_TEMP_DATA 0x61
+#define STMPE_REG_TEMP_TH 0x63
+#define STMPE_ADC_LAST_NR 7
+#define STMPE_TEMP_CHANNEL (STMPE_ADC_LAST_NR + 1)
+
+#define STMPE_ADC_CH(channel) ((1 << (channel)) & 0xff)
+
+#define STMPE_ADC_TIMEOUT msecs_to_jiffies(1000)
+
+struct stmpe_adc {
+ struct stmpe *stmpe;
+ struct clk *clk;
+ struct device *dev;
+ struct mutex lock;
+
+ /* We are allocating plus one for the temperature channel */
+ struct iio_chan_spec stmpe_adc_iio_channels[STMPE_ADC_LAST_NR + 2];
+
+ struct completion completion;
+
+ u8 channel;
+ u32 value;
+};
+
+static int stmpe_read_voltage(struct stmpe_adc *info,
+ struct iio_chan_spec const *chan, int *val)
+{
+ long ret;
+
+ mutex_lock(&info->lock);
+
+ info->channel = (u8)chan->channel;
+
+ if (info->channel > STMPE_ADC_LAST_NR) {
+ mutex_unlock(&info->lock);
+ return -EINVAL;
+ }
+
+ stmpe_reg_write(info->stmpe, STMPE_REG_ADC_INT_EN,
+ STMPE_ADC_CH(info->channel));
+
+ stmpe_reg_write(info->stmpe, STMPE_REG_ADC_CAPT,
+ STMPE_ADC_CH(info->channel));
+
+ *val = info->value;
+
+ ret = wait_for_completion_interruptible_timeout
+ (&info->completion, STMPE_ADC_TIMEOUT);
+
+ if (ret <= 0) {
+ mutex_unlock(&info->lock);
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else
+ return ret;
+ }
+
+ *val = info->value;
+
+ mutex_unlock(&info->lock);
+
+ return 0;
+}
+
+static int stmpe_read_temp(struct stmpe_adc *info,
+ struct iio_chan_spec const *chan, int *val)
+{
+ long ret;
+
+ mutex_lock(&info->lock);
+
+ info->channel = (u8)chan->channel;
+
+ if (info->channel != STMPE_TEMP_CHANNEL) {
+ mutex_unlock(&info->lock);
+ return -EINVAL;
+ }
+
+ stmpe_reg_write(info->stmpe, STMPE_REG_TEMP_CTRL,
+ STMPE_START_ONE_TEMP_CONV);
+
+ ret = wait_for_completion_interruptible_timeout
+ (&info->completion, STMPE_ADC_TIMEOUT);
+
+ if (ret <= 0) {
+ mutex_unlock(&info->lock);
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else
+ return ret;
+ }
+
+ /*
+ * absolute temp = +V3.3 * value /7.51 [K]
+ * scale to [milli °C]
+ */
+ *val = ((449960l * info->value) / 1024l) - 273150;
+
+ mutex_unlock(&info->lock);
+
+ return 0;
+}
+
+static int stmpe_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask)
+{
+ struct stmpe_adc *info = iio_priv(indio_dev);
+ long ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ case IIO_CHAN_INFO_PROCESSED:
+
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ ret = stmpe_read_voltage(info, chan, val);
+ break;
+
+ case IIO_TEMP:
+ ret = stmpe_read_temp(info, chan, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = 3300;
+ *val2 = info->stmpe->mod_12b ? 12 : 10;
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static irqreturn_t stmpe_adc_isr(int irq, void *dev_id)
+{
+ struct stmpe_adc *info = (struct stmpe_adc *)dev_id;
+ u16 data;
+
+ if (info->channel > STMPE_TEMP_CHANNEL)
+ return IRQ_NONE;
+
+ if (info->channel <= STMPE_ADC_LAST_NR) {
+ int int_sta;
+
+ int_sta = stmpe_reg_read(info->stmpe, STMPE_REG_ADC_INT_STA);
+
+ /* Is the interrupt relevant */
+ if (!(int_sta & STMPE_ADC_CH(info->channel)))
+ return IRQ_NONE;
+
+ /* Read value */
+ stmpe_block_read(info->stmpe,
+ STMPE_REG_ADC_DATA_CH(info->channel), 2, (u8 *) &data);
+
+ stmpe_reg_write(info->stmpe, STMPE_REG_ADC_INT_STA, int_sta);
+ } else if (info->channel == STMPE_TEMP_CHANNEL) {
+ /* Read value */
+ stmpe_block_read(info->stmpe, STMPE_REG_TEMP_DATA, 2,
+ (u8 *) &data);
+ }
+
+ info->value = (u32) be16_to_cpu(data);
+ complete(&info->completion);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_info stmpe_adc_iio_info = {
+ .read_raw = &stmpe_read_raw,
+};
+
+static void stmpe_adc_voltage_chan(struct iio_chan_spec *ics, int chan)
+{
+ ics->type = IIO_VOLTAGE;
+ ics->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+ ics->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
+ ics->indexed = 1;
+ ics->channel = chan;
+}
+
+static void stmpe_adc_temp_chan(struct iio_chan_spec *ics, int chan)
+{
+ ics->type = IIO_TEMP;
+ ics->info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED);
+ ics->indexed = 1;
+ ics->channel = chan;
+}
+
+static int stmpe_adc_init_hw(struct stmpe_adc *adc)
+{
+ int ret;
+ struct stmpe *stmpe = adc->stmpe;
+
+ ret = stmpe_enable(stmpe, STMPE_BLOCK_ADC);
+ if (ret) {
+ dev_err(stmpe->dev, "Could not enable clock for ADC\n");
+ return ret;
+ }
+
+ ret = stmpe811_adc_common_init(stmpe);
+ if (ret) {
+ stmpe_disable(stmpe, STMPE_BLOCK_ADC);
+ return ret;
+ }
+
+ /* use temp irq for each conversion completion */
+ stmpe_reg_write(stmpe, STMPE_REG_TEMP_TH, 0);
+ stmpe_reg_write(stmpe, STMPE_REG_TEMP_TH + 1, 0);
+
+ return 0;
+}
+
+static int stmpe_adc_probe(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev;
+ struct stmpe_adc *info;
+ struct device_node *np;
+ u32 norequest_mask = 0;
+ int irq_temp, irq_adc;
+ int num_chan = 0;
+ int i = 0;
+ int ret;
+
+ irq_adc = platform_get_irq_byname(pdev, "STMPE_ADC");
+ if (irq_adc < 0)
+ return irq_adc;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct stmpe_adc));
+ if (!indio_dev) {
+ dev_err(&pdev->dev, "failed allocating iio device\n");
+ return -ENOMEM;
+ }
+
+ info = iio_priv(indio_dev);
+ mutex_init(&info->lock);
+
+ init_completion(&info->completion);
+ ret = devm_request_threaded_irq(&pdev->dev, irq_adc, NULL,
+ stmpe_adc_isr, IRQF_ONESHOT,
+ "stmpe-adc", info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
+ irq_adc);
+ return ret;
+ }
+
+ irq_temp = platform_get_irq_byname(pdev, "STMPE_TEMP_SENS");
+ if (irq_temp >= 0) {
+ ret = devm_request_threaded_irq(&pdev->dev, irq_temp, NULL,
+ stmpe_adc_isr, IRQF_ONESHOT,
+ "stmpe-adc", info);
+ if (ret < 0)
+ dev_warn(&pdev->dev, "failed requesting irq for"
+ " temp sensor, irq = %d\n", irq_temp);
+ }
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ indio_dev->name = dev_name(&pdev->dev);
+ indio_dev->dev.parent = &pdev->dev;
+ indio_dev->info = &stmpe_adc_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ info->stmpe = dev_get_drvdata(pdev->dev.parent);
+
+ np = pdev->dev.of_node;
+
+ if (!np)
+ dev_err(&pdev->dev, "no device tree node found\n");
+
+ of_property_read_u32(np, "st,norequest-mask", &norequest_mask);
+
+ for_each_clear_bit(i, (unsigned long *) &norequest_mask,
+ (STMPE_ADC_LAST_NR + 1)) {
+ stmpe_adc_voltage_chan(&info->stmpe_adc_iio_channels[num_chan], i);
+ num_chan++;
+ }
+ stmpe_adc_temp_chan(&info->stmpe_adc_iio_channels[num_chan], i);
+ num_chan++;
+ indio_dev->channels = info->stmpe_adc_iio_channels;
+ indio_dev->num_channels = num_chan;
+
+ ret = stmpe_adc_init_hw(info);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static int __maybe_unused stmpe_adc_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct stmpe_adc *info = iio_priv(indio_dev);
+
+ stmpe_adc_init_hw(info);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(stmpe_adc_pm_ops, NULL, stmpe_adc_resume);
+
+static struct platform_driver stmpe_adc_driver = {
+ .probe = stmpe_adc_probe,
+ .driver = {
+ .name = "stmpe-adc",
+ .pm = &stmpe_adc_pm_ops,
+ },
+};
+
+module_platform_driver(stmpe_adc_driver);
+
+MODULE_AUTHOR("Stefan Agner <stefan.agner@toradex.com>");
+MODULE_DESCRIPTION("STMPEXXX ADC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:stmpe-adc");
diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c
index 7cf39b3e2416..1e5a936b5b6a 100644
--- a/drivers/iio/adc/ti-adc128s052.c
+++ b/drivers/iio/adc/ti-adc128s052.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2014 Angelo Compagnucci <angelo.compagnucci@gmail.com>
*
@@ -6,16 +7,14 @@
* http://www.ti.com/lit/ds/symlink/adc128s052.pdf
* http://www.ti.com/lit/ds/symlink/adc122s021.pdf
* http://www.ti.com/lit/ds/symlink/adc124s021.pdf
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
+#include <linux/acpi.h>
#include <linux/err.h>
#include <linux/spi/spi.h>
#include <linux/module.h>
#include <linux/iio/iio.h>
+#include <linux/property.h>
#include <linux/regulator/consumer.h>
struct adc128_configuration {
@@ -135,10 +134,15 @@ static const struct iio_info adc128_info = {
static int adc128_probe(struct spi_device *spi)
{
struct iio_dev *indio_dev;
+ unsigned int config;
struct adc128 *adc;
- int config = spi_get_device_id(spi)->driver_data;
int ret;
+ if (dev_fwnode(&spi->dev))
+ config = (unsigned long) device_get_match_data(&spi->dev);
+ else
+ config = spi_get_device_id(spi)->driver_data;
+
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc));
if (!indio_dev)
return -ENOMEM;
@@ -186,23 +190,40 @@ static int adc128_remove(struct spi_device *spi)
static const struct of_device_id adc128_of_match[] = {
{ .compatible = "ti,adc128s052", },
{ .compatible = "ti,adc122s021", },
+ { .compatible = "ti,adc122s051", },
+ { .compatible = "ti,adc122s101", },
{ .compatible = "ti,adc124s021", },
+ { .compatible = "ti,adc124s051", },
+ { .compatible = "ti,adc124s101", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, adc128_of_match);
static const struct spi_device_id adc128_id[] = {
- { "adc128s052", 0}, /* index into adc128_config */
- { "adc122s021", 1},
- { "adc124s021", 2},
+ { "adc128s052", 0 }, /* index into adc128_config */
+ { "adc122s021", 1 },
+ { "adc122s051", 1 },
+ { "adc122s101", 1 },
+ { "adc124s021", 2 },
+ { "adc124s051", 2 },
+ { "adc124s101", 2 },
{ }
};
MODULE_DEVICE_TABLE(spi, adc128_id);
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id adc128_acpi_match[] = {
+ { "AANT1280", 2 }, /* ADC124S021 compatible ACPI ID */
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, adc128_acpi_match);
+#endif
+
static struct spi_driver adc128_driver = {
.driver = {
.name = "adc128s052",
.of_match_table = of_match_ptr(adc128_of_match),
+ .acpi_match_table = ACPI_PTR(adc128_acpi_match),
},
.probe = adc128_probe,
.remove = adc128_remove,
diff --git a/drivers/iio/adc/ti-ads124s08.c b/drivers/iio/adc/ti-ads124s08.c
new file mode 100644
index 000000000000..53f17e4f2f23
--- /dev/null
+++ b/drivers/iio/adc/ti-ads124s08.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0
+/* TI ADS124S0X chip family driver
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/sysfs.h>
+
+/* Commands */
+#define ADS124S08_CMD_NOP 0x00
+#define ADS124S08_CMD_WAKEUP 0x02
+#define ADS124S08_CMD_PWRDWN 0x04
+#define ADS124S08_CMD_RESET 0x06
+#define ADS124S08_CMD_START 0x08
+#define ADS124S08_CMD_STOP 0x0a
+#define ADS124S08_CMD_SYOCAL 0x16
+#define ADS124S08_CMD_SYGCAL 0x17
+#define ADS124S08_CMD_SFOCAL 0x19
+#define ADS124S08_CMD_RDATA 0x12
+#define ADS124S08_CMD_RREG 0x20
+#define ADS124S08_CMD_WREG 0x40
+
+/* Registers */
+#define ADS124S08_ID_REG 0x00
+#define ADS124S08_STATUS 0x01
+#define ADS124S08_INPUT_MUX 0x02
+#define ADS124S08_PGA 0x03
+#define ADS124S08_DATA_RATE 0x04
+#define ADS124S08_REF 0x05
+#define ADS124S08_IDACMAG 0x06
+#define ADS124S08_IDACMUX 0x07
+#define ADS124S08_VBIAS 0x08
+#define ADS124S08_SYS 0x09
+#define ADS124S08_OFCAL0 0x0a
+#define ADS124S08_OFCAL1 0x0b
+#define ADS124S08_OFCAL2 0x0c
+#define ADS124S08_FSCAL0 0x0d
+#define ADS124S08_FSCAL1 0x0e
+#define ADS124S08_FSCAL2 0x0f
+#define ADS124S08_GPIODAT 0x10
+#define ADS124S08_GPIOCON 0x11
+
+/* ADS124S0x common channels */
+#define ADS124S08_AIN0 0x00
+#define ADS124S08_AIN1 0x01
+#define ADS124S08_AIN2 0x02
+#define ADS124S08_AIN3 0x03
+#define ADS124S08_AIN4 0x04
+#define ADS124S08_AIN5 0x05
+#define ADS124S08_AINCOM 0x0c
+/* ADS124S08 only channels */
+#define ADS124S08_AIN6 0x06
+#define ADS124S08_AIN7 0x07
+#define ADS124S08_AIN8 0x08
+#define ADS124S08_AIN9 0x09
+#define ADS124S08_AIN10 0x0a
+#define ADS124S08_AIN11 0x0b
+#define ADS124S08_MAX_CHANNELS 12
+
+#define ADS124S08_POS_MUX_SHIFT 0x04
+#define ADS124S08_INT_REF 0x09
+
+#define ADS124S08_START_REG_MASK 0x1f
+#define ADS124S08_NUM_BYTES_MASK 0x1f
+
+#define ADS124S08_START_CONV 0x01
+#define ADS124S08_STOP_CONV 0x00
+
+enum ads124s_id {
+ ADS124S08_ID,
+ ADS124S06_ID,
+};
+
+struct ads124s_chip_info {
+ const struct iio_chan_spec *channels;
+ unsigned int num_channels;
+};
+
+struct ads124s_private {
+ const struct ads124s_chip_info *chip_info;
+ struct gpio_desc *reset_gpio;
+ struct spi_device *spi;
+ struct mutex lock;
+ u8 data[5] ____cacheline_aligned;
+};
+
+#define ADS124S08_CHAN(index) \
+{ \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = index, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 32, \
+ .storagebits = 32, \
+ }, \
+}
+
+static const struct iio_chan_spec ads124s06_channels[] = {
+ ADS124S08_CHAN(0),
+ ADS124S08_CHAN(1),
+ ADS124S08_CHAN(2),
+ ADS124S08_CHAN(3),
+ ADS124S08_CHAN(4),
+ ADS124S08_CHAN(5),
+};
+
+static const struct iio_chan_spec ads124s08_channels[] = {
+ ADS124S08_CHAN(0),
+ ADS124S08_CHAN(1),
+ ADS124S08_CHAN(2),
+ ADS124S08_CHAN(3),
+ ADS124S08_CHAN(4),
+ ADS124S08_CHAN(5),
+ ADS124S08_CHAN(6),
+ ADS124S08_CHAN(7),
+ ADS124S08_CHAN(8),
+ ADS124S08_CHAN(9),
+ ADS124S08_CHAN(10),
+ ADS124S08_CHAN(11),
+};
+
+static const struct ads124s_chip_info ads124s_chip_info_tbl[] = {
+ [ADS124S08_ID] = {
+ .channels = ads124s08_channels,
+ .num_channels = ARRAY_SIZE(ads124s08_channels),
+ },
+ [ADS124S06_ID] = {
+ .channels = ads124s06_channels,
+ .num_channels = ARRAY_SIZE(ads124s06_channels),
+ },
+};
+
+static int ads124s_write_cmd(struct iio_dev *indio_dev, u8 command)
+{
+ struct ads124s_private *priv = iio_priv(indio_dev);
+
+ priv->data[0] = command;
+
+ return spi_write(priv->spi, &priv->data[0], 1);
+}
+
+static int ads124s_write_reg(struct iio_dev *indio_dev, u8 reg, u8 data)
+{
+ struct ads124s_private *priv = iio_priv(indio_dev);
+
+ priv->data[0] = ADS124S08_CMD_WREG | reg;
+ priv->data[1] = 0x0;
+ priv->data[2] = data;
+
+ return spi_write(priv->spi, &priv->data[0], 3);
+}
+
+static int ads124s_reset(struct iio_dev *indio_dev)
+{
+ struct ads124s_private *priv = iio_priv(indio_dev);
+
+ if (priv->reset_gpio) {
+ gpiod_set_value(priv->reset_gpio, 0);
+ udelay(200);
+ gpiod_set_value(priv->reset_gpio, 1);
+ } else {
+ return ads124s_write_cmd(indio_dev, ADS124S08_CMD_RESET);
+ }
+
+ return 0;
+};
+
+static int ads124s_read(struct iio_dev *indio_dev, unsigned int chan)
+{
+ struct ads124s_private *priv = iio_priv(indio_dev);
+ int ret;
+ u32 tmp;
+ struct spi_transfer t[] = {
+ {
+ .tx_buf = &priv->data[0],
+ .len = 4,
+ .cs_change = 1,
+ }, {
+ .tx_buf = &priv->data[1],
+ .rx_buf = &priv->data[1],
+ .len = 4,
+ },
+ };
+
+ priv->data[0] = ADS124S08_CMD_RDATA;
+ memset(&priv->data[1], ADS124S08_CMD_NOP, sizeof(priv->data));
+
+ ret = spi_sync_transfer(priv->spi, t, ARRAY_SIZE(t));
+ if (ret < 0)
+ return ret;
+
+ tmp = priv->data[2] << 16 | priv->data[3] << 8 | priv->data[4];
+
+ return tmp;
+}
+
+static int ads124s_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long m)
+{
+ struct ads124s_private *priv = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&priv->lock);
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ ret = ads124s_write_reg(indio_dev, ADS124S08_INPUT_MUX,
+ chan->channel);
+ if (ret) {
+ dev_err(&priv->spi->dev, "Set ADC CH failed\n");
+ goto out;
+ }
+
+ ret = ads124s_write_cmd(indio_dev, ADS124S08_START_CONV);
+ if (ret) {
+ dev_err(&priv->spi->dev, "Start conversions failed\n");
+ goto out;
+ }
+
+ ret = ads124s_read(indio_dev, chan->channel);
+ if (ret < 0) {
+ dev_err(&priv->spi->dev, "Read ADC failed\n");
+ goto out;
+ }
+
+ *val = ret;
+
+ ret = ads124s_write_cmd(indio_dev, ADS124S08_STOP_CONV);
+ if (ret) {
+ dev_err(&priv->spi->dev, "Stop conversions failed\n");
+ goto out;
+ }
+
+ ret = IIO_VAL_INT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+out:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static const struct iio_info ads124s_info = {
+ .read_raw = &ads124s_read_raw,
+};
+
+static irqreturn_t ads124s_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ads124s_private *priv = iio_priv(indio_dev);
+ u32 buffer[ADS124S08_MAX_CHANNELS + sizeof(s64)/sizeof(u16)];
+ int scan_index, j = 0;
+ int ret;
+
+ for_each_set_bit(scan_index, indio_dev->active_scan_mask,
+ indio_dev->masklength) {
+ ret = ads124s_write_reg(indio_dev, ADS124S08_INPUT_MUX,
+ scan_index);
+ if (ret)
+ dev_err(&priv->spi->dev, "Set ADC CH failed\n");
+
+ ret = ads124s_write_cmd(indio_dev, ADS124S08_START_CONV);
+ if (ret)
+ dev_err(&priv->spi->dev, "Start ADC conversions failed\n");
+
+ buffer[j] = ads124s_read(indio_dev, scan_index);
+ ret = ads124s_write_cmd(indio_dev, ADS124S08_STOP_CONV);
+ if (ret)
+ dev_err(&priv->spi->dev, "Stop ADC conversions failed\n");
+
+ j++;
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, buffer,
+ pf->timestamp);
+
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int ads124s_probe(struct spi_device *spi)
+{
+ struct ads124s_private *ads124s_priv;
+ struct iio_dev *indio_dev;
+ const struct spi_device_id *spi_id = spi_get_device_id(spi);
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*ads124s_priv));
+ if (indio_dev == NULL)
+ return -ENOMEM;
+
+ ads124s_priv = iio_priv(indio_dev);
+
+ ads124s_priv->reset_gpio = devm_gpiod_get_optional(&spi->dev,
+ "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ads124s_priv->reset_gpio))
+ dev_info(&spi->dev, "Reset GPIO not defined\n");
+
+ ads124s_priv->chip_info = &ads124s_chip_info_tbl[spi_id->driver_data];
+
+ spi_set_drvdata(spi, indio_dev);
+
+ ads124s_priv->spi = spi;
+
+ indio_dev->name = spi_id->name;
+ indio_dev->dev.parent = &spi->dev;
+ indio_dev->dev.of_node = spi->dev.of_node;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = ads124s_priv->chip_info->channels;
+ indio_dev->num_channels = ads124s_priv->chip_info->num_channels;
+ indio_dev->info = &ads124s_info;
+
+ mutex_init(&ads124s_priv->lock);
+
+ ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL,
+ ads124s_trigger_handler, NULL);
+ if (ret) {
+ dev_err(&spi->dev, "iio triggered buffer setup failed\n");
+ return ret;
+ }
+
+ ads124s_reset(indio_dev);
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct spi_device_id ads124s_id[] = {
+ { "ads124s06", ADS124S06_ID },
+ { "ads124s08", ADS124S08_ID },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ads124s_id);
+
+static const struct of_device_id ads124s_of_table[] = {
+ { .compatible = "ti,ads124s06" },
+ { .compatible = "ti,ads124s08" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ads124s_of_table);
+
+static struct spi_driver ads124s_driver = {
+ .driver = {
+ .name = "ads124s08",
+ .of_match_table = ads124s_of_table,
+ },
+ .probe = ads124s_probe,
+ .id_table = ads124s_id,
+};
+module_spi_driver(ads124s_driver);
+
+MODULE_AUTHOR("Dan Murphy <dmuprhy@ti.com>");
+MODULE_DESCRIPTION("TI TI_ADS12S0X ADC");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/ti-ads8688.c b/drivers/iio/adc/ti-ads8688.c
index 184d686ebd99..8b4568edd5cb 100644
--- a/drivers/iio/adc/ti-ads8688.c
+++ b/drivers/iio/adc/ti-ads8688.c
@@ -41,6 +41,7 @@
#define ADS8688_VREF_MV 4096
#define ADS8688_REALBITS 16
+#define ADS8688_MAX_CHANNELS 8
/*
* enum ads8688_range - ADS8688 reference voltage range
@@ -385,7 +386,7 @@ static irqreturn_t ads8688_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
- u16 buffer[8];
+ u16 buffer[ADS8688_MAX_CHANNELS + sizeof(s64)/sizeof(u16)];
int i, j = 0;
for (i = 0; i < indio_dev->masklength; i++) {
diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c
index cafb1dcadc48..9d984f2a8ba7 100644
--- a/drivers/iio/adc/ti_am335x_adc.c
+++ b/drivers/iio/adc/ti_am335x_adc.c
@@ -142,7 +142,10 @@ static void tiadc_step_config(struct iio_dev *indio_dev)
stepconfig |= STEPCONFIG_MODE_SWCNT;
tiadc_writel(adc_dev, REG_STEPCONFIG(steps),
- stepconfig | STEPCONFIG_INP(chan));
+ stepconfig | STEPCONFIG_INP(chan) |
+ STEPCONFIG_INM_ADCREFM |
+ STEPCONFIG_RFP_VREFP |
+ STEPCONFIG_RFM_VREFN);
if (adc_dev->open_delay[i] > STEPDELAY_OPEN_MASK) {
dev_warn(dev, "chan %d open delay truncating to 0x3FFFF\n",
diff --git a/drivers/iio/adc/xilinx-xadc-core.c b/drivers/iio/adc/xilinx-xadc-core.c
index 3f6be5ac049a..6401ca7a9a20 100644
--- a/drivers/iio/adc/xilinx-xadc-core.c
+++ b/drivers/iio/adc/xilinx-xadc-core.c
@@ -1273,8 +1273,10 @@ static int xadc_probe(struct platform_device *pdev)
xadc->threshold[i] = 0xffff;
else
xadc->threshold[i] = 0;
- xadc_write_adc_reg(xadc, XADC_REG_THRESHOLD(i),
+ ret = xadc_write_adc_reg(xadc, XADC_REG_THRESHOLD(i),
xadc->threshold[i]);
+ if (ret)
+ goto err_free_irq;
}
/* Go to non-buffered mode */
@@ -1290,6 +1292,7 @@ static int xadc_probe(struct platform_device *pdev)
err_free_irq:
free_irq(xadc->irq, indio_dev);
+ cancel_delayed_work_sync(&xadc->zynq_unmask_work);
err_clk_disable_unprepare:
clk_disable_unprepare(xadc->clk);
err_free_samplerate_trigger:
@@ -1319,8 +1322,8 @@ static int xadc_remove(struct platform_device *pdev)
iio_triggered_buffer_cleanup(indio_dev);
}
free_irq(xadc->irq, indio_dev);
+ cancel_delayed_work_sync(&xadc->zynq_unmask_work);
clk_disable_unprepare(xadc->clk);
- cancel_delayed_work(&xadc->zynq_unmask_work);
kfree(xadc->data);
kfree(indio_dev->channels);
diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
index b8e005be4f87..92c684d2b67e 100644
--- a/drivers/iio/chemical/Kconfig
+++ b/drivers/iio/chemical/Kconfig
@@ -61,6 +61,41 @@ config IAQCORE
iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds)
sensors
+config PMS7003
+ tristate "Plantower PMS7003 particulate matter sensor"
+ depends on SERIAL_DEV_BUS
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say Y here to build support for the Plantower PMS7003 particulate
+ matter sensor.
+
+ To compile this driver as a module, choose M here: the module will
+ be called pms7003.
+
+config SENSIRION_SGP30
+ tristate "Sensirion SGPxx gas sensors"
+ depends on I2C
+ select CRC8
+ help
+ Say Y here to build I2C interface support for the following
+ Sensirion SGP gas sensors:
+ * SGP30 gas sensor
+ * SGPC3 low power gas sensor
+
+ To compile this driver as module, choose M here: the
+ module will be called sgp30.
+
+config SPS30
+ tristate "SPS30 particulate matter sensor"
+ depends on I2C
+ select CRC8
+ help
+ Say Y here to build support for the Sensirion SPS30 particulate
+ matter sensor.
+
+ To compile this driver as a module, choose M here: the module will
+ be called sps30.
+
config VZ89X
tristate "SGX Sensortech MiCS VZ89X VOC sensor"
depends on I2C
diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
index 2f4c4ba4d781..f5d1365acb49 100644
--- a/drivers/iio/chemical/Makefile
+++ b/drivers/iio/chemical/Makefile
@@ -9,4 +9,7 @@ obj-$(CONFIG_BME680_I2C) += bme680_i2c.o
obj-$(CONFIG_BME680_SPI) += bme680_spi.o
obj-$(CONFIG_CCS811) += ccs811.o
obj-$(CONFIG_IAQCORE) += ams-iaq-core.o
+obj-$(CONFIG_PMS7003) += pms7003.o
+obj-$(CONFIG_SENSIRION_SGP30) += sgp30.o
+obj-$(CONFIG_SPS30) += sps30.o
obj-$(CONFIG_VZ89X) += vz89x.o
diff --git a/drivers/iio/chemical/atlas-ph-sensor.c b/drivers/iio/chemical/atlas-ph-sensor.c
index a406ad31b096..3a20cb5d9bff 100644
--- a/drivers/iio/chemical/atlas-ph-sensor.c
+++ b/drivers/iio/chemical/atlas-ph-sensor.c
@@ -444,9 +444,8 @@ static int atlas_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_TEMP:
- *val = 1; /* 0.01 */
- *val2 = 100;
- break;
+ *val = 10;
+ return IIO_VAL_INT;
case IIO_PH:
*val = 1; /* 0.001 */
*val2 = 1000;
@@ -477,7 +476,7 @@ static int atlas_write_raw(struct iio_dev *indio_dev,
int val, int val2, long mask)
{
struct atlas_data *data = iio_priv(indio_dev);
- __be32 reg = cpu_to_be32(val);
+ __be32 reg = cpu_to_be32(val / 10);
if (val2 != 0 || val < 0 || val > 20000)
return -EINVAL;
diff --git a/drivers/iio/chemical/bme680.h b/drivers/iio/chemical/bme680.h
index 0ae89b87e2d6..4edc5d21cb9f 100644
--- a/drivers/iio/chemical/bme680.h
+++ b/drivers/iio/chemical/bme680.h
@@ -2,11 +2,9 @@
#ifndef BME680_H_
#define BME680_H_
-#define BME680_REG_CHIP_I2C_ID 0xD0
-#define BME680_REG_CHIP_SPI_ID 0x50
+#define BME680_REG_CHIP_ID 0xD0
#define BME680_CHIP_ID_VAL 0x61
-#define BME680_REG_SOFT_RESET_I2C 0xE0
-#define BME680_REG_SOFT_RESET_SPI 0x60
+#define BME680_REG_SOFT_RESET 0xE0
#define BME680_CMD_SOFTRESET 0xB6
#define BME680_REG_STATUS 0x73
#define BME680_SPI_MEM_PAGE_BIT BIT(4)
diff --git a/drivers/iio/chemical/bme680_core.c b/drivers/iio/chemical/bme680_core.c
index 70c1fe4366f4..ccde4c65ff93 100644
--- a/drivers/iio/chemical/bme680_core.c
+++ b/drivers/iio/chemical/bme680_core.c
@@ -63,9 +63,23 @@ struct bme680_data {
s32 t_fine;
};
+static const struct regmap_range bme680_volatile_ranges[] = {
+ regmap_reg_range(BME680_REG_MEAS_STAT_0, BME680_REG_GAS_R_LSB),
+ regmap_reg_range(BME680_REG_STATUS, BME680_REG_STATUS),
+ regmap_reg_range(BME680_T2_LSB_REG, BME680_GH3_REG),
+};
+
+static const struct regmap_access_table bme680_volatile_table = {
+ .yes_ranges = bme680_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(bme680_volatile_ranges),
+};
+
const struct regmap_config bme680_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
+ .max_register = 0xef,
+ .volatile_table = &bme680_volatile_table,
+ .cache_type = REGCACHE_RBTREE,
};
EXPORT_SYMBOL(bme680_regmap_config);
@@ -316,6 +330,10 @@ static s16 bme680_compensate_temp(struct bme680_data *data,
s64 var1, var2, var3;
s16 calc_temp;
+ /* If the calibration is invalid, attempt to reload it */
+ if (!calib->par_t2)
+ bme680_read_calib(data, calib);
+
var1 = (adc_temp >> 3) - (calib->par_t1 << 1);
var2 = (var1 * calib->par_t2) >> 11;
var3 = ((var1 >> 1) * (var1 >> 1)) >> 12;
@@ -583,8 +601,7 @@ static int bme680_gas_config(struct bme680_data *data)
return ret;
}
-static int bme680_read_temp(struct bme680_data *data,
- int *val, int *val2)
+static int bme680_read_temp(struct bme680_data *data, int *val)
{
struct device *dev = regmap_get_device(data->regmap);
int ret;
@@ -617,10 +634,9 @@ static int bme680_read_temp(struct bme680_data *data,
* compensate_press/compensate_humid to get compensated
* pressure/humidity readings.
*/
- if (val && val2) {
- *val = comp_temp;
- *val2 = 100;
- return IIO_VAL_FRACTIONAL;
+ if (val) {
+ *val = comp_temp * 10; /* Centidegrees to millidegrees */
+ return IIO_VAL_INT;
}
return ret;
@@ -635,7 +651,7 @@ static int bme680_read_press(struct bme680_data *data,
s32 adc_press;
/* Read and compensate temperature to get a reading of t_fine */
- ret = bme680_read_temp(data, NULL, NULL);
+ ret = bme680_read_temp(data, NULL);
if (ret < 0)
return ret;
@@ -668,7 +684,7 @@ static int bme680_read_humid(struct bme680_data *data,
u32 comp_humidity;
/* Read and compensate temperature to get a reading of t_fine */
- ret = bme680_read_temp(data, NULL, NULL);
+ ret = bme680_read_temp(data, NULL);
if (ret < 0)
return ret;
@@ -761,7 +777,7 @@ static int bme680_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_PROCESSED:
switch (chan->type) {
case IIO_TEMP:
- return bme680_read_temp(data, val, val2);
+ return bme680_read_temp(data, val);
case IIO_PRESSURE:
return bme680_read_press(data, val, val2);
case IIO_HUMIDITYRELATIVE:
@@ -867,8 +883,28 @@ int bme680_core_probe(struct device *dev, struct regmap *regmap,
{
struct iio_dev *indio_dev;
struct bme680_data *data;
+ unsigned int val;
int ret;
+ ret = regmap_write(regmap, BME680_REG_SOFT_RESET,
+ BME680_CMD_SOFTRESET);
+ if (ret < 0) {
+ dev_err(dev, "Failed to reset chip\n");
+ return ret;
+ }
+
+ ret = regmap_read(regmap, BME680_REG_CHIP_ID, &val);
+ if (ret < 0) {
+ dev_err(dev, "Error reading chip ID\n");
+ return ret;
+ }
+
+ if (val != BME680_CHIP_ID_VAL) {
+ dev_err(dev, "Wrong chip ID, got %x expected %x\n",
+ val, BME680_CHIP_ID_VAL);
+ return -ENODEV;
+ }
+
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
diff --git a/drivers/iio/chemical/bme680_i2c.c b/drivers/iio/chemical/bme680_i2c.c
index 06d4be539d2e..de9c9e3d23ea 100644
--- a/drivers/iio/chemical/bme680_i2c.c
+++ b/drivers/iio/chemical/bme680_i2c.c
@@ -23,8 +23,6 @@ static int bme680_i2c_probe(struct i2c_client *client,
{
struct regmap *regmap;
const char *name = NULL;
- unsigned int val;
- int ret;
regmap = devm_regmap_init_i2c(client, &bme680_regmap_config);
if (IS_ERR(regmap)) {
@@ -33,25 +31,6 @@ static int bme680_i2c_probe(struct i2c_client *client,
return PTR_ERR(regmap);
}
- ret = regmap_write(regmap, BME680_REG_SOFT_RESET_I2C,
- BME680_CMD_SOFTRESET);
- if (ret < 0) {
- dev_err(&client->dev, "Failed to reset chip\n");
- return ret;
- }
-
- ret = regmap_read(regmap, BME680_REG_CHIP_I2C_ID, &val);
- if (ret < 0) {
- dev_err(&client->dev, "Error reading I2C chip ID\n");
- return ret;
- }
-
- if (val != BME680_CHIP_ID_VAL) {
- dev_err(&client->dev, "Wrong chip ID, got %x expected %x\n",
- val, BME680_CHIP_ID_VAL);
- return -ENODEV;
- }
-
if (id)
name = id->name;
@@ -70,10 +49,17 @@ static const struct acpi_device_id bme680_acpi_match[] = {
};
MODULE_DEVICE_TABLE(acpi, bme680_acpi_match);
+static const struct of_device_id bme680_of_i2c_match[] = {
+ { .compatible = "bosch,bme680", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bme680_of_i2c_match);
+
static struct i2c_driver bme680_i2c_driver = {
.driver = {
.name = "bme680_i2c",
.acpi_match_table = ACPI_PTR(bme680_acpi_match),
+ .of_match_table = bme680_of_i2c_match,
},
.probe = bme680_i2c_probe,
.id_table = bme680_i2c_id,
diff --git a/drivers/iio/chemical/bme680_spi.c b/drivers/iio/chemical/bme680_spi.c
index c9fb05e8d0b9..3b838068a7e4 100644
--- a/drivers/iio/chemical/bme680_spi.c
+++ b/drivers/iio/chemical/bme680_spi.c
@@ -6,33 +6,99 @@
*/
#include <linux/acpi.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include "bme680.h"
+struct bme680_spi_bus_context {
+ struct spi_device *spi;
+ u8 current_page;
+};
+
+/*
+ * In SPI mode there are only 7 address bits, a "page" register determines
+ * which part of the 8-bit range is active. This function looks at the address
+ * and writes the page selection bit if needed
+ */
+static int bme680_regmap_spi_select_page(
+ struct bme680_spi_bus_context *ctx, u8 reg)
+{
+ struct spi_device *spi = ctx->spi;
+ int ret;
+ u8 buf[2];
+ u8 page = (reg & 0x80) ? 0 : 1; /* Page "1" is low range */
+
+ if (page == ctx->current_page)
+ return 0;
+
+ /*
+ * Data sheet claims we're only allowed to change bit 4, so we must do
+ * a read-modify-write on each and every page select
+ */
+ buf[0] = BME680_REG_STATUS;
+ ret = spi_write_then_read(spi, buf, 1, buf + 1, 1);
+ if (ret < 0) {
+ dev_err(&spi->dev, "failed to set page %u\n", page);
+ return ret;
+ }
+
+ buf[0] = BME680_REG_STATUS;
+ if (page)
+ buf[1] |= BME680_SPI_MEM_PAGE_BIT;
+ else
+ buf[1] &= ~BME680_SPI_MEM_PAGE_BIT;
+
+ ret = spi_write(spi, buf, 2);
+ if (ret < 0) {
+ dev_err(&spi->dev, "failed to set page %u\n", page);
+ return ret;
+ }
+
+ ctx->current_page = page;
+
+ return 0;
+}
+
static int bme680_regmap_spi_write(void *context, const void *data,
size_t count)
{
- struct spi_device *spi = context;
+ struct bme680_spi_bus_context *ctx = context;
+ struct spi_device *spi = ctx->spi;
+ int ret;
u8 buf[2];
memcpy(buf, data, 2);
+
+ ret = bme680_regmap_spi_select_page(ctx, buf[0]);
+ if (ret)
+ return ret;
+
/*
* The SPI register address (= full register address without bit 7)
* and the write command (bit7 = RW = '0')
*/
buf[0] &= ~0x80;
- return spi_write_then_read(spi, buf, 2, NULL, 0);
+ return spi_write(spi, buf, 2);
}
static int bme680_regmap_spi_read(void *context, const void *reg,
size_t reg_size, void *val, size_t val_size)
{
- struct spi_device *spi = context;
+ struct bme680_spi_bus_context *ctx = context;
+ struct spi_device *spi = ctx->spi;
+ int ret;
+ u8 addr = *(const u8 *)reg;
+
+ ret = bme680_regmap_spi_select_page(ctx, addr);
+ if (ret)
+ return ret;
- return spi_write_then_read(spi, reg, reg_size, val, val_size);
+ addr |= 0x80; /* bit7 = RW = '1' */
+
+ return spi_write_then_read(spi, &addr, 1, val, val_size);
}
static struct regmap_bus bme680_regmap_bus = {
@@ -45,8 +111,8 @@ static struct regmap_bus bme680_regmap_bus = {
static int bme680_spi_probe(struct spi_device *spi)
{
const struct spi_device_id *id = spi_get_device_id(spi);
+ struct bme680_spi_bus_context *bus_context;
struct regmap *regmap;
- unsigned int val;
int ret;
spi->bits_per_word = 8;
@@ -56,45 +122,21 @@ static int bme680_spi_probe(struct spi_device *spi)
return ret;
}
+ bus_context = devm_kzalloc(&spi->dev, sizeof(*bus_context), GFP_KERNEL);
+ if (!bus_context)
+ return -ENOMEM;
+
+ bus_context->spi = spi;
+ bus_context->current_page = 0xff; /* Undefined on warm boot */
+
regmap = devm_regmap_init(&spi->dev, &bme680_regmap_bus,
- &spi->dev, &bme680_regmap_config);
+ bus_context, &bme680_regmap_config);
if (IS_ERR(regmap)) {
dev_err(&spi->dev, "Failed to register spi regmap %d\n",
(int)PTR_ERR(regmap));
return PTR_ERR(regmap);
}
- ret = regmap_write(regmap, BME680_REG_SOFT_RESET_SPI,
- BME680_CMD_SOFTRESET);
- if (ret < 0) {
- dev_err(&spi->dev, "Failed to reset chip\n");
- return ret;
- }
-
- /* after power-on reset, Page 0(0x80-0xFF) of spi_mem_page is active */
- ret = regmap_read(regmap, BME680_REG_CHIP_SPI_ID, &val);
- if (ret < 0) {
- dev_err(&spi->dev, "Error reading SPI chip ID\n");
- return ret;
- }
-
- if (val != BME680_CHIP_ID_VAL) {
- dev_err(&spi->dev, "Wrong chip ID, got %x expected %x\n",
- val, BME680_CHIP_ID_VAL);
- return -ENODEV;
- }
- /*
- * select Page 1 of spi_mem_page to enable access to
- * to registers from address 0x00 to 0x7F.
- */
- ret = regmap_write_bits(regmap, BME680_REG_STATUS,
- BME680_SPI_MEM_PAGE_BIT,
- BME680_SPI_MEM_PAGE_1_VAL);
- if (ret < 0) {
- dev_err(&spi->dev, "failed to set page 1 of spi_mem_page\n");
- return ret;
- }
-
return bme680_core_probe(&spi->dev, regmap, id->name);
}
@@ -110,10 +152,17 @@ static const struct acpi_device_id bme680_acpi_match[] = {
};
MODULE_DEVICE_TABLE(acpi, bme680_acpi_match);
+static const struct of_device_id bme680_of_spi_match[] = {
+ { .compatible = "bosch,bme680", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bme680_of_spi_match);
+
static struct spi_driver bme680_spi_driver = {
.driver = {
.name = "bme680_spi",
.acpi_match_table = ACPI_PTR(bme680_acpi_match),
+ .of_match_table = bme680_of_spi_match,
},
.probe = bme680_spi_probe,
.id_table = bme680_spi_id,
diff --git a/drivers/iio/chemical/pms7003.c b/drivers/iio/chemical/pms7003.c
new file mode 100644
index 000000000000..db8e7b2327b3
--- /dev/null
+++ b/drivers/iio/chemical/pms7003.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Plantower PMS7003 particulate matter sensor driver
+ *
+ * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/serdev.h>
+
+#define PMS7003_DRIVER_NAME "pms7003"
+
+#define PMS7003_MAGIC 0x424d
+/* last 2 data bytes hold frame checksum */
+#define PMS7003_MAX_DATA_LENGTH 28
+#define PMS7003_CHECKSUM_LENGTH 2
+#define PMS7003_PM10_OFFSET 10
+#define PMS7003_PM2P5_OFFSET 8
+#define PMS7003_PM1_OFFSET 6
+
+#define PMS7003_TIMEOUT msecs_to_jiffies(6000)
+#define PMS7003_CMD_LENGTH 7
+#define PMS7003_PM_MAX 1000
+#define PMS7003_PM_MIN 0
+
+enum {
+ PM1,
+ PM2P5,
+ PM10,
+};
+
+enum pms7003_cmd {
+ CMD_WAKEUP,
+ CMD_ENTER_PASSIVE_MODE,
+ CMD_READ_PASSIVE,
+ CMD_SLEEP,
+};
+
+/*
+ * commands have following format:
+ *
+ * +------+------+-----+------+-----+-----------+-----------+
+ * | 0x42 | 0x4d | cmd | 0x00 | arg | cksum msb | cksum lsb |
+ * +------+------+-----+------+-----+-----------+-----------+
+ */
+static const u8 pms7003_cmd_tbl[][PMS7003_CMD_LENGTH] = {
+ [CMD_WAKEUP] = { 0x42, 0x4d, 0xe4, 0x00, 0x01, 0x01, 0x74 },
+ [CMD_ENTER_PASSIVE_MODE] = { 0x42, 0x4d, 0xe1, 0x00, 0x00, 0x01, 0x70 },
+ [CMD_READ_PASSIVE] = { 0x42, 0x4d, 0xe2, 0x00, 0x00, 0x01, 0x71 },
+ [CMD_SLEEP] = { 0x42, 0x4d, 0xe4, 0x00, 0x00, 0x01, 0x73 },
+};
+
+struct pms7003_frame {
+ u8 data[PMS7003_MAX_DATA_LENGTH];
+ u16 expected_length;
+ u16 length;
+};
+
+struct pms7003_state {
+ struct serdev_device *serdev;
+ struct pms7003_frame frame;
+ struct completion frame_ready;
+ struct mutex lock; /* must be held whenever state gets touched */
+};
+
+static int pms7003_do_cmd(struct pms7003_state *state, enum pms7003_cmd cmd)
+{
+ int ret;
+
+ ret = serdev_device_write(state->serdev, pms7003_cmd_tbl[cmd],
+ PMS7003_CMD_LENGTH, PMS7003_TIMEOUT);
+ if (ret < PMS7003_CMD_LENGTH)
+ return ret < 0 ? ret : -EIO;
+
+ ret = wait_for_completion_interruptible_timeout(&state->frame_ready,
+ PMS7003_TIMEOUT);
+ if (!ret)
+ ret = -ETIMEDOUT;
+
+ return ret < 0 ? ret : 0;
+}
+
+static u16 pms7003_get_pm(const u8 *data)
+{
+ return clamp_val(get_unaligned_be16(data),
+ PMS7003_PM_MIN, PMS7003_PM_MAX);
+}
+
+static irqreturn_t pms7003_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct pms7003_state *state = iio_priv(indio_dev);
+ struct pms7003_frame *frame = &state->frame;
+ u16 data[3 + 1 + 4]; /* PM1, PM2P5, PM10, padding, timestamp */
+ int ret;
+
+ mutex_lock(&state->lock);
+ ret = pms7003_do_cmd(state, CMD_READ_PASSIVE);
+ if (ret) {
+ mutex_unlock(&state->lock);
+ goto err;
+ }
+
+ data[PM1] = pms7003_get_pm(frame->data + PMS7003_PM1_OFFSET);
+ data[PM2P5] = pms7003_get_pm(frame->data + PMS7003_PM2P5_OFFSET);
+ data[PM10] = pms7003_get_pm(frame->data + PMS7003_PM10_OFFSET);
+ mutex_unlock(&state->lock);
+
+ iio_push_to_buffers_with_timestamp(indio_dev, data,
+ iio_get_time_ns(indio_dev));
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int pms7003_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct pms7003_state *state = iio_priv(indio_dev);
+ struct pms7003_frame *frame = &state->frame;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (chan->type) {
+ case IIO_MASSCONCENTRATION:
+ mutex_lock(&state->lock);
+ ret = pms7003_do_cmd(state, CMD_READ_PASSIVE);
+ if (ret) {
+ mutex_unlock(&state->lock);
+ return ret;
+ }
+
+ *val = pms7003_get_pm(frame->data + chan->address);
+ mutex_unlock(&state->lock);
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info pms7003_info = {
+ .read_raw = pms7003_read_raw,
+};
+
+#define PMS7003_CHAN(_index, _mod, _addr) { \
+ .type = IIO_MASSCONCENTRATION, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_ ## _mod, \
+ .address = _addr, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
+ .scan_index = _index, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 10, \
+ .storagebits = 16, \
+ .endianness = IIO_CPU, \
+ }, \
+}
+
+static const struct iio_chan_spec pms7003_channels[] = {
+ PMS7003_CHAN(0, PM1, PMS7003_PM1_OFFSET),
+ PMS7003_CHAN(1, PM2P5, PMS7003_PM2P5_OFFSET),
+ PMS7003_CHAN(2, PM10, PMS7003_PM10_OFFSET),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static u16 pms7003_calc_checksum(struct pms7003_frame *frame)
+{
+ u16 checksum = (PMS7003_MAGIC >> 8) + (u8)(PMS7003_MAGIC & 0xff) +
+ (frame->length >> 8) + (u8)frame->length;
+ int i;
+
+ for (i = 0; i < frame->length - PMS7003_CHECKSUM_LENGTH; i++)
+ checksum += frame->data[i];
+
+ return checksum;
+}
+
+static bool pms7003_frame_is_okay(struct pms7003_frame *frame)
+{
+ int offset = frame->length - PMS7003_CHECKSUM_LENGTH;
+ u16 checksum = get_unaligned_be16(frame->data + offset);
+
+ return checksum == pms7003_calc_checksum(frame);
+}
+
+static int pms7003_receive_buf(struct serdev_device *serdev,
+ const unsigned char *buf, size_t size)
+{
+ struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);
+ struct pms7003_state *state = iio_priv(indio_dev);
+ struct pms7003_frame *frame = &state->frame;
+ int num;
+
+ if (!frame->expected_length) {
+ u16 magic;
+
+ /* wait for SOF and data length */
+ if (size < 4)
+ return 0;
+
+ magic = get_unaligned_be16(buf);
+ if (magic != PMS7003_MAGIC)
+ return 2;
+
+ num = get_unaligned_be16(buf + 2);
+ if (num <= PMS7003_MAX_DATA_LENGTH) {
+ frame->expected_length = num;
+ frame->length = 0;
+ }
+
+ return 4;
+ }
+
+ num = min(size, (size_t)(frame->expected_length - frame->length));
+ memcpy(frame->data + frame->length, buf, num);
+ frame->length += num;
+
+ if (frame->length == frame->expected_length) {
+ if (pms7003_frame_is_okay(frame))
+ complete(&state->frame_ready);
+
+ frame->expected_length = 0;
+ }
+
+ return num;
+}
+
+static const struct serdev_device_ops pms7003_serdev_ops = {
+ .receive_buf = pms7003_receive_buf,
+ .write_wakeup = serdev_device_write_wakeup,
+};
+
+static void pms7003_stop(void *data)
+{
+ struct pms7003_state *state = data;
+
+ pms7003_do_cmd(state, CMD_SLEEP);
+}
+
+static const unsigned long pms7003_scan_masks[] = { 0x07, 0x00 };
+
+static int pms7003_probe(struct serdev_device *serdev)
+{
+ struct pms7003_state *state;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&serdev->dev, sizeof(*state));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ state = iio_priv(indio_dev);
+ serdev_device_set_drvdata(serdev, indio_dev);
+ state->serdev = serdev;
+ indio_dev->dev.parent = &serdev->dev;
+ indio_dev->info = &pms7003_info;
+ indio_dev->name = PMS7003_DRIVER_NAME;
+ indio_dev->channels = pms7003_channels,
+ indio_dev->num_channels = ARRAY_SIZE(pms7003_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->available_scan_masks = pms7003_scan_masks;
+
+ mutex_init(&state->lock);
+ init_completion(&state->frame_ready);
+
+ serdev_device_set_client_ops(serdev, &pms7003_serdev_ops);
+ ret = devm_serdev_device_open(&serdev->dev, serdev);
+ if (ret)
+ return ret;
+
+ serdev_device_set_baudrate(serdev, 9600);
+ serdev_device_set_flow_control(serdev, false);
+
+ ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
+ if (ret)
+ return ret;
+
+ ret = pms7003_do_cmd(state, CMD_WAKEUP);
+ if (ret) {
+ dev_err(&serdev->dev, "failed to wakeup sensor\n");
+ return ret;
+ }
+
+ ret = pms7003_do_cmd(state, CMD_ENTER_PASSIVE_MODE);
+ if (ret) {
+ dev_err(&serdev->dev, "failed to enter passive mode\n");
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(&serdev->dev, pms7003_stop, state);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_triggered_buffer_setup(&serdev->dev, indio_dev, NULL,
+ pms7003_trigger_handler, NULL);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(&serdev->dev, indio_dev);
+}
+
+static const struct of_device_id pms7003_of_match[] = {
+ { .compatible = "plantower,pms7003" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pms7003_of_match);
+
+static struct serdev_device_driver pms7003_driver = {
+ .driver = {
+ .name = PMS7003_DRIVER_NAME,
+ .of_match_table = pms7003_of_match,
+ },
+ .probe = pms7003_probe,
+};
+module_serdev_device_driver(pms7003_driver);
+
+MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>");
+MODULE_DESCRIPTION("Plantower PMS7003 particulate matter sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/chemical/sgp30.c b/drivers/iio/chemical/sgp30.c
new file mode 100644
index 000000000000..8cc8fe5e356d
--- /dev/null
+++ b/drivers/iio/chemical/sgp30.c
@@ -0,0 +1,591 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * sgp30.c - Support for Sensirion SGP Gas Sensors
+ *
+ * Copyright (C) 2018 Andreas Brauchli <andreas.brauchli@sensirion.com>
+ *
+ * I2C slave address: 0x58
+ *
+ * Datasheets:
+ * https://www.sensirion.com/file/datasheet_sgp30
+ * https://www.sensirion.com/file/datasheet_sgpc3
+ *
+ * TODO:
+ * - baseline support
+ * - humidity compensation
+ * - power mode switching (SGPC3)
+ */
+
+#include <linux/crc8.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define SGP_WORD_LEN 2
+#define SGP_CRC8_POLYNOMIAL 0x31
+#define SGP_CRC8_INIT 0xff
+#define SGP_CRC8_LEN 1
+#define SGP_CMD(cmd_word) cpu_to_be16(cmd_word)
+#define SGP_CMD_DURATION_US 12000
+#define SGP_MEASUREMENT_DURATION_US 50000
+#define SGP_CMD_LEN SGP_WORD_LEN
+#define SGP_CMD_MAX_BUF_SIZE (SGP_CMD_LEN + 2 * SGP_WORD_LEN)
+#define SGP_MEASUREMENT_LEN 2
+#define SGP30_MEASURE_INTERVAL_HZ 1
+#define SGPC3_MEASURE_INTERVAL_HZ 2
+#define SGP_VERS_PRODUCT(data) ((((data)->feature_set) & 0xf000) >> 12)
+#define SGP_VERS_RESERVED(data) ((((data)->feature_set) & 0x0800) >> 11)
+#define SGP_VERS_GEN(data) ((((data)->feature_set) & 0x0600) >> 9)
+#define SGP_VERS_ENG_BIT(data) ((((data)->feature_set) & 0x0100) >> 8)
+#define SGP_VERS_MAJOR(data) ((((data)->feature_set) & 0x00e0) >> 5)
+#define SGP_VERS_MINOR(data) (((data)->feature_set) & 0x001f)
+
+DECLARE_CRC8_TABLE(sgp_crc8_table);
+
+enum sgp_product_id {
+ SGP30 = 0,
+ SGPC3,
+};
+
+enum sgp30_channel_idx {
+ SGP30_IAQ_TVOC_IDX = 0,
+ SGP30_IAQ_CO2EQ_IDX,
+ SGP30_SIG_ETOH_IDX,
+ SGP30_SIG_H2_IDX,
+};
+
+enum sgpc3_channel_idx {
+ SGPC3_IAQ_TVOC_IDX = 10,
+ SGPC3_SIG_ETOH_IDX,
+};
+
+enum sgp_cmd {
+ SGP_CMD_IAQ_INIT = SGP_CMD(0x2003),
+ SGP_CMD_IAQ_MEASURE = SGP_CMD(0x2008),
+ SGP_CMD_GET_FEATURE_SET = SGP_CMD(0x202f),
+ SGP_CMD_GET_SERIAL_ID = SGP_CMD(0x3682),
+
+ SGP30_CMD_MEASURE_SIGNAL = SGP_CMD(0x2050),
+
+ SGPC3_CMD_MEASURE_RAW = SGP_CMD(0x2046),
+};
+
+struct sgp_version {
+ u8 major;
+ u8 minor;
+};
+
+struct sgp_crc_word {
+ __be16 value;
+ u8 crc8;
+} __attribute__((__packed__));
+
+union sgp_reading {
+ u8 start;
+ struct sgp_crc_word raw_words[4];
+};
+
+enum _iaq_buffer_state {
+ IAQ_BUFFER_EMPTY = 0,
+ IAQ_BUFFER_DEFAULT_VALS,
+ IAQ_BUFFER_VALID,
+};
+
+struct sgp_data {
+ struct i2c_client *client;
+ struct task_struct *iaq_thread;
+ struct mutex data_lock;
+ unsigned long iaq_init_start_jiffies;
+ unsigned long iaq_defval_skip_jiffies;
+ u16 product_id;
+ u16 feature_set;
+ unsigned long measure_interval_jiffies;
+ enum sgp_cmd iaq_init_cmd;
+ enum sgp_cmd measure_iaq_cmd;
+ enum sgp_cmd measure_gas_signals_cmd;
+ union sgp_reading buffer;
+ union sgp_reading iaq_buffer;
+ enum _iaq_buffer_state iaq_buffer_state;
+};
+
+struct sgp_device {
+ const struct iio_chan_spec *channels;
+ int num_channels;
+};
+
+static const struct sgp_version supported_versions_sgp30[] = {
+ {
+ .major = 1,
+ .minor = 0,
+ },
+};
+
+static const struct sgp_version supported_versions_sgpc3[] = {
+ {
+ .major = 0,
+ .minor = 4,
+ },
+};
+
+static const struct iio_chan_spec sgp30_channels[] = {
+ {
+ .type = IIO_CONCENTRATION,
+ .channel2 = IIO_MOD_VOC,
+ .modified = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .address = SGP30_IAQ_TVOC_IDX,
+ },
+ {
+ .type = IIO_CONCENTRATION,
+ .channel2 = IIO_MOD_CO2,
+ .modified = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .address = SGP30_IAQ_CO2EQ_IDX,
+ },
+ {
+ .type = IIO_CONCENTRATION,
+ .channel2 = IIO_MOD_ETHANOL,
+ .modified = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .address = SGP30_SIG_ETOH_IDX,
+ },
+ {
+ .type = IIO_CONCENTRATION,
+ .channel2 = IIO_MOD_H2,
+ .modified = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .address = SGP30_SIG_H2_IDX,
+ },
+};
+
+static const struct iio_chan_spec sgpc3_channels[] = {
+ {
+ .type = IIO_CONCENTRATION,
+ .channel2 = IIO_MOD_VOC,
+ .modified = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .address = SGPC3_IAQ_TVOC_IDX,
+ },
+ {
+ .type = IIO_CONCENTRATION,
+ .channel2 = IIO_MOD_ETHANOL,
+ .modified = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .address = SGPC3_SIG_ETOH_IDX,
+ },
+};
+
+static const struct sgp_device sgp_devices[] = {
+ [SGP30] = {
+ .channels = sgp30_channels,
+ .num_channels = ARRAY_SIZE(sgp30_channels),
+ },
+ [SGPC3] = {
+ .channels = sgpc3_channels,
+ .num_channels = ARRAY_SIZE(sgpc3_channels),
+ },
+};
+
+/**
+ * sgp_verify_buffer() - verify the checksums of the data buffer words
+ *
+ * @data: SGP data
+ * @buf: Raw data buffer
+ * @word_count: Num data words stored in the buffer, excluding CRC bytes
+ *
+ * Return: 0 on success, negative error otherwise.
+ */
+static int sgp_verify_buffer(const struct sgp_data *data,
+ union sgp_reading *buf, size_t word_count)
+{
+ size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN);
+ int i;
+ u8 crc;
+ u8 *data_buf = &buf->start;
+
+ for (i = 0; i < size; i += SGP_WORD_LEN + SGP_CRC8_LEN) {
+ crc = crc8(sgp_crc8_table, &data_buf[i], SGP_WORD_LEN,
+ SGP_CRC8_INIT);
+ if (crc != data_buf[i + SGP_WORD_LEN]) {
+ dev_err(&data->client->dev, "CRC error\n");
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * sgp_read_cmd() - reads data from sensor after issuing a command
+ * The caller must hold data->data_lock for the duration of the call.
+ * @data: SGP data
+ * @cmd: SGP Command to issue
+ * @buf: Raw data buffer to use
+ * @word_count: Num words to read, excluding CRC bytes
+ *
+ * Return: 0 on success, negative error otherwise.
+ */
+static int sgp_read_cmd(struct sgp_data *data, enum sgp_cmd cmd,
+ union sgp_reading *buf, size_t word_count,
+ unsigned long duration_us)
+{
+ int ret;
+ struct i2c_client *client = data->client;
+ size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN);
+ u8 *data_buf;
+
+ ret = i2c_master_send(client, (const char *)&cmd, SGP_CMD_LEN);
+ if (ret != SGP_CMD_LEN)
+ return -EIO;
+ usleep_range(duration_us, duration_us + 1000);
+
+ if (word_count == 0)
+ return 0;
+
+ data_buf = &buf->start;
+ ret = i2c_master_recv(client, data_buf, size);
+ if (ret < 0)
+ return ret;
+ if (ret != size)
+ return -EIO;
+
+ return sgp_verify_buffer(data, buf, word_count);
+}
+
+/**
+ * sgp_measure_iaq() - measure and retrieve IAQ values from sensor
+ * The caller must hold data->data_lock for the duration of the call.
+ * @data: SGP data
+ *
+ * Return: 0 on success, -EBUSY on default values, negative error
+ * otherwise.
+ */
+
+static int sgp_measure_iaq(struct sgp_data *data)
+{
+ int ret;
+ /* data contains default values */
+ bool default_vals = !time_after(jiffies, data->iaq_init_start_jiffies +
+ data->iaq_defval_skip_jiffies);
+
+ ret = sgp_read_cmd(data, data->measure_iaq_cmd, &data->iaq_buffer,
+ SGP_MEASUREMENT_LEN, SGP_MEASUREMENT_DURATION_US);
+ if (ret < 0)
+ return ret;
+
+ data->iaq_buffer_state = IAQ_BUFFER_DEFAULT_VALS;
+
+ if (default_vals)
+ return -EBUSY;
+
+ data->iaq_buffer_state = IAQ_BUFFER_VALID;
+
+ return 0;
+}
+
+static void sgp_iaq_thread_sleep_until(const struct sgp_data *data,
+ unsigned long sleep_jiffies)
+{
+ const long IAQ_POLL = 50000;
+
+ while (!time_after(jiffies, sleep_jiffies)) {
+ usleep_range(IAQ_POLL, IAQ_POLL + 10000);
+ if (kthread_should_stop() || data->iaq_init_start_jiffies == 0)
+ return;
+ }
+}
+
+static int sgp_iaq_threadfn(void *p)
+{
+ struct sgp_data *data = (struct sgp_data *)p;
+ unsigned long next_update_jiffies;
+ int ret;
+
+ while (!kthread_should_stop()) {
+ mutex_lock(&data->data_lock);
+ if (data->iaq_init_start_jiffies == 0) {
+ ret = sgp_read_cmd(data, data->iaq_init_cmd, NULL, 0,
+ SGP_CMD_DURATION_US);
+ if (ret < 0)
+ goto unlock_sleep_continue;
+ data->iaq_init_start_jiffies = jiffies;
+ }
+
+ ret = sgp_measure_iaq(data);
+ if (ret && ret != -EBUSY) {
+ dev_warn(&data->client->dev,
+ "IAQ measurement error [%d]\n", ret);
+ }
+unlock_sleep_continue:
+ next_update_jiffies = jiffies + data->measure_interval_jiffies;
+ mutex_unlock(&data->data_lock);
+ sgp_iaq_thread_sleep_until(data, next_update_jiffies);
+ }
+
+ return 0;
+}
+
+static int sgp_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct sgp_data *data = iio_priv(indio_dev);
+ struct sgp_crc_word *words;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ mutex_lock(&data->data_lock);
+ if (data->iaq_buffer_state != IAQ_BUFFER_VALID) {
+ mutex_unlock(&data->data_lock);
+ return -EBUSY;
+ }
+ words = data->iaq_buffer.raw_words;
+ switch (chan->address) {
+ case SGP30_IAQ_TVOC_IDX:
+ case SGPC3_IAQ_TVOC_IDX:
+ *val = 0;
+ *val2 = be16_to_cpu(words[1].value);
+ ret = IIO_VAL_INT_PLUS_NANO;
+ break;
+ case SGP30_IAQ_CO2EQ_IDX:
+ *val = 0;
+ *val2 = be16_to_cpu(words[0].value);
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ mutex_unlock(&data->data_lock);
+ break;
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&data->data_lock);
+ if (chan->address == SGPC3_SIG_ETOH_IDX) {
+ if (data->iaq_buffer_state == IAQ_BUFFER_EMPTY)
+ ret = -EBUSY;
+ else
+ ret = 0;
+ words = data->iaq_buffer.raw_words;
+ } else {
+ ret = sgp_read_cmd(data, data->measure_gas_signals_cmd,
+ &data->buffer, SGP_MEASUREMENT_LEN,
+ SGP_MEASUREMENT_DURATION_US);
+ words = data->buffer.raw_words;
+ }
+ if (ret) {
+ mutex_unlock(&data->data_lock);
+ return ret;
+ }
+
+ switch (chan->address) {
+ case SGP30_SIG_ETOH_IDX:
+ *val = be16_to_cpu(words[1].value);
+ ret = IIO_VAL_INT;
+ break;
+ case SGPC3_SIG_ETOH_IDX:
+ case SGP30_SIG_H2_IDX:
+ *val = be16_to_cpu(words[0].value);
+ ret = IIO_VAL_INT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ mutex_unlock(&data->data_lock);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int sgp_check_compat(struct sgp_data *data,
+ unsigned int product_id)
+{
+ const struct sgp_version *supported_versions;
+ u16 ix, num_fs;
+ u16 product, generation, major, minor;
+
+ /* driver does not match product */
+ generation = SGP_VERS_GEN(data);
+ if (generation != 0) {
+ dev_err(&data->client->dev,
+ "incompatible product generation %d != 0", generation);
+ return -ENODEV;
+ }
+
+ product = SGP_VERS_PRODUCT(data);
+ if (product != product_id) {
+ dev_err(&data->client->dev,
+ "sensor reports a different product: 0x%04hx\n",
+ product);
+ return -ENODEV;
+ }
+
+ if (SGP_VERS_RESERVED(data))
+ dev_warn(&data->client->dev, "reserved bit is set\n");
+
+ /* engineering samples are not supported: no interface guarantees */
+ if (SGP_VERS_ENG_BIT(data))
+ return -ENODEV;
+
+ switch (product) {
+ case SGP30:
+ supported_versions = supported_versions_sgp30;
+ num_fs = ARRAY_SIZE(supported_versions_sgp30);
+ break;
+ case SGPC3:
+ supported_versions = supported_versions_sgpc3;
+ num_fs = ARRAY_SIZE(supported_versions_sgpc3);
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ major = SGP_VERS_MAJOR(data);
+ minor = SGP_VERS_MINOR(data);
+ for (ix = 0; ix < num_fs; ix++) {
+ if (major == supported_versions[ix].major &&
+ minor >= supported_versions[ix].minor)
+ return 0;
+ }
+ dev_err(&data->client->dev, "unsupported sgp version: %d.%d\n",
+ major, minor);
+
+ return -ENODEV;
+}
+
+static void sgp_init(struct sgp_data *data)
+{
+ data->iaq_init_cmd = SGP_CMD_IAQ_INIT;
+ data->iaq_init_start_jiffies = 0;
+ data->iaq_buffer_state = IAQ_BUFFER_EMPTY;
+ switch (SGP_VERS_PRODUCT(data)) {
+ case SGP30:
+ data->measure_interval_jiffies = SGP30_MEASURE_INTERVAL_HZ * HZ;
+ data->measure_iaq_cmd = SGP_CMD_IAQ_MEASURE;
+ data->measure_gas_signals_cmd = SGP30_CMD_MEASURE_SIGNAL;
+ data->product_id = SGP30;
+ data->iaq_defval_skip_jiffies = 15 * HZ;
+ break;
+ case SGPC3:
+ data->measure_interval_jiffies = SGPC3_MEASURE_INTERVAL_HZ * HZ;
+ data->measure_iaq_cmd = SGPC3_CMD_MEASURE_RAW;
+ data->measure_gas_signals_cmd = SGPC3_CMD_MEASURE_RAW;
+ data->product_id = SGPC3;
+ data->iaq_defval_skip_jiffies =
+ 43 * data->measure_interval_jiffies;
+ break;
+ };
+}
+
+static const struct iio_info sgp_info = {
+ .read_raw = sgp_read_raw,
+};
+
+static const struct of_device_id sgp_dt_ids[] = {
+ { .compatible = "sensirion,sgp30", .data = (void *)SGP30 },
+ { .compatible = "sensirion,sgpc3", .data = (void *)SGPC3 },
+ { }
+};
+
+static int sgp_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct iio_dev *indio_dev;
+ struct sgp_data *data;
+ const struct of_device_id *of_id;
+ unsigned long product_id;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ of_id = of_match_device(sgp_dt_ids, &client->dev);
+ if (of_id)
+ product_id = (unsigned long)of_id->data;
+ else
+ product_id = id->driver_data;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ crc8_populate_msb(sgp_crc8_table, SGP_CRC8_POLYNOMIAL);
+ mutex_init(&data->data_lock);
+
+ /* get feature set version and write it to client data */
+ ret = sgp_read_cmd(data, SGP_CMD_GET_FEATURE_SET, &data->buffer, 1,
+ SGP_CMD_DURATION_US);
+ if (ret < 0)
+ return ret;
+
+ data->feature_set = be16_to_cpu(data->buffer.raw_words[0].value);
+
+ ret = sgp_check_compat(data, product_id);
+ if (ret)
+ return ret;
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &sgp_info;
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = sgp_devices[product_id].channels;
+ indio_dev->num_channels = sgp_devices[product_id].num_channels;
+
+ sgp_init(data);
+
+ ret = devm_iio_device_register(&client->dev, indio_dev);
+ if (ret) {
+ dev_err(&client->dev, "failed to register iio device\n");
+ return ret;
+ }
+
+ data->iaq_thread = kthread_run(sgp_iaq_threadfn, data,
+ "%s-iaq", data->client->name);
+
+ return 0;
+}
+
+static int sgp_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct sgp_data *data = iio_priv(indio_dev);
+
+ if (data->iaq_thread)
+ kthread_stop(data->iaq_thread);
+
+ return 0;
+}
+
+static const struct i2c_device_id sgp_id[] = {
+ { "sgp30", SGP30 },
+ { "sgpc3", SGPC3 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, sgp_id);
+MODULE_DEVICE_TABLE(of, sgp_dt_ids);
+
+static struct i2c_driver sgp_driver = {
+ .driver = {
+ .name = "sgp30",
+ .of_match_table = of_match_ptr(sgp_dt_ids),
+ },
+ .probe = sgp_probe,
+ .remove = sgp_remove,
+ .id_table = sgp_id,
+};
+module_i2c_driver(sgp_driver);
+
+MODULE_AUTHOR("Andreas Brauchli <andreas.brauchli@sensirion.com>");
+MODULE_AUTHOR("Pascal Sachs <pascal.sachs@sensirion.com>");
+MODULE_DESCRIPTION("Sensirion SGP gas sensors");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/chemical/sps30.c b/drivers/iio/chemical/sps30.c
new file mode 100644
index 000000000000..edbb956e81e8
--- /dev/null
+++ b/drivers/iio/chemical/sps30.c
@@ -0,0 +1,548 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sensirion SPS30 particulate matter sensor driver
+ *
+ * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com>
+ *
+ * I2C slave address: 0x69
+ */
+
+#include <asm/unaligned.h>
+#include <linux/crc8.h>
+#include <linux/delay.h>
+#include <linux/i2c.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>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#define SPS30_CRC8_POLYNOMIAL 0x31
+/* max number of bytes needed to store PM measurements or serial string */
+#define SPS30_MAX_READ_SIZE 48
+/* sensor measures reliably up to 3000 ug / m3 */
+#define SPS30_MAX_PM 3000
+/* minimum and maximum self cleaning periods in seconds */
+#define SPS30_AUTO_CLEANING_PERIOD_MIN 0
+#define SPS30_AUTO_CLEANING_PERIOD_MAX 604800
+
+/* SPS30 commands */
+#define SPS30_START_MEAS 0x0010
+#define SPS30_STOP_MEAS 0x0104
+#define SPS30_RESET 0xd304
+#define SPS30_READ_DATA_READY_FLAG 0x0202
+#define SPS30_READ_DATA 0x0300
+#define SPS30_READ_SERIAL 0xd033
+#define SPS30_START_FAN_CLEANING 0x5607
+#define SPS30_AUTO_CLEANING_PERIOD 0x8004
+/* not a sensor command per se, used only to distinguish write from read */
+#define SPS30_READ_AUTO_CLEANING_PERIOD 0x8005
+
+enum {
+ PM1,
+ PM2P5,
+ PM4,
+ PM10,
+};
+
+enum {
+ RESET,
+ MEASURING,
+};
+
+struct sps30_state {
+ struct i2c_client *client;
+ /*
+ * Guards against concurrent access to sensor registers.
+ * Must be held whenever sequence of commands is to be executed.
+ */
+ struct mutex lock;
+ int state;
+};
+
+DECLARE_CRC8_TABLE(sps30_crc8_table);
+
+static int sps30_write_then_read(struct sps30_state *state, u8 *txbuf,
+ int txsize, u8 *rxbuf, int rxsize)
+{
+ int ret;
+
+ /*
+ * Sensor does not support repeated start so instead of
+ * sending two i2c messages in a row we just send one by one.
+ */
+ ret = i2c_master_send(state->client, txbuf, txsize);
+ if (ret != txsize)
+ return ret < 0 ? ret : -EIO;
+
+ if (!rxbuf)
+ return 0;
+
+ ret = i2c_master_recv(state->client, rxbuf, rxsize);
+ if (ret != rxsize)
+ return ret < 0 ? ret : -EIO;
+
+ return 0;
+}
+
+static int sps30_do_cmd(struct sps30_state *state, u16 cmd, u8 *data, int size)
+{
+ /*
+ * Internally sensor stores measurements in a following manner:
+ *
+ * PM1: upper two bytes, crc8, lower two bytes, crc8
+ * PM2P5: upper two bytes, crc8, lower two bytes, crc8
+ * PM4: upper two bytes, crc8, lower two bytes, crc8
+ * PM10: upper two bytes, crc8, lower two bytes, crc8
+ *
+ * What follows next are number concentration measurements and
+ * typical particle size measurement which we omit.
+ */
+ u8 buf[SPS30_MAX_READ_SIZE] = { cmd >> 8, cmd };
+ int i, ret = 0;
+
+ switch (cmd) {
+ case SPS30_START_MEAS:
+ buf[2] = 0x03;
+ buf[3] = 0x00;
+ buf[4] = crc8(sps30_crc8_table, &buf[2], 2, CRC8_INIT_VALUE);
+ ret = sps30_write_then_read(state, buf, 5, NULL, 0);
+ break;
+ case SPS30_STOP_MEAS:
+ case SPS30_RESET:
+ case SPS30_START_FAN_CLEANING:
+ ret = sps30_write_then_read(state, buf, 2, NULL, 0);
+ break;
+ case SPS30_READ_AUTO_CLEANING_PERIOD:
+ buf[0] = SPS30_AUTO_CLEANING_PERIOD >> 8;
+ buf[1] = (u8)SPS30_AUTO_CLEANING_PERIOD;
+ /* fall through */
+ case SPS30_READ_DATA_READY_FLAG:
+ case SPS30_READ_DATA:
+ case SPS30_READ_SERIAL:
+ /* every two data bytes are checksummed */
+ size += size / 2;
+ ret = sps30_write_then_read(state, buf, 2, buf, size);
+ break;
+ case SPS30_AUTO_CLEANING_PERIOD:
+ buf[2] = data[0];
+ buf[3] = data[1];
+ buf[4] = crc8(sps30_crc8_table, &buf[2], 2, CRC8_INIT_VALUE);
+ buf[5] = data[2];
+ buf[6] = data[3];
+ buf[7] = crc8(sps30_crc8_table, &buf[5], 2, CRC8_INIT_VALUE);
+ ret = sps30_write_then_read(state, buf, 8, NULL, 0);
+ break;
+ }
+
+ if (ret)
+ return ret;
+
+ /* validate received data and strip off crc bytes */
+ for (i = 0; i < size; i += 3) {
+ u8 crc = crc8(sps30_crc8_table, &buf[i], 2, CRC8_INIT_VALUE);
+
+ if (crc != buf[i + 2]) {
+ dev_err(&state->client->dev,
+ "data integrity check failed\n");
+ return -EIO;
+ }
+
+ *data++ = buf[i];
+ *data++ = buf[i + 1];
+ }
+
+ return 0;
+}
+
+static s32 sps30_float_to_int_clamped(const u8 *fp)
+{
+ int val = get_unaligned_be32(fp);
+ int mantissa = val & GENMASK(22, 0);
+ /* this is fine since passed float is always non-negative */
+ int exp = val >> 23;
+ int fraction, shift;
+
+ /* special case 0 */
+ if (!exp && !mantissa)
+ return 0;
+
+ exp -= 127;
+ if (exp < 0) {
+ /* return values ranging from 1 to 99 */
+ return ((((1 << 23) + mantissa) * 100) >> 23) >> (-exp);
+ }
+
+ /* return values ranging from 100 to 300000 */
+ shift = 23 - exp;
+ val = (1 << exp) + (mantissa >> shift);
+ if (val >= SPS30_MAX_PM)
+ return SPS30_MAX_PM * 100;
+
+ fraction = mantissa & GENMASK(shift - 1, 0);
+
+ return val * 100 + ((fraction * 100) >> shift);
+}
+
+static int sps30_do_meas(struct sps30_state *state, s32 *data, int size)
+{
+ int i, ret, tries = 5;
+ u8 tmp[16];
+
+ if (state->state == RESET) {
+ ret = sps30_do_cmd(state, SPS30_START_MEAS, NULL, 0);
+ if (ret)
+ return ret;
+
+ state->state = MEASURING;
+ }
+
+ while (tries--) {
+ ret = sps30_do_cmd(state, SPS30_READ_DATA_READY_FLAG, tmp, 2);
+ if (ret)
+ return -EIO;
+
+ /* new measurements ready to be read */
+ if (tmp[1] == 1)
+ break;
+
+ msleep_interruptible(300);
+ }
+
+ if (tries == -1)
+ return -ETIMEDOUT;
+
+ ret = sps30_do_cmd(state, SPS30_READ_DATA, tmp, sizeof(int) * size);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < size; i++)
+ data[i] = sps30_float_to_int_clamped(&tmp[4 * i]);
+
+ return 0;
+}
+
+static irqreturn_t sps30_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct sps30_state *state = iio_priv(indio_dev);
+ int ret;
+ s32 data[4 + 2]; /* PM1, PM2P5, PM4, PM10, timestamp */
+
+ mutex_lock(&state->lock);
+ ret = sps30_do_meas(state, data, 4);
+ mutex_unlock(&state->lock);
+ if (ret)
+ goto err;
+
+ iio_push_to_buffers_with_timestamp(indio_dev, data,
+ iio_get_time_ns(indio_dev));
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int sps30_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct sps30_state *state = iio_priv(indio_dev);
+ int data[4], ret = -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (chan->type) {
+ case IIO_MASSCONCENTRATION:
+ mutex_lock(&state->lock);
+ /* read up to the number of bytes actually needed */
+ switch (chan->channel2) {
+ case IIO_MOD_PM1:
+ ret = sps30_do_meas(state, data, 1);
+ break;
+ case IIO_MOD_PM2P5:
+ ret = sps30_do_meas(state, data, 2);
+ break;
+ case IIO_MOD_PM4:
+ ret = sps30_do_meas(state, data, 3);
+ break;
+ case IIO_MOD_PM10:
+ ret = sps30_do_meas(state, data, 4);
+ break;
+ }
+ mutex_unlock(&state->lock);
+ if (ret)
+ return ret;
+
+ *val = data[chan->address] / 100;
+ *val2 = (data[chan->address] % 100) * 10000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_MASSCONCENTRATION:
+ switch (chan->channel2) {
+ case IIO_MOD_PM1:
+ case IIO_MOD_PM2P5:
+ case IIO_MOD_PM4:
+ case IIO_MOD_PM10:
+ *val = 0;
+ *val2 = 10000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int sps30_do_cmd_reset(struct sps30_state *state)
+{
+ int ret;
+
+ ret = sps30_do_cmd(state, SPS30_RESET, NULL, 0);
+ msleep(300);
+ /*
+ * Power-on-reset causes sensor to produce some glitch on i2c bus and
+ * some controllers end up in error state. Recover simply by placing
+ * some data on the bus, for example STOP_MEAS command, which
+ * is NOP in this case.
+ */
+ sps30_do_cmd(state, SPS30_STOP_MEAS, NULL, 0);
+ state->state = RESET;
+
+ return ret;
+}
+
+static ssize_t start_cleaning_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 sps30_state *state = iio_priv(indio_dev);
+ int val, ret;
+
+ if (kstrtoint(buf, 0, &val) || val != 1)
+ return -EINVAL;
+
+ mutex_lock(&state->lock);
+ ret = sps30_do_cmd(state, SPS30_START_FAN_CLEANING, NULL, 0);
+ mutex_unlock(&state->lock);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t cleaning_period_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct sps30_state *state = iio_priv(indio_dev);
+ u8 tmp[4];
+ int ret;
+
+ mutex_lock(&state->lock);
+ ret = sps30_do_cmd(state, SPS30_READ_AUTO_CLEANING_PERIOD, tmp, 4);
+ mutex_unlock(&state->lock);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", get_unaligned_be32(tmp));
+}
+
+static ssize_t cleaning_period_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 sps30_state *state = iio_priv(indio_dev);
+ int val, ret;
+ u8 tmp[4];
+
+ if (kstrtoint(buf, 0, &val))
+ return -EINVAL;
+
+ if ((val < SPS30_AUTO_CLEANING_PERIOD_MIN) ||
+ (val > SPS30_AUTO_CLEANING_PERIOD_MAX))
+ return -EINVAL;
+
+ put_unaligned_be32(val, tmp);
+
+ mutex_lock(&state->lock);
+ ret = sps30_do_cmd(state, SPS30_AUTO_CLEANING_PERIOD, tmp, 0);
+ if (ret) {
+ mutex_unlock(&state->lock);
+ return ret;
+ }
+
+ msleep(20);
+
+ /*
+ * sensor requires reset in order to return up to date self cleaning
+ * period
+ */
+ ret = sps30_do_cmd_reset(state);
+ if (ret)
+ dev_warn(dev,
+ "period changed but reads will return the old value\n");
+
+ mutex_unlock(&state->lock);
+
+ return len;
+}
+
+static ssize_t cleaning_period_available_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "[%d %d %d]\n",
+ SPS30_AUTO_CLEANING_PERIOD_MIN, 1,
+ SPS30_AUTO_CLEANING_PERIOD_MAX);
+}
+
+static IIO_DEVICE_ATTR_WO(start_cleaning, 0);
+static IIO_DEVICE_ATTR_RW(cleaning_period, 0);
+static IIO_DEVICE_ATTR_RO(cleaning_period_available, 0);
+
+static struct attribute *sps30_attrs[] = {
+ &iio_dev_attr_start_cleaning.dev_attr.attr,
+ &iio_dev_attr_cleaning_period.dev_attr.attr,
+ &iio_dev_attr_cleaning_period_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group sps30_attr_group = {
+ .attrs = sps30_attrs,
+};
+
+static const struct iio_info sps30_info = {
+ .attrs = &sps30_attr_group,
+ .read_raw = sps30_read_raw,
+};
+
+#define SPS30_CHAN(_index, _mod) { \
+ .type = IIO_MASSCONCENTRATION, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_ ## _mod, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .address = _mod, \
+ .scan_index = _index, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 19, \
+ .storagebits = 32, \
+ .endianness = IIO_CPU, \
+ }, \
+}
+
+static const struct iio_chan_spec sps30_channels[] = {
+ SPS30_CHAN(0, PM1),
+ SPS30_CHAN(1, PM2P5),
+ SPS30_CHAN(2, PM4),
+ SPS30_CHAN(3, PM10),
+ IIO_CHAN_SOFT_TIMESTAMP(4),
+};
+
+static void sps30_stop_meas(void *data)
+{
+ struct sps30_state *state = data;
+
+ sps30_do_cmd(state, SPS30_STOP_MEAS, NULL, 0);
+}
+
+static const unsigned long sps30_scan_masks[] = { 0x0f, 0x00 };
+
+static int sps30_probe(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev;
+ struct sps30_state *state;
+ u8 buf[32];
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -EOPNOTSUPP;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*state));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ state = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ state->client = client;
+ state->state = RESET;
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &sps30_info;
+ indio_dev->name = client->name;
+ indio_dev->channels = sps30_channels;
+ indio_dev->num_channels = ARRAY_SIZE(sps30_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->available_scan_masks = sps30_scan_masks;
+
+ mutex_init(&state->lock);
+ crc8_populate_msb(sps30_crc8_table, SPS30_CRC8_POLYNOMIAL);
+
+ ret = sps30_do_cmd_reset(state);
+ if (ret) {
+ dev_err(&client->dev, "failed to reset device\n");
+ return ret;
+ }
+
+ ret = sps30_do_cmd(state, SPS30_READ_SERIAL, buf, sizeof(buf));
+ if (ret) {
+ dev_err(&client->dev, "failed to read serial number\n");
+ return ret;
+ }
+ /* returned serial number is already NUL terminated */
+ dev_info(&client->dev, "serial number: %s\n", buf);
+
+ ret = devm_add_action_or_reset(&client->dev, sps30_stop_meas, state);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL,
+ sps30_trigger_handler, NULL);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id sps30_id[] = {
+ { "sps30" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, sps30_id);
+
+static const struct of_device_id sps30_of_match[] = {
+ { .compatible = "sensirion,sps30" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sps30_of_match);
+
+static struct i2c_driver sps30_driver = {
+ .driver = {
+ .name = "sps30",
+ .of_match_table = sps30_of_match,
+ },
+ .id_table = sps30_id,
+ .probe_new = sps30_probe,
+};
+module_i2c_driver(sps30_driver);
+
+MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>");
+MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c
index 89cb0066a6e0..8d76afb87d87 100644
--- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c
+++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c
@@ -103,9 +103,10 @@ static int cros_ec_sensors_read(struct iio_dev *indio_dev,
* Do not use IIO_DEGREE_TO_RAD to avoid precision
* loss. Round to the nearest integer.
*/
- *val = div_s64(val64 * 314159 + 9000000ULL, 1000);
- *val2 = 18000 << (CROS_EC_SENSOR_BITS - 1);
- ret = IIO_VAL_FRACTIONAL;
+ *val = 0;
+ *val2 = div_s64(val64 * 3141592653ULL,
+ 180 << (CROS_EC_SENSOR_BITS - 1));
+ ret = IIO_VAL_INT_PLUS_NANO;
break;
case MOTIONSENSE_TYPE_MAG:
/*
diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
index ed3849d6fc6a..b2143d7b4ccb 100644
--- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
+++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
@@ -336,7 +336,7 @@ static void adjust_exponent_nano(int *val0, int *val1, int scale0,
scale1 = scale1 % pow_10(8 - i);
}
*val0 += res;
- *val1 = scale1 * pow_10(exp);
+ *val1 = scale1 * pow_10(exp);
} else if (exp < 0) {
exp = abs(exp);
if (exp > 9) {
diff --git a/drivers/iio/common/ssp_sensors/ssp_dev.c b/drivers/iio/common/ssp_sensors/ssp_dev.c
index af3aa38f67cd..9e13be2c0cb9 100644
--- a/drivers/iio/common/ssp_sensors/ssp_dev.c
+++ b/drivers/iio/common/ssp_sensors/ssp_dev.c
@@ -462,43 +462,35 @@ static struct ssp_data *ssp_parse_dt(struct device *dev)
data->mcu_ap_gpio = of_get_named_gpio(node, "mcu-ap-gpios", 0);
if (data->mcu_ap_gpio < 0)
- goto err_free_pd;
+ return NULL;
data->ap_mcu_gpio = of_get_named_gpio(node, "ap-mcu-gpios", 0);
if (data->ap_mcu_gpio < 0)
- goto err_free_pd;
+ return NULL;
data->mcu_reset_gpio = of_get_named_gpio(node, "mcu-reset-gpios", 0);
if (data->mcu_reset_gpio < 0)
- goto err_free_pd;
+ return NULL;
ret = devm_gpio_request_one(dev, data->ap_mcu_gpio, GPIOF_OUT_INIT_HIGH,
"ap-mcu-gpios");
if (ret)
- goto err_free_pd;
+ return NULL;
ret = devm_gpio_request_one(dev, data->mcu_reset_gpio,
GPIOF_OUT_INIT_HIGH, "mcu-reset-gpios");
if (ret)
- goto err_ap_mcu;
+ return NULL;
match = of_match_node(ssp_of_match, node);
if (!match)
- goto err_mcu_reset_gpio;
+ return NULL;
data->sensorhub_info = match->data;
dev_set_drvdata(dev, data);
return data;
-
-err_mcu_reset_gpio:
- devm_gpio_free(dev, data->mcu_reset_gpio);
-err_ap_mcu:
- devm_gpio_free(dev, data->ap_mcu_gpio);
-err_free_pd:
- devm_kfree(dev, data);
- return NULL;
}
#else
static struct ssp_data *ssp_parse_dt(struct device *pdev)
diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c
index 26fbd1bd9413..e50c975250e9 100644
--- a/drivers/iio/common/st_sensors/st_sensors_core.c
+++ b/drivers/iio/common/st_sensors/st_sensors_core.c
@@ -133,7 +133,7 @@ static int st_sensors_match_fs(struct st_sensor_settings *sensor_settings,
for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) {
if (sensor_settings->fs.fs_avl[i].num == 0)
- goto st_sensors_match_odr_error;
+ return ret;
if (sensor_settings->fs.fs_avl[i].num == fs) {
*index_fs_avl = i;
@@ -142,7 +142,6 @@ static int st_sensors_match_fs(struct st_sensor_settings *sensor_settings,
}
}
-st_sensors_match_odr_error:
return ret;
}
diff --git a/drivers/iio/common/st_sensors/st_sensors_trigger.c b/drivers/iio/common/st_sensors/st_sensors_trigger.c
index fdcc5a891958..224596b0e189 100644
--- a/drivers/iio/common/st_sensors/st_sensors_trigger.c
+++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c
@@ -104,7 +104,7 @@ static irqreturn_t st_sensors_irq_thread(int irq, void *p)
return IRQ_HANDLED;
/*
- * If we are using egde IRQs, new samples arrived while processing
+ * If we are using edge IRQs, new samples arrived while processing
* the IRQ and those may be missed unless we pick them here, so poll
* again. If the sensor delivery frequency is very high, this thread
* turns into a polled loop handler.
@@ -148,7 +148,7 @@ int st_sensors_allocate_trigger(struct iio_dev *indio_dev,
if (!sdata->sensor_settings->drdy_irq.addr_ihl) {
dev_err(&indio_dev->dev,
"falling/low specified for IRQ "
- "but hardware only support rising/high: "
+ "but hardware supports only rising/high: "
"will request rising/high\n");
if (irq_trig == IRQF_TRIGGER_FALLING)
irq_trig = IRQF_TRIGGER_RISING;
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index bb2057fd1b6f..fbef9107acad 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -148,9 +148,9 @@ config AD5686_SPI
depends on SPI
select AD5686
help
- Say yes here to build support for Analog Devices AD5672R, AD5676,
- AD5676R, AD5684, AD5684R, AD5684R, AD5685R, AD5686, AD5686R.
- Voltage Output Digital to Analog Converter.
+ Say yes here to build support for Analog Devices AD5672R, AD5674R,
+ AD5676, AD5676R, AD5679R, AD5684, AD5684R, AD5684R, AD5685R, AD5686,
+ AD5686R Voltage Output Digital to Analog Converter.
To compile this driver as a module, choose M here: the
module will be called ad5686.
@@ -366,6 +366,25 @@ config TI_DAC5571
If compiled as a module, it will be called ti-dac5571.
+config TI_DAC7311
+ tristate "Texas Instruments 8/10/12-bit 1-channel DAC driver"
+ depends on SPI
+ help
+ Driver for the Texas Instruments
+ DAC7311, DAC6311, DAC5311.
+
+ If compiled as a module, it will be called ti-dac7311.
+
+config TI_DAC7612
+ tristate "Texas Instruments 12-bit 2-channel DAC driver"
+ depends on SPI_MASTER && GPIOLIB
+ help
+ Driver for the Texas Instruments DAC7612, DAC7612U, DAC7612UB
+ The driver hand drive the load pin automatically, otherwise
+ it needs to be toggled manually.
+
+ If compiled as a module, it will be called ti-dac7612.
+
config VF610_DAC
tristate "Vybrid vf610 DAC driver"
depends on OF
diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
index 2ac93cc4a389..1369fa1d2f0e 100644
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -40,4 +40,6 @@ obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o
obj-$(CONFIG_STM32_DAC) += stm32-dac.o
obj-$(CONFIG_TI_DAC082S085) += ti-dac082s085.o
obj-$(CONFIG_TI_DAC5571) += ti-dac5571.o
+obj-$(CONFIG_TI_DAC7311) += ti-dac7311.o
+obj-$(CONFIG_TI_DAC7612) += ti-dac7612.o
obj-$(CONFIG_VF610_DAC) += vf610_dac.o
diff --git a/drivers/iio/dac/ad5686-spi.c b/drivers/iio/dac/ad5686-spi.c
index 1df9143f55e9..0188ded5137c 100644
--- a/drivers/iio/dac/ad5686-spi.c
+++ b/drivers/iio/dac/ad5686-spi.c
@@ -1,7 +1,8 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: GPL-2.0
/*
- * AD5672R, AD5676, AD5676R, AD5681R, AD5682R, AD5683, AD5683R,
- * AD5684, AD5684R, AD5685R, AD5686, AD5686R
+ * AD5672R, AD5674R, AD5676, AD5676R, AD5679R,
+ * AD5681R, AD5682R, AD5683, AD5683R, AD5684,
+ * AD5684R, AD5685R, AD5686, AD5686R
* Digital to analog converters driver
*
* Copyright 2018 Analog Devices Inc.
@@ -19,6 +20,12 @@ static int ad5686_spi_write(struct ad5686_state *st,
u8 tx_len, *buf;
switch (st->chip_info->regmap_type) {
+ case AD5310_REGMAP:
+ st->data[0].d16 = cpu_to_be16(AD5310_CMD(cmd) |
+ val);
+ buf = &st->data[0].d8[0];
+ tx_len = 2;
+ break;
case AD5683_REGMAP:
st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) |
AD5683_DATA(val));
@@ -56,10 +63,18 @@ static int ad5686_spi_read(struct ad5686_state *st, u8 addr)
u8 cmd = 0;
int ret;
- if (st->chip_info->regmap_type == AD5686_REGMAP)
- cmd = AD5686_CMD_READBACK_ENABLE;
- else if (st->chip_info->regmap_type == AD5683_REGMAP)
+ switch (st->chip_info->regmap_type) {
+ case AD5310_REGMAP:
+ return -ENOTSUPP;
+ case AD5683_REGMAP:
cmd = AD5686_CMD_READBACK_ENABLE_V2;
+ break;
+ case AD5686_REGMAP:
+ cmd = AD5686_CMD_READBACK_ENABLE;
+ break;
+ default:
+ return -EINVAL;
+ }
st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) |
AD5686_ADDR(addr));
@@ -86,9 +101,12 @@ static int ad5686_spi_remove(struct spi_device *spi)
}
static const struct spi_device_id ad5686_spi_id[] = {
+ {"ad5310r", ID_AD5310R},
{"ad5672r", ID_AD5672R},
+ {"ad5674r", ID_AD5674R},
{"ad5676", ID_AD5676},
{"ad5676r", ID_AD5676R},
+ {"ad5679r", ID_AD5679R},
{"ad5681r", ID_AD5681R},
{"ad5682r", ID_AD5682R},
{"ad5683", ID_AD5683},
diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c
index 0e134b13967a..e06b29c565b9 100644
--- a/drivers/iio/dac/ad5686.c
+++ b/drivers/iio/dac/ad5686.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: GPL-2.0
/*
* AD5686R, AD5685R, AD5684R Digital to analog converters driver
*
@@ -71,7 +71,7 @@ static ssize_t ad5686_write_dac_powerdown(struct iio_dev *indio_dev,
int ret;
struct ad5686_state *st = iio_priv(indio_dev);
unsigned int val, ref_bit_msk;
- u8 shift;
+ u8 shift, address = 0;
ret = strtobool(buf, &readin);
if (ret)
@@ -83,6 +83,10 @@ static ssize_t ad5686_write_dac_powerdown(struct iio_dev *indio_dev,
st->pwr_down_mask &= ~(0x3 << (chan->channel * 2));
switch (st->chip_info->regmap_type) {
+ case AD5310_REGMAP:
+ shift = 9;
+ ref_bit_msk = AD5310_REF_BIT_MSK;
+ break;
case AD5683_REGMAP:
shift = 13;
ref_bit_msk = AD5683_REF_BIT_MSK;
@@ -90,6 +94,9 @@ static ssize_t ad5686_write_dac_powerdown(struct iio_dev *indio_dev,
case AD5686_REGMAP:
shift = 0;
ref_bit_msk = 0;
+ /* AD5674R/AD5679R have 16 channels and 2 powerdown registers */
+ if (chan->channel > 0x7)
+ address = 0x8;
break;
case AD5693_REGMAP:
shift = 13;
@@ -103,7 +110,8 @@ static ssize_t ad5686_write_dac_powerdown(struct iio_dev *indio_dev,
if (!st->use_internal_vref)
val |= ref_bit_msk;
- ret = st->write(st, AD5686_CMD_POWERDOWN_DAC, 0, val);
+ ret = st->write(st, AD5686_CMD_POWERDOWN_DAC,
+ address, val >> (address * 2));
return ret ? ret : len;
}
@@ -124,7 +132,8 @@ static int ad5686_read_raw(struct iio_dev *indio_dev,
mutex_unlock(&indio_dev->mlock);
if (ret < 0)
return ret;
- *val = ret;
+ *val = (ret >> chan->scan_type.shift) &
+ GENMASK(chan->scan_type.realbits - 1, 0);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = st->vref_mv;
@@ -221,9 +230,32 @@ static struct iio_chan_spec name[] = { \
AD5868_CHANNEL(7, 7, bits, _shift), \
}
+#define DECLARE_AD5679_CHANNELS(name, bits, _shift) \
+static struct iio_chan_spec name[] = { \
+ AD5868_CHANNEL(0, 0, bits, _shift), \
+ AD5868_CHANNEL(1, 1, bits, _shift), \
+ AD5868_CHANNEL(2, 2, bits, _shift), \
+ AD5868_CHANNEL(3, 3, bits, _shift), \
+ AD5868_CHANNEL(4, 4, bits, _shift), \
+ AD5868_CHANNEL(5, 5, bits, _shift), \
+ AD5868_CHANNEL(6, 6, bits, _shift), \
+ AD5868_CHANNEL(7, 7, bits, _shift), \
+ AD5868_CHANNEL(8, 8, bits, _shift), \
+ AD5868_CHANNEL(9, 9, bits, _shift), \
+ AD5868_CHANNEL(10, 10, bits, _shift), \
+ AD5868_CHANNEL(11, 11, bits, _shift), \
+ AD5868_CHANNEL(12, 12, bits, _shift), \
+ AD5868_CHANNEL(13, 13, bits, _shift), \
+ AD5868_CHANNEL(14, 14, bits, _shift), \
+ AD5868_CHANNEL(15, 15, bits, _shift), \
+}
+
+DECLARE_AD5693_CHANNELS(ad5310r_channels, 10, 2);
DECLARE_AD5693_CHANNELS(ad5311r_channels, 10, 6);
DECLARE_AD5676_CHANNELS(ad5672_channels, 12, 4);
+DECLARE_AD5679_CHANNELS(ad5674r_channels, 12, 4);
DECLARE_AD5676_CHANNELS(ad5676_channels, 16, 0);
+DECLARE_AD5679_CHANNELS(ad5679r_channels, 16, 0);
DECLARE_AD5686_CHANNELS(ad5684_channels, 12, 4);
DECLARE_AD5686_CHANNELS(ad5685r_channels, 14, 2);
DECLARE_AD5686_CHANNELS(ad5686_channels, 16, 0);
@@ -232,6 +264,12 @@ DECLARE_AD5693_CHANNELS(ad5692r_channels, 14, 2);
DECLARE_AD5693_CHANNELS(ad5691r_channels, 12, 4);
static const struct ad5686_chip_info ad5686_chip_info_tbl[] = {
+ [ID_AD5310R] = {
+ .channels = ad5310r_channels,
+ .int_vref_mv = 2500,
+ .num_channels = 1,
+ .regmap_type = AD5310_REGMAP,
+ },
[ID_AD5311R] = {
.channels = ad5311r_channels,
.int_vref_mv = 2500,
@@ -250,6 +288,12 @@ static const struct ad5686_chip_info ad5686_chip_info_tbl[] = {
.num_channels = 8,
.regmap_type = AD5686_REGMAP,
},
+ [ID_AD5674R] = {
+ .channels = ad5674r_channels,
+ .int_vref_mv = 2500,
+ .num_channels = 16,
+ .regmap_type = AD5686_REGMAP,
+ },
[ID_AD5675R] = {
.channels = ad5676_channels,
.int_vref_mv = 2500,
@@ -267,6 +311,12 @@ static const struct ad5686_chip_info ad5686_chip_info_tbl[] = {
.num_channels = 8,
.regmap_type = AD5686_REGMAP,
},
+ [ID_AD5679R] = {
+ .channels = ad5679r_channels,
+ .int_vref_mv = 2500,
+ .num_channels = 16,
+ .regmap_type = AD5686_REGMAP,
+ },
[ID_AD5681R] = {
.channels = ad5691r_channels,
.int_vref_mv = 2500,
@@ -419,6 +469,11 @@ int ad5686_probe(struct device *dev,
indio_dev->num_channels = st->chip_info->num_channels;
switch (st->chip_info->regmap_type) {
+ case AD5310_REGMAP:
+ cmd = AD5686_CMD_CONTROL_REG;
+ ref_bit_msk = AD5310_REF_BIT_MSK;
+ st->use_internal_vref = !voltage_uv;
+ break;
case AD5683_REGMAP:
cmd = AD5686_CMD_CONTROL_REG;
ref_bit_msk = AD5683_REF_BIT_MSK;
diff --git a/drivers/iio/dac/ad5686.h b/drivers/iio/dac/ad5686.h
index 57b3c61bfb91..70a779939ddb 100644
--- a/drivers/iio/dac/ad5686.h
+++ b/drivers/iio/dac/ad5686.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0 */
/*
* This file is part of AD5686 DAC driver
*
@@ -13,7 +13,10 @@
#include <linux/mutex.h>
#include <linux/kernel.h>
+#define AD5310_CMD(x) ((x) << 12)
+
#define AD5683_DATA(x) ((x) << 4)
+
#define AD5686_ADDR(x) ((x) << 16)
#define AD5686_CMD(x) ((x) << 20)
@@ -38,6 +41,8 @@
#define AD5686_CMD_CONTROL_REG 0x4
#define AD5686_CMD_READBACK_ENABLE_V2 0x5
+
+#define AD5310_REF_BIT_MSK BIT(8)
#define AD5683_REF_BIT_MSK BIT(12)
#define AD5693_REF_BIT_MSK BIT(12)
@@ -45,12 +50,15 @@
* ad5686_supported_device_ids:
*/
enum ad5686_supported_device_ids {
+ ID_AD5310R,
ID_AD5311R,
ID_AD5671R,
ID_AD5672R,
+ ID_AD5674R,
ID_AD5675R,
ID_AD5676,
ID_AD5676R,
+ ID_AD5679R,
ID_AD5681R,
ID_AD5682R,
ID_AD5683,
@@ -72,6 +80,7 @@ enum ad5686_supported_device_ids {
};
enum ad5686_regmap_type {
+ AD5310_REGMAP,
AD5683_REGMAP,
AD5686_REGMAP,
AD5693_REGMAP
diff --git a/drivers/iio/dac/ad5696-i2c.c b/drivers/iio/dac/ad5696-i2c.c
index 7350d9806a11..ccf794caef43 100644
--- a/drivers/iio/dac/ad5696-i2c.c
+++ b/drivers/iio/dac/ad5696-i2c.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: GPL-2.0
/*
* AD5671R, AD5675R, AD5691R, AD5692R, AD5693, AD5693R,
* AD5694, AD5694R, AD5695R, AD5696, AD5696R
diff --git a/drivers/iio/dac/ad5758.c b/drivers/iio/dac/ad5758.c
index ef41f12bf262..2bdf1b0aee06 100644
--- a/drivers/iio/dac/ad5758.c
+++ b/drivers/iio/dac/ad5758.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: GPL-2.0
/*
* AD5758 Digital to analog converters driver
*
diff --git a/drivers/iio/dac/dpot-dac.c b/drivers/iio/dac/dpot-dac.c
index a791d0a09d3b..4a6111b7e86c 100644
--- a/drivers/iio/dac/dpot-dac.c
+++ b/drivers/iio/dac/dpot-dac.c
@@ -74,11 +74,11 @@ static int dpot_dac_read_raw(struct iio_dev *indio_dev,
case IIO_VAL_INT:
/*
* Convert integer scale to fractional scale by
- * setting the denominator (val2) to one...
+ * setting the denominator (val2) to one, and...
*/
*val2 = 1;
ret = IIO_VAL_FRACTIONAL;
- /* ...and fall through. */
+ /* fall through */
case IIO_VAL_FRACTIONAL:
*val *= regulator_get_voltage(dac->vref) / 1000;
*val2 *= dac->max_ohms;
diff --git a/drivers/iio/dac/mcp4725.c b/drivers/iio/dac/mcp4725.c
index 6d71fd905e29..c701a45469f6 100644
--- a/drivers/iio/dac/mcp4725.c
+++ b/drivers/iio/dac/mcp4725.c
@@ -92,6 +92,7 @@ static ssize_t mcp4725_store_eeprom(struct device *dev,
inoutbuf[0] = 0x60; /* write EEPROM */
inoutbuf[0] |= data->ref_mode << 3;
+ inoutbuf[0] |= data->powerdown ? ((data->powerdown_mode + 1) << 1) : 0;
inoutbuf[1] = data->dac_value >> 4;
inoutbuf[2] = (data->dac_value & 0xf) << 4;
diff --git a/drivers/iio/dac/ti-dac7311.c b/drivers/iio/dac/ti-dac7311.c
new file mode 100644
index 000000000000..6f5df1a30a1c
--- /dev/null
+++ b/drivers/iio/dac/ti-dac7311.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0
+/* ti-dac7311.c - Texas Instruments 8/10/12-bit 1-channel DAC driver
+ *
+ * Copyright (C) 2018 CMC NV
+ *
+ * http://www.ti.com/lit/ds/symlink/dac7311.pdf
+ */
+
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+enum {
+ ID_DAC5311 = 0,
+ ID_DAC6311,
+ ID_DAC7311,
+};
+
+enum {
+ POWER_1KOHM_TO_GND = 0,
+ POWER_100KOHM_TO_GND,
+ POWER_TRI_STATE,
+};
+
+struct ti_dac_spec {
+ u8 resolution;
+};
+
+static const struct ti_dac_spec ti_dac_spec[] = {
+ [ID_DAC5311] = { .resolution = 8 },
+ [ID_DAC6311] = { .resolution = 10 },
+ [ID_DAC7311] = { .resolution = 12 },
+};
+
+/**
+ * struct ti_dac_chip - TI DAC chip
+ * @lock: protects write sequences
+ * @vref: regulator generating Vref
+ * @spi: SPI device to send data to the device
+ * @val: cached value
+ * @powerdown: whether the chip is powered down
+ * @powerdown_mode: selected by the user
+ * @resolution: resolution of the chip
+ * @buf: buffer for transfer data
+ */
+struct ti_dac_chip {
+ struct mutex lock;
+ struct regulator *vref;
+ struct spi_device *spi;
+ u16 val;
+ bool powerdown;
+ u8 powerdown_mode;
+ u8 resolution;
+ u8 buf[2] ____cacheline_aligned;
+};
+
+static u8 ti_dac_get_power(struct ti_dac_chip *ti_dac, bool powerdown)
+{
+ if (powerdown)
+ return ti_dac->powerdown_mode + 1;
+
+ return 0;
+}
+
+static int ti_dac_cmd(struct ti_dac_chip *ti_dac, u8 power, u16 val)
+{
+ u8 shift = 14 - ti_dac->resolution;
+
+ ti_dac->buf[0] = (val << shift) & 0xFF;
+ ti_dac->buf[1] = (power << 6) | (val >> (8 - shift));
+ return spi_write(ti_dac->spi, ti_dac->buf, 2);
+}
+
+static const char * const ti_dac_powerdown_modes[] = {
+ "1kohm_to_gnd",
+ "100kohm_to_gnd",
+ "three_state",
+};
+
+static int ti_dac_get_powerdown_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
+
+ return ti_dac->powerdown_mode;
+}
+
+static int ti_dac_set_powerdown_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int mode)
+{
+ struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
+
+ ti_dac->powerdown_mode = mode;
+ return 0;
+}
+
+static const struct iio_enum ti_dac_powerdown_mode = {
+ .items = ti_dac_powerdown_modes,
+ .num_items = ARRAY_SIZE(ti_dac_powerdown_modes),
+ .get = ti_dac_get_powerdown_mode,
+ .set = ti_dac_set_powerdown_mode,
+};
+
+static ssize_t ti_dac_read_powerdown(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
+
+ return sprintf(buf, "%d\n", ti_dac->powerdown);
+}
+
+static ssize_t ti_dac_write_powerdown(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
+ bool powerdown;
+ u8 power;
+ int ret;
+
+ ret = strtobool(buf, &powerdown);
+ if (ret)
+ return ret;
+
+ power = ti_dac_get_power(ti_dac, powerdown);
+
+ mutex_lock(&ti_dac->lock);
+ ret = ti_dac_cmd(ti_dac, power, 0);
+ if (!ret)
+ ti_dac->powerdown = powerdown;
+ mutex_unlock(&ti_dac->lock);
+
+ return ret ? ret : len;
+}
+
+static const struct iio_chan_spec_ext_info ti_dac_ext_info[] = {
+ {
+ .name = "powerdown",
+ .read = ti_dac_read_powerdown,
+ .write = ti_dac_write_powerdown,
+ .shared = IIO_SHARED_BY_TYPE,
+ },
+ IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, &ti_dac_powerdown_mode),
+ IIO_ENUM_AVAILABLE("powerdown_mode", &ti_dac_powerdown_mode),
+ { },
+};
+
+#define TI_DAC_CHANNEL(chan) { \
+ .type = IIO_VOLTAGE, \
+ .channel = (chan), \
+ .output = true, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .ext_info = ti_dac_ext_info, \
+}
+
+static const struct iio_chan_spec ti_dac_channels[] = {
+ TI_DAC_CHANNEL(0),
+};
+
+static int ti_dac_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *val = ti_dac->val;
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ ret = regulator_get_voltage(ti_dac->vref);
+ if (ret < 0)
+ return ret;
+
+ *val = ret / 1000;
+ *val2 = ti_dac->resolution;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ }
+
+ return -EINVAL;
+}
+
+static int ti_dac_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
+ u8 power = ti_dac_get_power(ti_dac, ti_dac->powerdown);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (ti_dac->val == val)
+ return 0;
+
+ if (val >= (1 << ti_dac->resolution) || val < 0)
+ return -EINVAL;
+
+ if (ti_dac->powerdown)
+ return -EBUSY;
+
+ mutex_lock(&ti_dac->lock);
+ ret = ti_dac_cmd(ti_dac, power, val);
+ if (!ret)
+ ti_dac->val = val;
+ mutex_unlock(&ti_dac->lock);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int ti_dac_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, long mask)
+{
+ return IIO_VAL_INT;
+}
+
+static const struct iio_info ti_dac_info = {
+ .read_raw = ti_dac_read_raw,
+ .write_raw = ti_dac_write_raw,
+ .write_raw_get_fmt = ti_dac_write_raw_get_fmt,
+};
+
+static int ti_dac_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ const struct ti_dac_spec *spec;
+ struct ti_dac_chip *ti_dac;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*ti_dac));
+ if (!indio_dev) {
+ dev_err(dev, "can not allocate iio device\n");
+ return -ENOMEM;
+ }
+
+ spi->mode = SPI_MODE_1;
+ spi->bits_per_word = 16;
+ spi_setup(spi);
+
+ indio_dev->dev.parent = dev;
+ indio_dev->dev.of_node = spi->dev.of_node;
+ indio_dev->info = &ti_dac_info;
+ indio_dev->name = spi_get_device_id(spi)->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = ti_dac_channels;
+ spi_set_drvdata(spi, indio_dev);
+
+ ti_dac = iio_priv(indio_dev);
+ ti_dac->powerdown = false;
+ ti_dac->spi = spi;
+
+ spec = &ti_dac_spec[spi_get_device_id(spi)->driver_data];
+ indio_dev->num_channels = 1;
+ ti_dac->resolution = spec->resolution;
+
+ ti_dac->vref = devm_regulator_get(dev, "vref");
+ if (IS_ERR(ti_dac->vref)) {
+ dev_err(dev, "error to get regulator\n");
+ return PTR_ERR(ti_dac->vref);
+ }
+
+ ret = regulator_enable(ti_dac->vref);
+ if (ret < 0) {
+ dev_err(dev, "can not enable regulator\n");
+ return ret;
+ }
+
+ mutex_init(&ti_dac->lock);
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(dev, "fail to register iio device: %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ mutex_destroy(&ti_dac->lock);
+ regulator_disable(ti_dac->vref);
+ return ret;
+}
+
+static int ti_dac_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ mutex_destroy(&ti_dac->lock);
+ regulator_disable(ti_dac->vref);
+ return 0;
+}
+
+static const struct of_device_id ti_dac_of_id[] = {
+ { .compatible = "ti,dac5311" },
+ { .compatible = "ti,dac6311" },
+ { .compatible = "ti,dac7311" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ti_dac_of_id);
+
+static const struct spi_device_id ti_dac_spi_id[] = {
+ { "dac5311", ID_DAC5311 },
+ { "dac6311", ID_DAC6311 },
+ { "dac7311", ID_DAC7311 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ti_dac_spi_id);
+
+static struct spi_driver ti_dac_driver = {
+ .driver = {
+ .name = "ti-dac7311",
+ .of_match_table = ti_dac_of_id,
+ },
+ .probe = ti_dac_probe,
+ .remove = ti_dac_remove,
+ .id_table = ti_dac_spi_id,
+};
+module_spi_driver(ti_dac_driver);
+
+MODULE_AUTHOR("Charles-Antoine Couret <charles-antoine.couret@essensium.com>");
+MODULE_DESCRIPTION("Texas Instruments 8/10/12-bit 1-channel DAC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/dac/ti-dac7612.c b/drivers/iio/dac/ti-dac7612.c
new file mode 100644
index 000000000000..c46805144dd4
--- /dev/null
+++ b/drivers/iio/dac/ti-dac7612.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DAC7612 Dual, 12-Bit Serial input Digital-to-Analog Converter
+ *
+ * Copyright 2019 Qtechnology A/S
+ * 2019 Ricardo Ribalda <ricardo@ribalda.com>
+ *
+ * Licensed under the GPL-2.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/iio.h>
+
+#define DAC7612_RESOLUTION 12
+#define DAC7612_ADDRESS 4
+#define DAC7612_START 5
+
+struct dac7612 {
+ struct spi_device *spi;
+ struct gpio_desc *loaddacs;
+ uint16_t cache[2];
+
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+ uint8_t data[2] ____cacheline_aligned;
+};
+
+static int dac7612_cmd_single(struct dac7612 *priv, int channel, u16 val)
+{
+ int ret;
+
+ priv->data[0] = BIT(DAC7612_START) | (channel << DAC7612_ADDRESS);
+ priv->data[0] |= val >> 8;
+ priv->data[1] = val & 0xff;
+
+ priv->cache[channel] = val;
+
+ ret = spi_write(priv->spi, priv->data, sizeof(priv->data));
+ if (ret)
+ return ret;
+
+ gpiod_set_value(priv->loaddacs, 1);
+ gpiod_set_value(priv->loaddacs, 0);
+
+ return 0;
+}
+
+#define dac7612_CHANNEL(chan, name) { \
+ .type = IIO_VOLTAGE, \
+ .channel = (chan), \
+ .indexed = 1, \
+ .output = 1, \
+ .datasheet_name = name, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec dac7612_channels[] = {
+ dac7612_CHANNEL(0, "OUTA"),
+ dac7612_CHANNEL(1, "OUTB"),
+};
+
+static int dac7612_read_raw(struct iio_dev *iio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct dac7612 *priv;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ priv = iio_priv(iio_dev);
+ *val = priv->cache[chan->channel];
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1;
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int dac7612_write_raw(struct iio_dev *iio_dev,
+ const struct iio_chan_spec *chan,
+ int val, int val2, long mask)
+{
+ struct dac7612 *priv = iio_priv(iio_dev);
+ int ret;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ if ((val >= BIT(DAC7612_RESOLUTION)) || val < 0 || val2)
+ return -EINVAL;
+
+ if (val == priv->cache[chan->channel])
+ return 0;
+
+ mutex_lock(&iio_dev->mlock);
+ ret = dac7612_cmd_single(priv, chan->channel, val);
+ mutex_unlock(&iio_dev->mlock);
+
+ return ret;
+}
+
+static const struct iio_info dac7612_info = {
+ .read_raw = dac7612_read_raw,
+ .write_raw = dac7612_write_raw,
+};
+
+static int dac7612_probe(struct spi_device *spi)
+{
+ struct iio_dev *iio_dev;
+ struct dac7612 *priv;
+ int i;
+ int ret;
+
+ iio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*priv));
+ if (!iio_dev)
+ return -ENOMEM;
+
+ priv = iio_priv(iio_dev);
+ /*
+ * LOADDACS pin can be controlled by the driver or externally.
+ * When controlled by the driver, the DAC value is updated after
+ * every write.
+ * When the driver does not control the PIN, the user or an external
+ * event can change the value of all DACs by pulsing down the LOADDACs
+ * pin.
+ */
+ priv->loaddacs = devm_gpiod_get_optional(&spi->dev, "ti,loaddacs",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(priv->loaddacs))
+ return PTR_ERR(priv->loaddacs);
+ priv->spi = spi;
+ spi_set_drvdata(spi, iio_dev);
+ iio_dev->dev.parent = &spi->dev;
+ iio_dev->info = &dac7612_info;
+ iio_dev->modes = INDIO_DIRECT_MODE;
+ iio_dev->channels = dac7612_channels;
+ iio_dev->num_channels = ARRAY_SIZE(priv->cache);
+ iio_dev->name = spi_get_device_id(spi)->name;
+
+ for (i = 0; i < ARRAY_SIZE(priv->cache); i++) {
+ ret = dac7612_cmd_single(priv, i, 0);
+ if (ret)
+ return ret;
+ }
+
+ return devm_iio_device_register(&spi->dev, iio_dev);
+}
+
+static const struct spi_device_id dac7612_id[] = {
+ {"ti-dac7612"},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, dac7612_id);
+
+static const struct of_device_id dac7612_of_match[] = {
+ { .compatible = "ti,dac7612" },
+ { .compatible = "ti,dac7612u" },
+ { .compatible = "ti,dac7612ub" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, dac7612_of_match);
+
+static struct spi_driver dac7612_driver = {
+ .driver = {
+ .name = "ti-dac7612",
+ .of_match_table = dac7612_of_match,
+ },
+ .probe = dac7612_probe,
+ .id_table = dac7612_id,
+};
+module_spi_driver(dac7612_driver);
+
+MODULE_AUTHOR("Ricardo Ribalda <ricardo@ribalda.com>");
+MODULE_DESCRIPTION("Texas Instruments DAC7612 DAC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/frequency/ad9523.c b/drivers/iio/frequency/ad9523.c
index f3f94fbdd20a..3f9be69499ec 100644
--- a/drivers/iio/frequency/ad9523.c
+++ b/drivers/iio/frequency/ad9523.c
@@ -943,11 +943,14 @@ static int ad9523_setup(struct iio_dev *indio_dev)
}
}
- for_each_clear_bit(i, &active_mask, AD9523_NUM_CHAN)
- ad9523_write(indio_dev,
+ for_each_clear_bit(i, &active_mask, AD9523_NUM_CHAN) {
+ ret = ad9523_write(indio_dev,
AD9523_CHANNEL_CLOCK_DIST(i),
AD9523_CLK_DIST_DRIVER_MODE(TRISTATE) |
AD9523_CLK_DIST_PWR_DOWN_EN);
+ if (ret < 0)
+ return ret;
+ }
ret = ad9523_write(indio_dev, AD9523_POWER_DOWN_CTRL, 0);
if (ret < 0)
diff --git a/drivers/iio/gyro/bmg160_core.c b/drivers/iio/gyro/bmg160_core.c
index 63ca31628a93..92c07ab826eb 100644
--- a/drivers/iio/gyro/bmg160_core.c
+++ b/drivers/iio/gyro/bmg160_core.c
@@ -582,11 +582,10 @@ static int bmg160_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
return bmg160_get_filter(data, val);
case IIO_CHAN_INFO_SCALE:
- *val = 0;
switch (chan->type) {
case IIO_TEMP:
- *val2 = 500000;
- return IIO_VAL_INT_PLUS_MICRO;
+ *val = 500;
+ return IIO_VAL_INT;
case IIO_ANGL_VEL:
{
int i;
@@ -594,6 +593,7 @@ static int bmg160_read_raw(struct iio_dev *indio_dev,
for (i = 0; i < ARRAY_SIZE(bmg160_scale_table); ++i) {
if (bmg160_scale_table[i].dps_range ==
data->dps_range) {
+ *val = 0;
*val2 = bmg160_scale_table[i].scale;
return IIO_VAL_INT_PLUS_MICRO;
}
diff --git a/drivers/iio/gyro/mpu3050-core.c b/drivers/iio/gyro/mpu3050-core.c
index 77fac81a3adc..5ddebede31a6 100644
--- a/drivers/iio/gyro/mpu3050-core.c
+++ b/drivers/iio/gyro/mpu3050-core.c
@@ -29,7 +29,8 @@
#include "mpu3050.h"
-#define MPU3050_CHIP_ID 0x69
+#define MPU3050_CHIP_ID 0x68
+#define MPU3050_CHIP_ID_MASK 0x7E
/*
* Register map: anything suffixed *_H is a big-endian high byte and always
@@ -1176,8 +1177,9 @@ int mpu3050_common_probe(struct device *dev,
goto err_power_down;
}
- if (val != MPU3050_CHIP_ID) {
- dev_err(dev, "unsupported chip id %02x\n", (u8)val);
+ if ((val & MPU3050_CHIP_ID_MASK) != MPU3050_CHIP_ID) {
+ dev_err(dev, "unsupported chip id %02x\n",
+ (u8)(val & MPU3050_CHIP_ID_MASK));
ret = -ENODEV;
goto err_power_down;
}
diff --git a/drivers/iio/imu/bmi160/bmi160.h b/drivers/iio/imu/bmi160/bmi160.h
index e7b11e74fd1d..621f5309d735 100644
--- a/drivers/iio/imu/bmi160/bmi160.h
+++ b/drivers/iio/imu/bmi160/bmi160.h
@@ -2,10 +2,20 @@
#ifndef BMI160_H_
#define BMI160_H_
+#include <linux/iio/iio.h>
+
+struct bmi160_data {
+ struct regmap *regmap;
+ struct iio_trigger *trig;
+};
+
extern const struct regmap_config bmi160_regmap_config;
int bmi160_core_probe(struct device *dev, struct regmap *regmap,
const char *name, bool use_spi);
-void bmi160_core_remove(struct device *dev);
+
+int bmi160_enable_irq(struct regmap *regmap, bool enable);
+
+int bmi160_probe_trigger(struct iio_dev *indio_dev, int irq, u32 irq_type);
#endif /* BMI160_H_ */
diff --git a/drivers/iio/imu/bmi160/bmi160_core.c b/drivers/iio/imu/bmi160/bmi160_core.c
index c85659ca9507..6af65d6f1d28 100644
--- a/drivers/iio/imu/bmi160/bmi160_core.c
+++ b/drivers/iio/imu/bmi160/bmi160_core.c
@@ -1,26 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* BMI160 - Bosch IMU (accel, gyro plus external magnetometer)
*
* Copyright (c) 2016, Intel Corporation.
- *
- * This file is subject to the terms and conditions of version 2 of
- * the GNU General Public License. See the file COPYING in the main
- * directory of this archive for more details.
+ * Copyright (c) 2019, Martin Kelly.
*
* IIO core driver for BMI160, with support for I2C/SPI busses
*
- * TODO: magnetometer, interrupts, hardware FIFO
+ * TODO: magnetometer, hardware FIFO
*/
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/acpi.h>
#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/of_irq.h>
#include <linux/iio/iio.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/buffer.h>
#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
#include "bmi160.h"
@@ -64,8 +65,32 @@
#define BMI160_CMD_GYRO_PM_FAST_STARTUP 0x17
#define BMI160_CMD_SOFTRESET 0xB6
+#define BMI160_REG_INT_EN 0x51
+#define BMI160_DRDY_INT_EN BIT(4)
+
+#define BMI160_REG_INT_OUT_CTRL 0x53
+#define BMI160_INT_OUT_CTRL_MASK 0x0f
+#define BMI160_INT1_OUT_CTRL_SHIFT 0
+#define BMI160_INT2_OUT_CTRL_SHIFT 4
+#define BMI160_EDGE_TRIGGERED BIT(0)
+#define BMI160_ACTIVE_HIGH BIT(1)
+#define BMI160_OPEN_DRAIN BIT(2)
+#define BMI160_OUTPUT_EN BIT(3)
+
+#define BMI160_REG_INT_LATCH 0x54
+#define BMI160_INT1_LATCH_MASK BIT(4)
+#define BMI160_INT2_LATCH_MASK BIT(5)
+
+/* INT1 and INT2 are in the opposite order as in INT_OUT_CTRL! */
+#define BMI160_REG_INT_MAP 0x56
+#define BMI160_INT1_MAP_DRDY_EN 0x80
+#define BMI160_INT2_MAP_DRDY_EN 0x08
+
#define BMI160_REG_DUMMY 0x7F
+#define BMI160_NORMAL_WRITE_USLEEP 2
+#define BMI160_SUSPENDED_WRITE_USLEEP 450
+
#define BMI160_ACCEL_PMU_MIN_USLEEP 3800
#define BMI160_GYRO_PMU_MIN_USLEEP 80000
#define BMI160_SOFTRESET_USLEEP 1000
@@ -108,8 +133,9 @@ enum bmi160_sensor_type {
BMI160_NUM_SENSORS /* must be last */
};
-struct bmi160_data {
- struct regmap *regmap;
+enum bmi160_int_pin {
+ BMI160_PIN_INT1,
+ BMI160_PIN_INT2
};
const struct regmap_config bmi160_regmap_config = {
@@ -273,7 +299,7 @@ int bmi160_set_mode(struct bmi160_data *data, enum bmi160_sensor_type t,
cmd = bmi160_regs[t].pmu_cmd_suspend;
ret = regmap_write(data->regmap, BMI160_REG_CMD, cmd);
- if (ret < 0)
+ if (ret)
return ret;
usleep_range(bmi160_pmu_time[t], bmi160_pmu_time[t] + 1000);
@@ -305,7 +331,7 @@ int bmi160_get_scale(struct bmi160_data *data, enum bmi160_sensor_type t,
int i, ret, val;
ret = regmap_read(data->regmap, bmi160_regs[t].range, &val);
- if (ret < 0)
+ if (ret)
return ret;
for (i = 0; i < bmi160_scale_table[t].num; i++)
@@ -328,7 +354,7 @@ static int bmi160_get_data(struct bmi160_data *data, int chan_type,
reg = bmi160_regs[t].data + (axis - IIO_MOD_X) * sizeof(sample);
ret = regmap_bulk_read(data->regmap, reg, &sample, sizeof(sample));
- if (ret < 0)
+ if (ret)
return ret;
*val = sign_extend32(le16_to_cpu(sample), 15);
@@ -362,7 +388,7 @@ static int bmi160_get_odr(struct bmi160_data *data, enum bmi160_sensor_type t,
int i, val, ret;
ret = regmap_read(data->regmap, bmi160_regs[t].config, &val);
- if (ret < 0)
+ if (ret)
return ret;
val &= bmi160_regs[t].config_odr_mask;
@@ -394,13 +420,12 @@ static irqreturn_t bmi160_trigger_handler(int irq, void *p)
indio_dev->masklength) {
ret = regmap_bulk_read(data->regmap, base + i * sizeof(sample),
&sample, sizeof(sample));
- if (ret < 0)
+ if (ret)
goto done;
buf[j++] = sample;
}
- iio_push_to_buffers_with_timestamp(indio_dev, buf,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_timestamp(indio_dev, buf, pf->timestamp);
done:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
@@ -416,18 +441,18 @@ static int bmi160_read_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = bmi160_get_data(data, chan->type, chan->channel2, val);
- if (ret < 0)
+ if (ret)
return ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = 0;
ret = bmi160_get_scale(data,
bmi160_to_sensor(chan->type), val2);
- return ret < 0 ? ret : IIO_VAL_INT_PLUS_MICRO;
+ return ret ? ret : IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_SAMP_FREQ:
ret = bmi160_get_odr(data, bmi160_to_sensor(chan->type),
val, val2);
- return ret < 0 ? ret : IIO_VAL_INT_PLUS_MICRO;
+ return ret ? ret : IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
@@ -498,6 +523,186 @@ static const char *bmi160_match_acpi_device(struct device *dev)
return dev_name(dev);
}
+static int bmi160_write_conf_reg(struct regmap *regmap, unsigned int reg,
+ unsigned int mask, unsigned int bits,
+ unsigned int write_usleep)
+{
+ int ret;
+ unsigned int val;
+
+ ret = regmap_read(regmap, reg, &val);
+ if (ret)
+ return ret;
+
+ val = (val & ~mask) | bits;
+
+ ret = regmap_write(regmap, reg, val);
+ if (ret)
+ return ret;
+
+ /*
+ * We need to wait after writing before we can write again. See the
+ * datasheet, page 93.
+ */
+ usleep_range(write_usleep, write_usleep + 1000);
+
+ return 0;
+}
+
+static int bmi160_config_pin(struct regmap *regmap, enum bmi160_int_pin pin,
+ bool open_drain, u8 irq_mask,
+ unsigned long write_usleep)
+{
+ int ret;
+ struct device *dev = regmap_get_device(regmap);
+ u8 int_out_ctrl_shift;
+ u8 int_latch_mask;
+ u8 int_map_mask;
+ u8 int_out_ctrl_mask;
+ u8 int_out_ctrl_bits;
+ const char *pin_name;
+
+ switch (pin) {
+ case BMI160_PIN_INT1:
+ int_out_ctrl_shift = BMI160_INT1_OUT_CTRL_SHIFT;
+ int_latch_mask = BMI160_INT1_LATCH_MASK;
+ int_map_mask = BMI160_INT1_MAP_DRDY_EN;
+ break;
+ case BMI160_PIN_INT2:
+ int_out_ctrl_shift = BMI160_INT2_OUT_CTRL_SHIFT;
+ int_latch_mask = BMI160_INT2_LATCH_MASK;
+ int_map_mask = BMI160_INT2_MAP_DRDY_EN;
+ break;
+ }
+ int_out_ctrl_mask = BMI160_INT_OUT_CTRL_MASK << int_out_ctrl_shift;
+
+ /*
+ * Enable the requested pin with the right settings:
+ * - Push-pull/open-drain
+ * - Active low/high
+ * - Edge/level triggered
+ */
+ int_out_ctrl_bits = BMI160_OUTPUT_EN;
+ if (open_drain)
+ /* Default is push-pull. */
+ int_out_ctrl_bits |= BMI160_OPEN_DRAIN;
+ int_out_ctrl_bits |= irq_mask;
+ int_out_ctrl_bits <<= int_out_ctrl_shift;
+
+ ret = bmi160_write_conf_reg(regmap, BMI160_REG_INT_OUT_CTRL,
+ int_out_ctrl_mask, int_out_ctrl_bits,
+ write_usleep);
+ if (ret)
+ return ret;
+
+ /* Set the pin to input mode with no latching. */
+ ret = bmi160_write_conf_reg(regmap, BMI160_REG_INT_LATCH,
+ int_latch_mask, int_latch_mask,
+ write_usleep);
+ if (ret)
+ return ret;
+
+ /* Map interrupts to the requested pin. */
+ ret = bmi160_write_conf_reg(regmap, BMI160_REG_INT_MAP,
+ int_map_mask, int_map_mask,
+ write_usleep);
+ if (ret) {
+ switch (pin) {
+ case BMI160_PIN_INT1:
+ pin_name = "INT1";
+ break;
+ case BMI160_PIN_INT2:
+ pin_name = "INT2";
+ break;
+ }
+ dev_err(dev, "Failed to configure %s IRQ pin", pin_name);
+ }
+
+ return ret;
+}
+
+int bmi160_enable_irq(struct regmap *regmap, bool enable)
+{
+ unsigned int enable_bit = 0;
+
+ if (enable)
+ enable_bit = BMI160_DRDY_INT_EN;
+
+ return bmi160_write_conf_reg(regmap, BMI160_REG_INT_EN,
+ BMI160_DRDY_INT_EN, enable_bit,
+ BMI160_NORMAL_WRITE_USLEEP);
+}
+EXPORT_SYMBOL(bmi160_enable_irq);
+
+static int bmi160_get_irq(struct device_node *of_node, enum bmi160_int_pin *pin)
+{
+ int irq;
+
+ /* Use INT1 if possible, otherwise fall back to INT2. */
+ irq = of_irq_get_byname(of_node, "INT1");
+ if (irq > 0) {
+ *pin = BMI160_PIN_INT1;
+ return irq;
+ }
+
+ irq = of_irq_get_byname(of_node, "INT2");
+ if (irq > 0)
+ *pin = BMI160_PIN_INT2;
+
+ return irq;
+}
+
+static int bmi160_config_device_irq(struct iio_dev *indio_dev, int irq_type,
+ enum bmi160_int_pin pin)
+{
+ bool open_drain;
+ u8 irq_mask;
+ struct bmi160_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+
+ /* Level-triggered, active-low is the default if we set all zeroes. */
+ if (irq_type == IRQF_TRIGGER_RISING)
+ irq_mask = BMI160_ACTIVE_HIGH | BMI160_EDGE_TRIGGERED;
+ else if (irq_type == IRQF_TRIGGER_FALLING)
+ irq_mask = BMI160_EDGE_TRIGGERED;
+ else if (irq_type == IRQF_TRIGGER_HIGH)
+ irq_mask = BMI160_ACTIVE_HIGH;
+ else if (irq_type == IRQF_TRIGGER_LOW)
+ irq_mask = 0;
+ else {
+ dev_err(&indio_dev->dev,
+ "Invalid interrupt type 0x%x specified\n", irq_type);
+ return -EINVAL;
+ }
+
+ open_drain = of_property_read_bool(dev->of_node, "drive-open-drain");
+
+ return bmi160_config_pin(data->regmap, pin, open_drain, irq_mask,
+ BMI160_NORMAL_WRITE_USLEEP);
+}
+
+static int bmi160_setup_irq(struct iio_dev *indio_dev, int irq,
+ enum bmi160_int_pin pin)
+{
+ struct irq_data *desc;
+ u32 irq_type;
+ int ret;
+
+ desc = irq_get_irq_data(irq);
+ if (!desc) {
+ dev_err(&indio_dev->dev, "Could not find IRQ %d\n", irq);
+ return -EINVAL;
+ }
+
+ irq_type = irqd_get_trigger_type(desc);
+
+ ret = bmi160_config_device_irq(indio_dev, irq_type, pin);
+ if (ret)
+ return ret;
+
+ return bmi160_probe_trigger(indio_dev, irq, irq_type);
+}
+
static int bmi160_chip_init(struct bmi160_data *data, bool use_spi)
{
int ret;
@@ -505,7 +710,7 @@ static int bmi160_chip_init(struct bmi160_data *data, bool use_spi)
struct device *dev = regmap_get_device(data->regmap);
ret = regmap_write(data->regmap, BMI160_REG_CMD, BMI160_CMD_SOFTRESET);
- if (ret < 0)
+ if (ret)
return ret;
usleep_range(BMI160_SOFTRESET_USLEEP, BMI160_SOFTRESET_USLEEP + 1);
@@ -516,12 +721,12 @@ static int bmi160_chip_init(struct bmi160_data *data, bool use_spi)
*/
if (use_spi) {
ret = regmap_read(data->regmap, BMI160_REG_DUMMY, &val);
- if (ret < 0)
+ if (ret)
return ret;
}
ret = regmap_read(data->regmap, BMI160_REG_CHIP_ID, &val);
- if (ret < 0) {
+ if (ret) {
dev_err(dev, "Error reading chip id\n");
return ret;
}
@@ -532,20 +737,65 @@ static int bmi160_chip_init(struct bmi160_data *data, bool use_spi)
}
ret = bmi160_set_mode(data, BMI160_ACCEL, true);
- if (ret < 0)
+ if (ret)
return ret;
ret = bmi160_set_mode(data, BMI160_GYRO, true);
- if (ret < 0)
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int bmi160_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool enable)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct bmi160_data *data = iio_priv(indio_dev);
+
+ return bmi160_enable_irq(data->regmap, enable);
+}
+
+static const struct iio_trigger_ops bmi160_trigger_ops = {
+ .set_trigger_state = &bmi160_data_rdy_trigger_set_state,
+};
+
+int bmi160_probe_trigger(struct iio_dev *indio_dev, int irq, u32 irq_type)
+{
+ struct bmi160_data *data = iio_priv(indio_dev);
+ int ret;
+
+ data->trig = devm_iio_trigger_alloc(&indio_dev->dev, "%s-dev%d",
+ indio_dev->name, indio_dev->id);
+
+ if (data->trig == NULL)
+ return -ENOMEM;
+
+ ret = devm_request_irq(&indio_dev->dev, irq,
+ &iio_trigger_generic_data_rdy_poll,
+ irq_type, "bmi160", data->trig);
+ if (ret)
+ return ret;
+
+ data->trig->dev.parent = regmap_get_device(data->regmap);
+ data->trig->ops = &bmi160_trigger_ops;
+ iio_trigger_set_drvdata(data->trig, indio_dev);
+
+ ret = devm_iio_trigger_register(&indio_dev->dev, data->trig);
+ if (ret)
return ret;
+ indio_dev->trig = iio_trigger_get(data->trig);
+
return 0;
}
-static void bmi160_chip_uninit(struct bmi160_data *data)
+static void bmi160_chip_uninit(void *data)
{
- bmi160_set_mode(data, BMI160_GYRO, false);
- bmi160_set_mode(data, BMI160_ACCEL, false);
+ struct bmi160_data *bmi_data = data;
+
+ bmi160_set_mode(bmi_data, BMI160_GYRO, false);
+ bmi160_set_mode(bmi_data, BMI160_ACCEL, false);
}
int bmi160_core_probe(struct device *dev, struct regmap *regmap,
@@ -553,6 +803,8 @@ int bmi160_core_probe(struct device *dev, struct regmap *regmap,
{
struct iio_dev *indio_dev;
struct bmi160_data *data;
+ int irq;
+ enum bmi160_int_pin int_pin;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
@@ -564,7 +816,11 @@ int bmi160_core_probe(struct device *dev, struct regmap *regmap,
data->regmap = regmap;
ret = bmi160_chip_init(data, use_spi);
- if (ret < 0)
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, bmi160_chip_uninit, data);
+ if (ret)
return ret;
if (!name && ACPI_HANDLE(dev))
@@ -577,35 +833,26 @@ int bmi160_core_probe(struct device *dev, struct regmap *regmap,
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &bmi160_info;
- ret = iio_triggered_buffer_setup(indio_dev, NULL,
- bmi160_trigger_handler, NULL);
- if (ret < 0)
- goto uninit;
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ bmi160_trigger_handler, NULL);
+ if (ret)
+ return ret;
- ret = iio_device_register(indio_dev);
- if (ret < 0)
- goto buffer_cleanup;
+ irq = bmi160_get_irq(dev->of_node, &int_pin);
+ if (irq > 0) {
+ ret = bmi160_setup_irq(indio_dev, irq, int_pin);
+ if (ret)
+ dev_err(&indio_dev->dev, "Failed to setup IRQ %d\n",
+ irq);
+ } else {
+ dev_info(&indio_dev->dev, "Not setting up IRQ trigger\n");
+ }
- return 0;
-buffer_cleanup:
- iio_triggered_buffer_cleanup(indio_dev);
-uninit:
- bmi160_chip_uninit(data);
- return ret;
+ return devm_iio_device_register(dev, indio_dev);
}
EXPORT_SYMBOL_GPL(bmi160_core_probe);
-void bmi160_core_remove(struct device *dev)
-{
- struct iio_dev *indio_dev = dev_get_drvdata(dev);
- struct bmi160_data *data = iio_priv(indio_dev);
-
- iio_device_unregister(indio_dev);
- iio_triggered_buffer_cleanup(indio_dev);
- bmi160_chip_uninit(data);
-}
-EXPORT_SYMBOL_GPL(bmi160_core_remove);
-
MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com");
MODULE_DESCRIPTION("Bosch BMI160 driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/imu/bmi160/bmi160_i2c.c b/drivers/iio/imu/bmi160/bmi160_i2c.c
index 155a31f72445..e36f5e82d400 100644
--- a/drivers/iio/imu/bmi160/bmi160_i2c.c
+++ b/drivers/iio/imu/bmi160/bmi160_i2c.c
@@ -1,12 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* BMI160 - Bosch IMU, I2C bits
*
* Copyright (c) 2016, Intel Corporation.
*
- * This file is subject to the terms and conditions of version 2 of
- * the GNU General Public License. See the file COPYING in the main
- * directory of this archive for more details.
- *
* 7-bit I2C slave address is:
* - 0x68 if SDO is pulled to GND
* - 0x69 if SDO is pulled to VDDIO
@@ -38,13 +35,6 @@ static int bmi160_i2c_probe(struct i2c_client *client,
return bmi160_core_probe(&client->dev, regmap, name, false);
}
-static int bmi160_i2c_remove(struct i2c_client *client)
-{
- bmi160_core_remove(&client->dev);
-
- return 0;
-}
-
static const struct i2c_device_id bmi160_i2c_id[] = {
{"bmi160", 0},
{}
@@ -72,7 +62,6 @@ static struct i2c_driver bmi160_i2c_driver = {
.of_match_table = of_match_ptr(bmi160_of_match),
},
.probe = bmi160_i2c_probe,
- .remove = bmi160_i2c_remove,
.id_table = bmi160_i2c_id,
};
module_i2c_driver(bmi160_i2c_driver);
diff --git a/drivers/iio/imu/bmi160/bmi160_spi.c b/drivers/iio/imu/bmi160/bmi160_spi.c
index d34dfdfd1a7d..c19e3df35559 100644
--- a/drivers/iio/imu/bmi160/bmi160_spi.c
+++ b/drivers/iio/imu/bmi160/bmi160_spi.c
@@ -1,11 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* BMI160 - Bosch IMU, SPI bits
*
* Copyright (c) 2016, Intel Corporation.
*
- * This file is subject to the terms and conditions of version 2 of
- * the GNU General Public License. See the file COPYING in the main
- * directory of this archive for more details.
*/
#include <linux/acpi.h>
#include <linux/module.h>
@@ -29,13 +27,6 @@ static int bmi160_spi_probe(struct spi_device *spi)
return bmi160_core_probe(&spi->dev, regmap, id->name, true);
}
-static int bmi160_spi_remove(struct spi_device *spi)
-{
- bmi160_core_remove(&spi->dev);
-
- return 0;
-}
-
static const struct spi_device_id bmi160_spi_id[] = {
{"bmi160", 0},
{}
@@ -58,7 +49,6 @@ MODULE_DEVICE_TABLE(of, bmi160_of_match);
static struct spi_driver bmi160_spi_driver = {
.probe = bmi160_spi_probe,
- .remove = bmi160_spi_remove,
.id_table = bmi160_spi_id,
.driver = {
.acpi_match_table = ACPI_PTR(bmi160_acpi_match),
diff --git a/drivers/iio/imu/inv_mpu6050/Kconfig b/drivers/iio/imu/inv_mpu6050/Kconfig
index 5483b2ea754d..d2fe9dbddda7 100644
--- a/drivers/iio/imu/inv_mpu6050/Kconfig
+++ b/drivers/iio/imu/inv_mpu6050/Kconfig
@@ -13,8 +13,8 @@ config INV_MPU6050_I2C
select INV_MPU6050_IIO
select REGMAP_I2C
help
- This driver supports the Invensense MPU6050/6500/9150 and ICM20608
- motion tracking devices over I2C.
+ This driver supports the Invensense MPU6050/6500/9150 and
+ ICM20608/20602 motion tracking devices over I2C.
This driver can be built as a module. The module will be called
inv-mpu6050-i2c.
@@ -24,7 +24,7 @@ config INV_MPU6050_SPI
select INV_MPU6050_IIO
select REGMAP_SPI
help
- This driver supports the Invensense MPU6050/6500/9150 and ICM20608
- motion tracking devices over SPI.
+ This driver supports the Invensense MPU6050/6500/9150 and
+ ICM20608/20602 motion tracking devices over SPI.
This driver can be built as a module. The module will be called
inv-mpu6050-spi.
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c
index d78a10403bac..a961b5a06fe6 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c
@@ -91,18 +91,14 @@ static int asus_acpi_get_sensor_info(struct acpi_device *adev,
static int acpi_i2c_check_resource(struct acpi_resource *ares, void *data)
{
+ struct acpi_resource_i2c_serialbus *sb;
u32 *addr = data;
- if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) {
- struct acpi_resource_i2c_serialbus *sb;
-
- sb = &ares->data.i2c_serial_bus;
- if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_I2C) {
- if (*addr)
- *addr |= (sb->slave_address << 16);
- else
- *addr = sb->slave_address;
- }
+ if (i2c_acpi_get_i2c_resource(ares, &sb)) {
+ if (*addr)
+ *addr |= (sb->slave_address << 16);
+ else
+ *addr = sb->slave_address;
}
/* Tell the ACPI core that we already copied this address */
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
index 1e428c196a82..650de0fefb7b 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
@@ -38,6 +38,29 @@ static const int gyro_scale_6050[] = {133090, 266181, 532362, 1064724};
*/
static const int accel_scale[] = {598, 1196, 2392, 4785};
+static const struct inv_mpu6050_reg_map reg_set_icm20602 = {
+ .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV,
+ .lpf = INV_MPU6050_REG_CONFIG,
+ .accel_lpf = INV_MPU6500_REG_ACCEL_CONFIG_2,
+ .user_ctrl = INV_MPU6050_REG_USER_CTRL,
+ .fifo_en = INV_MPU6050_REG_FIFO_EN,
+ .gyro_config = INV_MPU6050_REG_GYRO_CONFIG,
+ .accl_config = INV_MPU6050_REG_ACCEL_CONFIG,
+ .fifo_count_h = INV_MPU6050_REG_FIFO_COUNT_H,
+ .fifo_r_w = INV_MPU6050_REG_FIFO_R_W,
+ .raw_gyro = INV_MPU6050_REG_RAW_GYRO,
+ .raw_accl = INV_MPU6050_REG_RAW_ACCEL,
+ .temperature = INV_MPU6050_REG_TEMPERATURE,
+ .int_enable = INV_MPU6050_REG_INT_ENABLE,
+ .int_status = INV_MPU6050_REG_INT_STATUS,
+ .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1,
+ .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2,
+ .int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG,
+ .accl_offset = INV_MPU6500_REG_ACCEL_OFFSET,
+ .gyro_offset = INV_MPU6050_REG_GYRO_OFFSET,
+ .i2c_if = INV_ICM20602_REG_I2C_IF,
+};
+
static const struct inv_mpu6050_reg_map reg_set_6500 = {
.sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV,
.lpf = INV_MPU6050_REG_CONFIG,
@@ -58,6 +81,7 @@ static const struct inv_mpu6050_reg_map reg_set_6500 = {
.int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG,
.accl_offset = INV_MPU6500_REG_ACCEL_OFFSET,
.gyro_offset = INV_MPU6050_REG_GYRO_OFFSET,
+ .i2c_if = 0,
};
static const struct inv_mpu6050_reg_map reg_set_6050 = {
@@ -78,6 +102,7 @@ static const struct inv_mpu6050_reg_map reg_set_6050 = {
.int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG,
.accl_offset = INV_MPU6050_REG_ACCEL_OFFSET,
.gyro_offset = INV_MPU6050_REG_GYRO_OFFSET,
+ .i2c_if = 0,
};
static const struct inv_mpu6050_chip_config chip_config_6050 = {
@@ -140,6 +165,12 @@ static const struct inv_mpu6050_hw hw_info[] = {
.reg = &reg_set_6500,
.config = &chip_config_6050,
},
+ {
+ .whoami = INV_ICM20602_WHOAMI_VALUE,
+ .name = "ICM20602",
+ .reg = &reg_set_icm20602,
+ .config = &chip_config_6050,
+ },
};
int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask)
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c
index dd758e3d403d..e46eb4ddea21 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c
@@ -127,6 +127,7 @@ static int inv_mpu_probe(struct i2c_client *client,
st = iio_priv(dev_get_drvdata(&client->dev));
switch (st->chip_type) {
case INV_ICM20608:
+ case INV_ICM20602:
/* no i2c auxiliary bus on the chip */
break;
default:
@@ -179,6 +180,7 @@ static const struct i2c_device_id inv_mpu_id[] = {
{"mpu9250", INV_MPU9250},
{"mpu9255", INV_MPU9255},
{"icm20608", INV_ICM20608},
+ {"icm20602", INV_ICM20602},
{}
};
@@ -213,6 +215,10 @@ static const struct of_device_id inv_of_match[] = {
.compatible = "invensense,icm20608",
.data = (void *)INV_ICM20608
},
+ {
+ .compatible = "invensense,icm20602",
+ .data = (void *)INV_ICM20602
+ },
{ }
};
MODULE_DEVICE_TABLE(of, inv_of_match);
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
index 6bcc11fc1b88..325afd9f5f61 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
@@ -44,6 +44,7 @@
* @int_pin_cfg; Controls interrupt pin configuration.
* @accl_offset: Controls the accelerometer calibration offset.
* @gyro_offset: Controls the gyroscope calibration offset.
+ * @i2c_if: Controls the i2c interface
*/
struct inv_mpu6050_reg_map {
u8 sample_rate_div;
@@ -65,6 +66,7 @@ struct inv_mpu6050_reg_map {
u8 int_pin_cfg;
u8 accl_offset;
u8 gyro_offset;
+ u8 i2c_if;
};
/*device enum */
@@ -77,6 +79,7 @@ enum inv_devices {
INV_MPU9250,
INV_MPU9255,
INV_ICM20608,
+ INV_ICM20602,
INV_NUM_PARTS
};
@@ -195,6 +198,10 @@ struct inv_mpu6050_state {
#define INV_MPU6050_BIT_PWR_ACCL_STBY 0x38
#define INV_MPU6050_BIT_PWR_GYRO_STBY 0x07
+/* ICM20602 register */
+#define INV_ICM20602_REG_I2C_IF 0x70
+#define INV_ICM20602_BIT_I2C_IF_DIS 0x40
+
#define INV_MPU6050_REG_FIFO_COUNT_H 0x72
#define INV_MPU6050_REG_FIFO_R_W 0x74
@@ -261,6 +268,7 @@ struct inv_mpu6050_state {
#define INV_MPU9255_WHOAMI_VALUE 0x73
#define INV_MPU6515_WHOAMI_VALUE 0x74
#define INV_ICM20608_WHOAMI_VALUE 0xAF
+#define INV_ICM20602_WHOAMI_VALUE 0x12
/* scan element definition */
enum inv_mpu6050_scan {
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c
index 227f50afff22..a112c3f45f74 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c
@@ -31,9 +31,14 @@ static int inv_mpu_i2c_disable(struct iio_dev *indio_dev)
if (ret)
return ret;
- st->chip_config.user_ctrl |= INV_MPU6050_BIT_I2C_IF_DIS;
- ret = regmap_write(st->map, st->reg->user_ctrl,
- st->chip_config.user_ctrl);
+ if (st->reg->i2c_if) {
+ ret = regmap_write(st->map, st->reg->i2c_if,
+ INV_ICM20602_BIT_I2C_IF_DIS);
+ } else {
+ st->chip_config.user_ctrl |= INV_MPU6050_BIT_I2C_IF_DIS;
+ ret = regmap_write(st->map, st->reg->user_ctrl,
+ st->chip_config.user_ctrl);
+ }
if (ret) {
inv_mpu6050_set_power_itg(st, false);
return ret;
@@ -81,6 +86,7 @@ static const struct spi_device_id inv_mpu_id[] = {
{"mpu9250", INV_MPU9250},
{"mpu9255", INV_MPU9255},
{"icm20608", INV_ICM20608},
+ {"icm20602", INV_ICM20602},
{}
};
diff --git a/drivers/iio/imu/st_lsm6dsx/Makefile b/drivers/iio/imu/st_lsm6dsx/Makefile
index 35919febea2a..e5f733ce6e11 100644
--- a/drivers/iio/imu/st_lsm6dsx/Makefile
+++ b/drivers/iio/imu/st_lsm6dsx/Makefile
@@ -1,4 +1,5 @@
-st_lsm6dsx-y := st_lsm6dsx_core.o st_lsm6dsx_buffer.o
+st_lsm6dsx-y := st_lsm6dsx_core.o st_lsm6dsx_buffer.o \
+ st_lsm6dsx_shub.o
obj-$(CONFIG_IIO_ST_LSM6DSX) += st_lsm6dsx.o
obj-$(CONFIG_IIO_ST_LSM6DSX_I2C) += st_lsm6dsx_i2c.o
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h
index ef73519a0fb6..d1d8d07a0714 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h
@@ -43,6 +43,24 @@ enum st_lsm6dsx_hw_id {
* ST_LSM6DSX_TAGGED_SAMPLE_SIZE)
#define ST_LSM6DSX_SHIFT_VAL(val, mask) (((val) << __ffs(mask)) & (mask))
+#define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx) \
+{ \
+ .type = chan_type, \
+ .address = addr, \
+ .modified = 1, \
+ .channel2 = mod, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = scan_idx, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_LE, \
+ }, \
+}
+
struct st_lsm6dsx_reg {
u8 addr;
u8 mask;
@@ -50,6 +68,28 @@ struct st_lsm6dsx_reg {
struct st_lsm6dsx_hw;
+struct st_lsm6dsx_odr {
+ u16 hz;
+ u8 val;
+};
+
+#define ST_LSM6DSX_ODR_LIST_SIZE 6
+struct st_lsm6dsx_odr_table_entry {
+ struct st_lsm6dsx_reg reg;
+ struct st_lsm6dsx_odr odr_avl[ST_LSM6DSX_ODR_LIST_SIZE];
+};
+
+struct st_lsm6dsx_fs {
+ u32 gain;
+ u8 val;
+};
+
+#define ST_LSM6DSX_FS_LIST_SIZE 4
+struct st_lsm6dsx_fs_table_entry {
+ struct st_lsm6dsx_reg reg;
+ struct st_lsm6dsx_fs fs_avl[ST_LSM6DSX_FS_LIST_SIZE];
+};
+
/**
* struct st_lsm6dsx_fifo_ops - ST IMU FIFO settings
* @read_fifo: Read FIFO callback.
@@ -85,6 +125,70 @@ struct st_lsm6dsx_hw_ts_settings {
};
/**
+ * struct st_lsm6dsx_shub_settings - ST IMU hw i2c controller settings
+ * @page_mux: register page mux info (addr + mask).
+ * @master_en: master config register info (addr + mask).
+ * @pullup_en: i2c controller pull-up register info (addr + mask).
+ * @aux_sens: aux sensor register info (addr + mask).
+ * @wr_once: write_once register info (addr + mask).
+ * @shub_out: sensor hub first output register info.
+ * @slv0_addr: slave0 address in secondary page.
+ * @dw_slv0_addr: slave0 write register address in secondary page.
+ * @batch_en: Enable/disable FIFO batching.
+ */
+struct st_lsm6dsx_shub_settings {
+ struct st_lsm6dsx_reg page_mux;
+ struct st_lsm6dsx_reg master_en;
+ struct st_lsm6dsx_reg pullup_en;
+ struct st_lsm6dsx_reg aux_sens;
+ struct st_lsm6dsx_reg wr_once;
+ u8 shub_out;
+ u8 slv0_addr;
+ u8 dw_slv0_addr;
+ u8 batch_en;
+};
+
+enum st_lsm6dsx_ext_sensor_id {
+ ST_LSM6DSX_ID_MAGN,
+};
+
+/**
+ * struct st_lsm6dsx_ext_dev_settings - i2c controller slave settings
+ * @i2c_addr: I2c slave address list.
+ * @wai: Wai address info.
+ * @id: external sensor id.
+ * @odr: Output data rate of the sensor [Hz].
+ * @gain: Configured sensor sensitivity.
+ * @temp_comp: Temperature compensation register info (addr + mask).
+ * @pwr_table: Power on register info (addr + mask).
+ * @off_canc: Offset cancellation register info (addr + mask).
+ * @bdu: Block data update register info (addr + mask).
+ * @out: Output register info.
+ */
+struct st_lsm6dsx_ext_dev_settings {
+ u8 i2c_addr[2];
+ struct {
+ u8 addr;
+ u8 val;
+ } wai;
+ enum st_lsm6dsx_ext_sensor_id id;
+ struct st_lsm6dsx_odr_table_entry odr_table;
+ struct st_lsm6dsx_fs_table_entry fs_table;
+ struct st_lsm6dsx_reg temp_comp;
+ struct {
+ struct st_lsm6dsx_reg reg;
+ u8 off_val;
+ u8 on_val;
+ } pwr_table;
+ struct st_lsm6dsx_reg off_canc;
+ struct st_lsm6dsx_reg bdu;
+ struct {
+ u8 addr;
+ u8 len;
+ } out;
+};
+
+/**
* struct st_lsm6dsx_settings - ST IMU sensor settings
* @wai: Sensor WhoAmI default value.
* @max_fifo_size: Sensor max fifo length in FIFO words.
@@ -93,6 +197,7 @@ struct st_lsm6dsx_hw_ts_settings {
* @batch: List of FIFO batching register info (addr + mask).
* @fifo_ops: Sensor hw FIFO parameters.
* @ts_settings: Hw timer related settings.
+ * @shub_settings: i2c controller related settings.
*/
struct st_lsm6dsx_settings {
u8 wai;
@@ -102,11 +207,15 @@ struct st_lsm6dsx_settings {
struct st_lsm6dsx_reg batch[ST_LSM6DSX_MAX_ID];
struct st_lsm6dsx_fifo_ops fifo_ops;
struct st_lsm6dsx_hw_ts_settings ts_settings;
+ struct st_lsm6dsx_shub_settings shub_settings;
};
enum st_lsm6dsx_sensor_id {
- ST_LSM6DSX_ID_ACC,
ST_LSM6DSX_ID_GYRO,
+ ST_LSM6DSX_ID_ACC,
+ ST_LSM6DSX_ID_EXT0,
+ ST_LSM6DSX_ID_EXT1,
+ ST_LSM6DSX_ID_EXT2,
ST_LSM6DSX_ID_MAX,
};
@@ -126,6 +235,7 @@ enum st_lsm6dsx_fifo_mode {
* @sip: Number of samples in a given pattern.
* @decimator: FIFO decimation factor.
* @ts_ref: Sensor timestamp reference for hw one.
+ * @ext_info: Sensor settings if it is connected to i2c controller
*/
struct st_lsm6dsx_sensor {
char name[32];
@@ -139,6 +249,11 @@ struct st_lsm6dsx_sensor {
u8 sip;
u8 decimator;
s64 ts_ref;
+
+ struct {
+ const struct st_lsm6dsx_ext_dev_settings *settings;
+ u8 addr;
+ } ext_info;
};
/**
@@ -148,6 +263,7 @@ struct st_lsm6dsx_sensor {
* @irq: Device interrupt line (I2C or SPI).
* @fifo_lock: Mutex to prevent concurrent access to the hw FIFO.
* @conf_lock: Mutex to prevent concurrent FIFO configuration update.
+ * @page_lock: Mutex to prevent concurrent memory page configuration.
* @fifo_mode: FIFO operating mode supported by the device.
* @enable_mask: Enabled sensor bitmask.
* @ts_sip: Total number of timestamp samples in a given pattern.
@@ -163,6 +279,7 @@ struct st_lsm6dsx_hw {
struct mutex fifo_lock;
struct mutex conf_lock;
+ struct mutex page_lock;
enum st_lsm6dsx_fifo_mode fifo_mode;
u8 enable_mask;
@@ -176,13 +293,15 @@ struct st_lsm6dsx_hw {
const struct st_lsm6dsx_settings *settings;
};
+static const unsigned long st_lsm6dsx_available_scan_masks[] = {0x7, 0x0};
extern const struct dev_pm_ops st_lsm6dsx_pm_ops;
int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
struct regmap *regmap);
-int st_lsm6dsx_sensor_enable(struct st_lsm6dsx_sensor *sensor);
-int st_lsm6dsx_sensor_disable(struct st_lsm6dsx_sensor *sensor);
+int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor,
+ bool enable);
int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw);
+int st_lsm6dsx_set_watermark(struct iio_dev *iio_dev, unsigned int val);
int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor,
u16 watermark);
int st_lsm6dsx_flush_fifo(struct st_lsm6dsx_hw *hw);
@@ -191,5 +310,47 @@ int st_lsm6dsx_set_fifo_mode(struct st_lsm6dsx_hw *hw,
int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw);
int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw);
int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u16 odr, u8 *val);
+int st_lsm6dsx_shub_probe(struct st_lsm6dsx_hw *hw, const char *name);
+int st_lsm6dsx_shub_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable);
+int st_lsm6dsx_set_page(struct st_lsm6dsx_hw *hw, bool enable);
+
+static inline int
+st_lsm6dsx_update_bits_locked(struct st_lsm6dsx_hw *hw, unsigned int addr,
+ unsigned int mask, unsigned int val)
+{
+ int err;
+
+ mutex_lock(&hw->page_lock);
+ err = regmap_update_bits(hw->regmap, addr, mask, val);
+ mutex_unlock(&hw->page_lock);
+
+ return err;
+}
+
+static inline int
+st_lsm6dsx_read_locked(struct st_lsm6dsx_hw *hw, unsigned int addr,
+ void *val, unsigned int len)
+{
+ int err;
+
+ mutex_lock(&hw->page_lock);
+ err = regmap_bulk_read(hw->regmap, addr, val, len);
+ mutex_unlock(&hw->page_lock);
+
+ return err;
+}
+
+static inline int
+st_lsm6dsx_write_locked(struct st_lsm6dsx_hw *hw, unsigned int addr,
+ unsigned int val)
+{
+ int err;
+
+ mutex_lock(&hw->page_lock);
+ err = regmap_write(hw->regmap, addr, val);
+ mutex_unlock(&hw->page_lock);
+
+ return err;
+}
#endif /* ST_LSM6DSX_H */
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c
index b5263fc522ca..2c0d3763405a 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c
@@ -68,6 +68,9 @@ enum st_lsm6dsx_fifo_tag {
ST_LSM6DSX_GYRO_TAG = 0x01,
ST_LSM6DSX_ACC_TAG = 0x02,
ST_LSM6DSX_TS_TAG = 0x04,
+ ST_LSM6DSX_EXT0_TAG = 0x0f,
+ ST_LSM6DSX_EXT1_TAG = 0x10,
+ ST_LSM6DSX_EXT2_TAG = 0x11,
};
static const
@@ -102,6 +105,9 @@ static void st_lsm6dsx_get_max_min_odr(struct st_lsm6dsx_hw *hw,
*max_odr = 0, *min_odr = ~0;
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
+ if (!hw->iio_devs[i])
+ continue;
+
sensor = iio_priv(hw->iio_devs[i]);
if (!(hw->enable_mask & BIT(sensor->id)))
@@ -125,6 +131,9 @@ static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw)
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
const struct st_lsm6dsx_reg *dec_reg;
+ if (!hw->iio_devs[i])
+ continue;
+
sensor = iio_priv(hw->iio_devs[i]);
/* update fifo decimators and sample in pattern */
if (hw->enable_mask & BIT(sensor->id)) {
@@ -142,8 +151,9 @@ static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw)
if (dec_reg->addr) {
int val = ST_LSM6DSX_SHIFT_VAL(data, dec_reg->mask);
- err = regmap_update_bits(hw->regmap, dec_reg->addr,
- dec_reg->mask, val);
+ err = st_lsm6dsx_update_bits_locked(hw, dec_reg->addr,
+ dec_reg->mask,
+ val);
if (err < 0)
return err;
}
@@ -162,8 +172,8 @@ static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw)
int val, ts_dec = !!hw->ts_sip;
val = ST_LSM6DSX_SHIFT_VAL(ts_dec, ts_dec_reg->mask);
- err = regmap_update_bits(hw->regmap, ts_dec_reg->addr,
- ts_dec_reg->mask, val);
+ err = st_lsm6dsx_update_bits_locked(hw, ts_dec_reg->addr,
+ ts_dec_reg->mask, val);
}
return err;
}
@@ -171,12 +181,12 @@ static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw)
int st_lsm6dsx_set_fifo_mode(struct st_lsm6dsx_hw *hw,
enum st_lsm6dsx_fifo_mode fifo_mode)
{
+ unsigned int data;
int err;
- err = regmap_update_bits(hw->regmap, ST_LSM6DSX_REG_FIFO_MODE_ADDR,
- ST_LSM6DSX_FIFO_MODE_MASK,
- FIELD_PREP(ST_LSM6DSX_FIFO_MODE_MASK,
- fifo_mode));
+ data = FIELD_PREP(ST_LSM6DSX_FIFO_MODE_MASK, fifo_mode);
+ err = st_lsm6dsx_update_bits_locked(hw, ST_LSM6DSX_REG_FIFO_MODE_ADDR,
+ ST_LSM6DSX_FIFO_MODE_MASK, data);
if (err < 0)
return err;
@@ -207,15 +217,15 @@ static int st_lsm6dsx_set_fifo_odr(struct st_lsm6dsx_sensor *sensor,
data = 0;
}
val = ST_LSM6DSX_SHIFT_VAL(data, batch_reg->mask);
- return regmap_update_bits(hw->regmap, batch_reg->addr,
- batch_reg->mask, val);
+ return st_lsm6dsx_update_bits_locked(hw, batch_reg->addr,
+ batch_reg->mask, val);
} else {
data = hw->enable_mask ? ST_LSM6DSX_MAX_FIFO_ODR_VAL : 0;
- return regmap_update_bits(hw->regmap,
- ST_LSM6DSX_REG_FIFO_MODE_ADDR,
- ST_LSM6DSX_FIFO_ODR_MASK,
- FIELD_PREP(ST_LSM6DSX_FIFO_ODR_MASK,
- data));
+ return st_lsm6dsx_update_bits_locked(hw,
+ ST_LSM6DSX_REG_FIFO_MODE_ADDR,
+ ST_LSM6DSX_FIFO_ODR_MASK,
+ FIELD_PREP(ST_LSM6DSX_FIFO_ODR_MASK,
+ data));
}
}
@@ -231,6 +241,9 @@ int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor, u16 watermark)
return 0;
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
+ if (!hw->iio_devs[i])
+ continue;
+
cur_sensor = iio_priv(hw->iio_devs[i]);
if (!(hw->enable_mask & BIT(cur_sensor->id)))
@@ -246,19 +259,23 @@ int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor, u16 watermark)
fifo_watermark = (fifo_watermark / hw->sip) * hw->sip;
fifo_watermark = fifo_watermark * hw->settings->fifo_ops.th_wl;
+ mutex_lock(&hw->page_lock);
err = regmap_read(hw->regmap, hw->settings->fifo_ops.fifo_th.addr + 1,
&data);
if (err < 0)
- return err;
+ goto out;
fifo_th_mask = hw->settings->fifo_ops.fifo_th.mask;
fifo_watermark = ((data << 8) & ~fifo_th_mask) |
(fifo_watermark & fifo_th_mask);
wdata = cpu_to_le16(fifo_watermark);
- return regmap_bulk_write(hw->regmap,
- hw->settings->fifo_ops.fifo_th.addr,
- &wdata, sizeof(wdata));
+ err = regmap_bulk_write(hw->regmap,
+ hw->settings->fifo_ops.fifo_th.addr,
+ &wdata, sizeof(wdata));
+out:
+ mutex_unlock(&hw->page_lock);
+ return err;
}
static int st_lsm6dsx_reset_hw_ts(struct st_lsm6dsx_hw *hw)
@@ -267,12 +284,15 @@ static int st_lsm6dsx_reset_hw_ts(struct st_lsm6dsx_hw *hw)
int i, err;
/* reset hw ts counter */
- err = regmap_write(hw->regmap, ST_LSM6DSX_REG_TS_RESET_ADDR,
- ST_LSM6DSX_TS_RESET_VAL);
+ err = st_lsm6dsx_write_locked(hw, ST_LSM6DSX_REG_TS_RESET_ADDR,
+ ST_LSM6DSX_TS_RESET_VAL);
if (err < 0)
return err;
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
+ if (!hw->iio_devs[i])
+ continue;
+
sensor = iio_priv(hw->iio_devs[i]);
/*
* store enable buffer timestamp as reference for
@@ -297,8 +317,8 @@ static inline int st_lsm6dsx_read_block(struct st_lsm6dsx_hw *hw, u8 addr,
while (read_len < data_len) {
word_len = min_t(unsigned int, data_len - read_len,
max_word_len);
- err = regmap_bulk_read(hw->regmap, addr, data + read_len,
- word_len);
+ err = st_lsm6dsx_read_locked(hw, addr, data + read_len,
+ word_len);
if (err < 0)
return err;
read_len += word_len;
@@ -328,9 +348,9 @@ int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw)
__le16 fifo_status;
s64 ts = 0;
- err = regmap_bulk_read(hw->regmap,
- hw->settings->fifo_ops.fifo_diff.addr,
- &fifo_status, sizeof(fifo_status));
+ err = st_lsm6dsx_read_locked(hw,
+ hw->settings->fifo_ops.fifo_diff.addr,
+ &fifo_status, sizeof(fifo_status));
if (err < 0) {
dev_err(hw->dev, "failed to read fifo status (err=%d)\n",
err);
@@ -436,6 +456,55 @@ int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw)
return read_len;
}
+static int
+st_lsm6dsx_push_tagged_data(struct st_lsm6dsx_hw *hw, u8 tag,
+ u8 *data, s64 ts)
+{
+ struct st_lsm6dsx_sensor *sensor;
+ struct iio_dev *iio_dev;
+
+ /*
+ * EXT_TAG are managed in FIFO fashion so ST_LSM6DSX_EXT0_TAG
+ * corresponds to the first enabled channel, ST_LSM6DSX_EXT1_TAG
+ * to the second one and ST_LSM6DSX_EXT2_TAG to the last enabled
+ * channel
+ */
+ switch (tag) {
+ case ST_LSM6DSX_GYRO_TAG:
+ iio_dev = hw->iio_devs[ST_LSM6DSX_ID_GYRO];
+ break;
+ case ST_LSM6DSX_ACC_TAG:
+ iio_dev = hw->iio_devs[ST_LSM6DSX_ID_ACC];
+ break;
+ case ST_LSM6DSX_EXT0_TAG:
+ if (hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT0))
+ iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT0];
+ else if (hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT1))
+ iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT1];
+ else
+ iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT2];
+ break;
+ case ST_LSM6DSX_EXT1_TAG:
+ if ((hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT0)) &&
+ (hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT1)))
+ iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT1];
+ else
+ iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT2];
+ break;
+ case ST_LSM6DSX_EXT2_TAG:
+ iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT2];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ sensor = iio_priv(iio_dev);
+ iio_push_to_buffers_with_timestamp(iio_dev, data,
+ ts + sensor->ts_ref);
+
+ return 0;
+}
+
/**
* st_lsm6dsx_read_tagged_fifo() - LSM6DSO read FIFO routine
* @hw: Pointer to instance of struct st_lsm6dsx_hw.
@@ -455,9 +524,9 @@ int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw)
__le16 fifo_status;
s64 ts = 0;
- err = regmap_bulk_read(hw->regmap,
- hw->settings->fifo_ops.fifo_diff.addr,
- &fifo_status, sizeof(fifo_status));
+ err = st_lsm6dsx_read_locked(hw,
+ hw->settings->fifo_ops.fifo_diff.addr,
+ &fifo_status, sizeof(fifo_status));
if (err < 0) {
dev_err(hw->dev, "failed to read fifo status (err=%d)\n",
err);
@@ -491,8 +560,7 @@ int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw)
ST_LSM6DSX_SAMPLE_SIZE);
tag = hw->buff[i] >> 3;
- switch (tag) {
- case ST_LSM6DSX_TS_TAG:
+ if (tag == ST_LSM6DSX_TS_TAG) {
/*
* hw timestamp is 4B long and it is stored
* in FIFO according to this schema:
@@ -509,19 +577,9 @@ int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw)
if (!reset_ts && ts >= 0xffff0000)
reset_ts = true;
ts *= ST_LSM6DSX_TS_SENSITIVITY;
- break;
- case ST_LSM6DSX_GYRO_TAG:
- iio_push_to_buffers_with_timestamp(
- hw->iio_devs[ST_LSM6DSX_ID_GYRO],
- iio_buff, gyro_sensor->ts_ref + ts);
- break;
- case ST_LSM6DSX_ACC_TAG:
- iio_push_to_buffers_with_timestamp(
- hw->iio_devs[ST_LSM6DSX_ID_ACC],
- iio_buff, acc_sensor->ts_ref + ts);
- break;
- default:
- break;
+ } else {
+ st_lsm6dsx_push_tagged_data(hw, tag, iio_buff,
+ ts);
}
}
}
@@ -562,19 +620,21 @@ static int st_lsm6dsx_update_fifo(struct iio_dev *iio_dev, bool enable)
goto out;
}
- if (enable) {
- err = st_lsm6dsx_sensor_enable(sensor);
+ if (sensor->id == ST_LSM6DSX_ID_EXT0 ||
+ sensor->id == ST_LSM6DSX_ID_EXT1 ||
+ sensor->id == ST_LSM6DSX_ID_EXT2) {
+ err = st_lsm6dsx_shub_set_enable(sensor, enable);
if (err < 0)
goto out;
} else {
- err = st_lsm6dsx_sensor_disable(sensor);
+ err = st_lsm6dsx_sensor_set_enable(sensor, enable);
if (err < 0)
goto out;
- }
- err = st_lsm6dsx_set_fifo_odr(sensor, enable);
- if (err < 0)
- goto out;
+ err = st_lsm6dsx_set_fifo_odr(sensor, enable);
+ if (err < 0)
+ goto out;
+ }
err = st_lsm6dsx_update_decimators(hw);
if (err < 0)
@@ -690,6 +750,9 @@ int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw)
}
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
+ if (!hw->iio_devs[i])
+ continue;
+
buffer = devm_iio_kfifo_allocate(hw->dev);
if (!buffer)
return -ENOMEM;
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
index 2ad3c610e4b6..12e29dda9b98 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
@@ -56,6 +56,7 @@
#define ST_LSM6DSX_REG_WHOAMI_ADDR 0x0f
#define ST_LSM6DSX_REG_RESET_ADDR 0x12
#define ST_LSM6DSX_REG_RESET_MASK BIT(0)
+#define ST_LSM6DSX_REG_BOOT_MASK BIT(7)
#define ST_LSM6DSX_REG_BDU_ADDR 0x12
#define ST_LSM6DSX_REG_BDU_MASK BIT(6)
#define ST_LSM6DSX_REG_INT2_ON_INT1_ADDR 0x13
@@ -87,17 +88,6 @@
#define ST_LSM6DSX_GYRO_FS_1000_GAIN IIO_DEGREE_TO_RAD(35000)
#define ST_LSM6DSX_GYRO_FS_2000_GAIN IIO_DEGREE_TO_RAD(70000)
-struct st_lsm6dsx_odr {
- u16 hz;
- u8 val;
-};
-
-#define ST_LSM6DSX_ODR_LIST_SIZE 6
-struct st_lsm6dsx_odr_table_entry {
- struct st_lsm6dsx_reg reg;
- struct st_lsm6dsx_odr odr_avl[ST_LSM6DSX_ODR_LIST_SIZE];
-};
-
static const struct st_lsm6dsx_odr_table_entry st_lsm6dsx_odr_table[] = {
[ST_LSM6DSX_ID_ACC] = {
.reg = {
@@ -125,17 +115,6 @@ static const struct st_lsm6dsx_odr_table_entry st_lsm6dsx_odr_table[] = {
}
};
-struct st_lsm6dsx_fs {
- u32 gain;
- u8 val;
-};
-
-#define ST_LSM6DSX_FS_LIST_SIZE 4
-struct st_lsm6dsx_fs_table_entry {
- struct st_lsm6dsx_reg reg;
- struct st_lsm6dsx_fs fs_avl[ST_LSM6DSX_FS_LIST_SIZE];
-};
-
static const struct st_lsm6dsx_fs_table_entry st_lsm6dsx_fs_table[] = {
[ST_LSM6DSX_ID_ACC] = {
.reg = {
@@ -341,27 +320,35 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = {
.mask = GENMASK(7, 6),
},
},
+ .shub_settings = {
+ .page_mux = {
+ .addr = 0x01,
+ .mask = BIT(6),
+ },
+ .master_en = {
+ .addr = 0x14,
+ .mask = BIT(2),
+ },
+ .pullup_en = {
+ .addr = 0x14,
+ .mask = BIT(3),
+ },
+ .aux_sens = {
+ .addr = 0x14,
+ .mask = GENMASK(1, 0),
+ },
+ .wr_once = {
+ .addr = 0x14,
+ .mask = BIT(6),
+ },
+ .shub_out = 0x02,
+ .slv0_addr = 0x15,
+ .dw_slv0_addr = 0x21,
+ .batch_en = BIT(3),
+ }
},
};
-#define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx) \
-{ \
- .type = chan_type, \
- .address = addr, \
- .modified = 1, \
- .channel2 = mod, \
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
- BIT(IIO_CHAN_INFO_SCALE), \
- .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
- .scan_index = scan_idx, \
- .scan_type = { \
- .sign = 's', \
- .realbits = 16, \
- .storagebits = 16, \
- .endianness = IIO_LE, \
- }, \
-}
-
static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = {
ST_LSM6DSX_CHANNEL(IIO_ACCEL, ST_LSM6DSX_REG_ACC_OUT_X_L_ADDR,
IIO_MOD_X, 0),
@@ -382,6 +369,21 @@ static const struct iio_chan_spec st_lsm6dsx_gyro_channels[] = {
IIO_CHAN_SOFT_TIMESTAMP(3),
};
+int st_lsm6dsx_set_page(struct st_lsm6dsx_hw *hw, bool enable)
+{
+ const struct st_lsm6dsx_shub_settings *hub_settings;
+ unsigned int data;
+ int err;
+
+ hub_settings = &hw->settings->shub_settings;
+ data = ST_LSM6DSX_SHIFT_VAL(enable, hub_settings->page_mux.mask);
+ err = regmap_update_bits(hw->regmap, hub_settings->page_mux.addr,
+ hub_settings->page_mux.mask, data);
+ usleep_range(100, 150);
+
+ return err;
+}
+
static int st_lsm6dsx_check_whoami(struct st_lsm6dsx_hw *hw, int id)
{
int err, i, j, data;
@@ -421,6 +423,7 @@ static int st_lsm6dsx_set_full_scale(struct st_lsm6dsx_sensor *sensor,
{
struct st_lsm6dsx_hw *hw = sensor->hw;
const struct st_lsm6dsx_reg *reg;
+ unsigned int data;
int i, err;
u8 val;
@@ -433,8 +436,8 @@ static int st_lsm6dsx_set_full_scale(struct st_lsm6dsx_sensor *sensor,
val = st_lsm6dsx_fs_table[sensor->id].fs_avl[i].val;
reg = &st_lsm6dsx_fs_table[sensor->id].reg;
- err = regmap_update_bits(hw->regmap, reg->addr, reg->mask,
- ST_LSM6DSX_SHIFT_VAL(val, reg->mask));
+ data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask);
+ err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data);
if (err < 0)
return err;
@@ -448,7 +451,11 @@ int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u16 odr, u8 *val)
int i;
for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++)
- if (st_lsm6dsx_odr_table[sensor->id].odr_avl[i].hz == odr)
+ /*
+ * ext devices can run at different odr respect to
+ * accel sensor
+ */
+ if (st_lsm6dsx_odr_table[sensor->id].odr_avl[i].hz >= odr)
break;
if (i == ST_LSM6DSX_ODR_LIST_SIZE)
@@ -459,48 +466,86 @@ int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u16 odr, u8 *val)
return 0;
}
-static int st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u16 odr)
+static u16 st_lsm6dsx_check_odr_dependency(struct st_lsm6dsx_hw *hw, u16 odr,
+ enum st_lsm6dsx_sensor_id id)
{
- struct st_lsm6dsx_hw *hw = sensor->hw;
- const struct st_lsm6dsx_reg *reg;
- int err;
- u8 val;
-
- err = st_lsm6dsx_check_odr(sensor, odr, &val);
- if (err < 0)
- return err;
-
- reg = &st_lsm6dsx_odr_table[sensor->id].reg;
- return regmap_update_bits(hw->regmap, reg->addr, reg->mask,
- ST_LSM6DSX_SHIFT_VAL(val, reg->mask));
+ struct st_lsm6dsx_sensor *ref = iio_priv(hw->iio_devs[id]);
+
+ if (odr > 0) {
+ if (hw->enable_mask & BIT(id))
+ return max_t(u16, ref->odr, odr);
+ else
+ return odr;
+ } else {
+ return (hw->enable_mask & BIT(id)) ? ref->odr : 0;
+ }
}
-int st_lsm6dsx_sensor_enable(struct st_lsm6dsx_sensor *sensor)
+static int st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u16 req_odr)
{
+ struct st_lsm6dsx_sensor *ref_sensor = sensor;
+ struct st_lsm6dsx_hw *hw = sensor->hw;
+ const struct st_lsm6dsx_reg *reg;
+ unsigned int data;
+ u8 val = 0;
int err;
- err = st_lsm6dsx_set_odr(sensor, sensor->odr);
- if (err < 0)
- return err;
+ switch (sensor->id) {
+ case ST_LSM6DSX_ID_EXT0:
+ case ST_LSM6DSX_ID_EXT1:
+ case ST_LSM6DSX_ID_EXT2:
+ case ST_LSM6DSX_ID_ACC: {
+ u16 odr;
+ int i;
+
+ /*
+ * i2c embedded controller relies on the accelerometer sensor as
+ * bus read/write trigger so we need to enable accel device
+ * at odr = max(accel_odr, ext_odr) in order to properly
+ * communicate with i2c slave devices
+ */
+ ref_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
+ for (i = ST_LSM6DSX_ID_ACC; i < ST_LSM6DSX_ID_MAX; i++) {
+ if (!hw->iio_devs[i] || i == sensor->id)
+ continue;
+
+ odr = st_lsm6dsx_check_odr_dependency(hw, req_odr, i);
+ if (odr != req_odr)
+ /* device already configured */
+ return 0;
+ }
+ break;
+ }
+ default:
+ break;
+ }
- sensor->hw->enable_mask |= BIT(sensor->id);
+ if (req_odr > 0) {
+ err = st_lsm6dsx_check_odr(ref_sensor, req_odr, &val);
+ if (err < 0)
+ return err;
+ }
- return 0;
+ reg = &st_lsm6dsx_odr_table[ref_sensor->id].reg;
+ data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask);
+ return st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data);
}
-int st_lsm6dsx_sensor_disable(struct st_lsm6dsx_sensor *sensor)
+int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor,
+ bool enable)
{
struct st_lsm6dsx_hw *hw = sensor->hw;
- const struct st_lsm6dsx_reg *reg;
+ u16 odr = enable ? sensor->odr : 0;
int err;
- reg = &st_lsm6dsx_odr_table[sensor->id].reg;
- err = regmap_update_bits(hw->regmap, reg->addr, reg->mask,
- ST_LSM6DSX_SHIFT_VAL(0, reg->mask));
+ err = st_lsm6dsx_set_odr(sensor, odr);
if (err < 0)
return err;
- sensor->hw->enable_mask &= ~BIT(sensor->id);
+ if (enable)
+ hw->enable_mask |= BIT(sensor->id);
+ else
+ hw->enable_mask &= ~BIT(sensor->id);
return 0;
}
@@ -512,18 +557,18 @@ static int st_lsm6dsx_read_oneshot(struct st_lsm6dsx_sensor *sensor,
int err, delay;
__le16 data;
- err = st_lsm6dsx_sensor_enable(sensor);
+ err = st_lsm6dsx_sensor_set_enable(sensor, true);
if (err < 0)
return err;
delay = 1000000 / sensor->odr;
usleep_range(delay, 2 * delay);
- err = regmap_bulk_read(hw->regmap, addr, &data, sizeof(data));
+ err = st_lsm6dsx_read_locked(hw, addr, &data, sizeof(data));
if (err < 0)
return err;
- st_lsm6dsx_sensor_disable(sensor);
+ st_lsm6dsx_sensor_set_enable(sensor, false);
*val = (s16)le16_to_cpu(data);
@@ -596,7 +641,7 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev,
return err;
}
-static int st_lsm6dsx_set_watermark(struct iio_dev *iio_dev, unsigned int val)
+int st_lsm6dsx_set_watermark(struct iio_dev *iio_dev, unsigned int val)
{
struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
struct st_lsm6dsx_hw *hw = sensor->hw;
@@ -692,8 +737,6 @@ static const struct iio_info st_lsm6dsx_gyro_info = {
.hwfifo_set_watermark = st_lsm6dsx_set_watermark,
};
-static const unsigned long st_lsm6dsx_available_scan_masks[] = {0x7, 0x0};
-
static int st_lsm6dsx_of_get_drdy_pin(struct st_lsm6dsx_hw *hw, int *drdy_pin)
{
struct device_node *np = hw->dev->of_node;
@@ -732,6 +775,51 @@ static int st_lsm6dsx_get_drdy_reg(struct st_lsm6dsx_hw *hw, u8 *drdy_reg)
return err;
}
+static int st_lsm6dsx_init_shub(struct st_lsm6dsx_hw *hw)
+{
+ const struct st_lsm6dsx_shub_settings *hub_settings;
+ struct device_node *np = hw->dev->of_node;
+ struct st_sensors_platform_data *pdata;
+ unsigned int data;
+ int err = 0;
+
+ hub_settings = &hw->settings->shub_settings;
+
+ pdata = (struct st_sensors_platform_data *)hw->dev->platform_data;
+ if ((np && of_property_read_bool(np, "st,pullups")) ||
+ (pdata && pdata->pullups)) {
+ err = st_lsm6dsx_set_page(hw, true);
+ if (err < 0)
+ return err;
+
+ data = ST_LSM6DSX_SHIFT_VAL(1, hub_settings->pullup_en.mask);
+ err = regmap_update_bits(hw->regmap,
+ hub_settings->pullup_en.addr,
+ hub_settings->pullup_en.mask, data);
+
+ st_lsm6dsx_set_page(hw, false);
+
+ if (err < 0)
+ return err;
+ }
+
+ if (hub_settings->aux_sens.addr) {
+ /* configure aux sensors */
+ err = st_lsm6dsx_set_page(hw, true);
+ if (err < 0)
+ return err;
+
+ data = ST_LSM6DSX_SHIFT_VAL(3, hub_settings->aux_sens.mask);
+ err = regmap_update_bits(hw->regmap,
+ hub_settings->aux_sens.addr,
+ hub_settings->aux_sens.mask, data);
+
+ st_lsm6dsx_set_page(hw, false);
+ }
+
+ return err;
+}
+
static int st_lsm6dsx_init_hw_timer(struct st_lsm6dsx_hw *hw)
{
const struct st_lsm6dsx_hw_ts_settings *ts_settings;
@@ -775,12 +863,23 @@ static int st_lsm6dsx_init_device(struct st_lsm6dsx_hw *hw)
u8 drdy_int_reg;
int err;
- err = regmap_write(hw->regmap, ST_LSM6DSX_REG_RESET_ADDR,
- ST_LSM6DSX_REG_RESET_MASK);
+ /* device sw reset */
+ err = regmap_update_bits(hw->regmap, ST_LSM6DSX_REG_RESET_ADDR,
+ ST_LSM6DSX_REG_RESET_MASK,
+ FIELD_PREP(ST_LSM6DSX_REG_RESET_MASK, 1));
+ if (err < 0)
+ return err;
+
+ msleep(50);
+
+ /* reload trimming parameter */
+ err = regmap_update_bits(hw->regmap, ST_LSM6DSX_REG_RESET_ADDR,
+ ST_LSM6DSX_REG_BOOT_MASK,
+ FIELD_PREP(ST_LSM6DSX_REG_BOOT_MASK, 1));
if (err < 0)
return err;
- msleep(200);
+ msleep(50);
/* enable Block Data Update */
err = regmap_update_bits(hw->regmap, ST_LSM6DSX_REG_BDU_ADDR,
@@ -801,6 +900,10 @@ static int st_lsm6dsx_init_device(struct st_lsm6dsx_hw *hw)
if (err < 0)
return err;
+ err = st_lsm6dsx_init_shub(hw);
+ if (err < 0)
+ return err;
+
return st_lsm6dsx_init_hw_timer(hw);
}
@@ -854,6 +957,7 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw,
int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
struct regmap *regmap)
{
+ const struct st_lsm6dsx_shub_settings *hub_settings;
struct st_lsm6dsx_hw *hw;
int i, err;
@@ -865,6 +969,7 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
mutex_init(&hw->fifo_lock);
mutex_init(&hw->conf_lock);
+ mutex_init(&hw->page_lock);
hw->buff = devm_kzalloc(dev, ST_LSM6DSX_BUFF_SIZE, GFP_KERNEL);
if (!hw->buff)
@@ -878,7 +983,7 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
if (err < 0)
return err;
- for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
+ for (i = 0; i < ST_LSM6DSX_ID_EXT0; i++) {
hw->iio_devs[i] = st_lsm6dsx_alloc_iiodev(hw, i, name);
if (!hw->iio_devs[i])
return -ENOMEM;
@@ -888,6 +993,13 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
if (err < 0)
return err;
+ hub_settings = &hw->settings->shub_settings;
+ if (hub_settings->master_en.addr) {
+ err = st_lsm6dsx_shub_probe(hw, name);
+ if (err < 0)
+ return err;
+ }
+
if (hw->irq > 0) {
err = st_lsm6dsx_fifo_setup(hw);
if (err < 0)
@@ -895,6 +1007,9 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
}
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
+ if (!hw->iio_devs[i])
+ continue;
+
err = devm_iio_device_register(hw->dev, hw->iio_devs[i]);
if (err)
return err;
@@ -909,16 +1024,21 @@ static int __maybe_unused st_lsm6dsx_suspend(struct device *dev)
struct st_lsm6dsx_hw *hw = dev_get_drvdata(dev);
struct st_lsm6dsx_sensor *sensor;
const struct st_lsm6dsx_reg *reg;
+ unsigned int data;
int i, err = 0;
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
+ if (!hw->iio_devs[i])
+ continue;
+
sensor = iio_priv(hw->iio_devs[i]);
if (!(hw->enable_mask & BIT(sensor->id)))
continue;
reg = &st_lsm6dsx_odr_table[sensor->id].reg;
- err = regmap_update_bits(hw->regmap, reg->addr, reg->mask,
- ST_LSM6DSX_SHIFT_VAL(0, reg->mask));
+ data = ST_LSM6DSX_SHIFT_VAL(0, reg->mask);
+ err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask,
+ data);
if (err < 0)
return err;
}
@@ -936,6 +1056,9 @@ static int __maybe_unused st_lsm6dsx_resume(struct device *dev)
int i, err = 0;
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
+ if (!hw->iio_devs[i])
+ continue;
+
sensor = iio_priv(hw->iio_devs[i]);
if (!(hw->enable_mask & BIT(sensor->id)))
continue;
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c
new file mode 100644
index 000000000000..66fbcd94642d
--- /dev/null
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c
@@ -0,0 +1,777 @@
+/*
+ * STMicroelectronics st_lsm6dsx i2c controller driver
+ *
+ * i2c controller embedded in lsm6dx series can connect up to four
+ * slave devices using accelerometer sensor as trigger for i2c
+ * read/write operations. Current implementation relies on SLV0 channel
+ * for slave configuration and SLV{1,2,3} to read data and push them into
+ * the hw FIFO
+ *
+ * Copyright (C) 2018 Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/bitfield.h>
+
+#include "st_lsm6dsx.h"
+
+#define ST_LSM6DSX_MAX_SLV_NUM 3
+#define ST_LSM6DSX_SLV_ADDR(n, base) ((base) + (n) * 3)
+#define ST_LSM6DSX_SLV_SUB_ADDR(n, base) ((base) + 1 + (n) * 3)
+#define ST_LSM6DSX_SLV_CONFIG(n, base) ((base) + 2 + (n) * 3)
+
+#define ST_LS6DSX_READ_OP_MASK GENMASK(2, 0)
+
+static const struct st_lsm6dsx_ext_dev_settings st_lsm6dsx_ext_dev_table[] = {
+ /* LIS2MDL */
+ {
+ .i2c_addr = { 0x1e },
+ .wai = {
+ .addr = 0x4f,
+ .val = 0x40,
+ },
+ .id = ST_LSM6DSX_ID_MAGN,
+ .odr_table = {
+ .reg = {
+ .addr = 0x60,
+ .mask = GENMASK(3, 2),
+ },
+ .odr_avl[0] = { 10, 0x0 },
+ .odr_avl[1] = { 20, 0x1 },
+ .odr_avl[2] = { 50, 0x2 },
+ .odr_avl[3] = { 100, 0x3 },
+ },
+ .fs_table = {
+ .fs_avl[0] = {
+ .gain = 1500,
+ .val = 0x0,
+ }, /* 1500 uG/LSB */
+ },
+ .temp_comp = {
+ .addr = 0x60,
+ .mask = BIT(7),
+ },
+ .pwr_table = {
+ .reg = {
+ .addr = 0x60,
+ .mask = GENMASK(1, 0),
+ },
+ .off_val = 0x2,
+ .on_val = 0x0,
+ },
+ .off_canc = {
+ .addr = 0x61,
+ .mask = BIT(1),
+ },
+ .bdu = {
+ .addr = 0x62,
+ .mask = BIT(4),
+ },
+ .out = {
+ .addr = 0x68,
+ .len = 6,
+ },
+ },
+};
+
+static void st_lsm6dsx_shub_wait_complete(struct st_lsm6dsx_hw *hw)
+{
+ struct st_lsm6dsx_sensor *sensor;
+
+ sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
+ msleep((2000U / sensor->odr) + 1);
+}
+
+/**
+ * st_lsm6dsx_shub_read_reg - read i2c controller register
+ *
+ * Read st_lsm6dsx i2c controller register
+ */
+static int st_lsm6dsx_shub_read_reg(struct st_lsm6dsx_hw *hw, u8 addr,
+ u8 *data, int len)
+{
+ int err;
+
+ mutex_lock(&hw->page_lock);
+
+ err = st_lsm6dsx_set_page(hw, true);
+ if (err < 0)
+ goto out;
+
+ err = regmap_bulk_read(hw->regmap, addr, data, len);
+
+ st_lsm6dsx_set_page(hw, false);
+out:
+ mutex_unlock(&hw->page_lock);
+
+ return err;
+}
+
+/**
+ * st_lsm6dsx_shub_write_reg - write i2c controller register
+ *
+ * Write st_lsm6dsx i2c controller register
+ */
+static int st_lsm6dsx_shub_write_reg(struct st_lsm6dsx_hw *hw, u8 addr,
+ u8 *data, int len)
+{
+ int err;
+
+ mutex_lock(&hw->page_lock);
+ err = st_lsm6dsx_set_page(hw, true);
+ if (err < 0)
+ goto out;
+
+ err = regmap_bulk_write(hw->regmap, addr, data, len);
+
+ st_lsm6dsx_set_page(hw, false);
+out:
+ mutex_unlock(&hw->page_lock);
+
+ return err;
+}
+
+static int
+st_lsm6dsx_shub_write_reg_with_mask(struct st_lsm6dsx_hw *hw, u8 addr,
+ u8 mask, u8 val)
+{
+ int err;
+
+ mutex_lock(&hw->page_lock);
+ err = st_lsm6dsx_set_page(hw, true);
+ if (err < 0)
+ goto out;
+
+ err = regmap_update_bits(hw->regmap, addr, mask, val);
+
+ st_lsm6dsx_set_page(hw, false);
+out:
+ mutex_unlock(&hw->page_lock);
+
+ return err;
+}
+
+static int st_lsm6dsx_shub_master_enable(struct st_lsm6dsx_sensor *sensor,
+ bool enable)
+{
+ const struct st_lsm6dsx_shub_settings *hub_settings;
+ struct st_lsm6dsx_hw *hw = sensor->hw;
+ unsigned int data;
+ int err;
+
+ /* enable acc sensor as trigger */
+ err = st_lsm6dsx_sensor_set_enable(sensor, enable);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&hw->page_lock);
+
+ hub_settings = &hw->settings->shub_settings;
+ err = st_lsm6dsx_set_page(hw, true);
+ if (err < 0)
+ goto out;
+
+ data = ST_LSM6DSX_SHIFT_VAL(enable, hub_settings->master_en.mask);
+ err = regmap_update_bits(hw->regmap, hub_settings->master_en.addr,
+ hub_settings->master_en.mask, data);
+
+ st_lsm6dsx_set_page(hw, false);
+out:
+ mutex_unlock(&hw->page_lock);
+
+ return err;
+}
+
+/**
+ * st_lsm6dsx_shub_read - read data from slave device register
+ *
+ * Read data from slave device register. SLV0 is used for
+ * one-shot read operation
+ */
+static int
+st_lsm6dsx_shub_read(struct st_lsm6dsx_sensor *sensor, u8 addr,
+ u8 *data, int len)
+{
+ const struct st_lsm6dsx_shub_settings *hub_settings;
+ struct st_lsm6dsx_hw *hw = sensor->hw;
+ u8 config[3], slv_addr;
+ int err;
+
+ hub_settings = &hw->settings->shub_settings;
+ slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
+
+ config[0] = (sensor->ext_info.addr << 1) | 1;
+ config[1] = addr;
+ config[2] = len & ST_LS6DSX_READ_OP_MASK;
+
+ err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+ sizeof(config));
+ if (err < 0)
+ return err;
+
+ err = st_lsm6dsx_shub_master_enable(sensor, true);
+ if (err < 0)
+ return err;
+
+ st_lsm6dsx_shub_wait_complete(hw);
+
+ err = st_lsm6dsx_shub_read_reg(hw, hub_settings->shub_out, data,
+ len & ST_LS6DSX_READ_OP_MASK);
+
+ st_lsm6dsx_shub_master_enable(sensor, false);
+
+ memset(config, 0, sizeof(config));
+ return st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+ sizeof(config));
+}
+
+/**
+ * st_lsm6dsx_shub_write - write data to slave device register
+ *
+ * Write data from slave device register. SLV0 is used for
+ * one-shot write operation
+ */
+static int
+st_lsm6dsx_shub_write(struct st_lsm6dsx_sensor *sensor, u8 addr,
+ u8 *data, int len)
+{
+ const struct st_lsm6dsx_shub_settings *hub_settings;
+ struct st_lsm6dsx_hw *hw = sensor->hw;
+ u8 config[2], slv_addr;
+ int err, i;
+
+ hub_settings = &hw->settings->shub_settings;
+ if (hub_settings->wr_once.addr) {
+ unsigned int data;
+
+ data = ST_LSM6DSX_SHIFT_VAL(1, hub_settings->wr_once.mask);
+ err = st_lsm6dsx_shub_write_reg_with_mask(hw,
+ hub_settings->wr_once.addr,
+ hub_settings->wr_once.mask,
+ data);
+ if (err < 0)
+ return err;
+ }
+
+ slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
+ config[0] = sensor->ext_info.addr << 1;
+ for (i = 0 ; i < len; i++) {
+ config[1] = addr + i;
+
+ err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+ sizeof(config));
+ if (err < 0)
+ return err;
+
+ err = st_lsm6dsx_shub_write_reg(hw, hub_settings->dw_slv0_addr,
+ &data[i], 1);
+ if (err < 0)
+ return err;
+
+ err = st_lsm6dsx_shub_master_enable(sensor, true);
+ if (err < 0)
+ return err;
+
+ st_lsm6dsx_shub_wait_complete(hw);
+
+ st_lsm6dsx_shub_master_enable(sensor, false);
+ }
+
+ memset(config, 0, sizeof(config));
+ return st_lsm6dsx_shub_write_reg(hw, slv_addr, config, sizeof(config));
+}
+
+static int
+st_lsm6dsx_shub_write_with_mask(struct st_lsm6dsx_sensor *sensor,
+ u8 addr, u8 mask, u8 val)
+{
+ int err;
+ u8 data;
+
+ err = st_lsm6dsx_shub_read(sensor, addr, &data, sizeof(data));
+ if (err < 0)
+ return err;
+
+ data = ((data & ~mask) | (val << __ffs(mask) & mask));
+
+ return st_lsm6dsx_shub_write(sensor, addr, &data, sizeof(data));
+}
+
+static int
+st_lsm6dsx_shub_get_odr_val(struct st_lsm6dsx_sensor *sensor,
+ u16 odr, u16 *val)
+{
+ const struct st_lsm6dsx_ext_dev_settings *settings;
+ int i;
+
+ settings = sensor->ext_info.settings;
+ for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++)
+ if (settings->odr_table.odr_avl[i].hz == odr)
+ break;
+
+ if (i == ST_LSM6DSX_ODR_LIST_SIZE)
+ return -EINVAL;
+
+ *val = settings->odr_table.odr_avl[i].val;
+ return 0;
+}
+
+static int
+st_lsm6dsx_shub_set_odr(struct st_lsm6dsx_sensor *sensor, u16 odr)
+{
+ const struct st_lsm6dsx_ext_dev_settings *settings;
+ u16 val;
+ int err;
+
+ err = st_lsm6dsx_shub_get_odr_val(sensor, odr, &val);
+ if (err < 0)
+ return err;
+
+ settings = sensor->ext_info.settings;
+ return st_lsm6dsx_shub_write_with_mask(sensor,
+ settings->odr_table.reg.addr,
+ settings->odr_table.reg.mask,
+ val);
+}
+
+/* use SLV{1,2,3} for FIFO read operations */
+static int
+st_lsm6dsx_shub_config_channels(struct st_lsm6dsx_sensor *sensor,
+ bool enable)
+{
+ const struct st_lsm6dsx_shub_settings *hub_settings;
+ const struct st_lsm6dsx_ext_dev_settings *settings;
+ u8 config[9] = {}, enable_mask, slv_addr;
+ struct st_lsm6dsx_hw *hw = sensor->hw;
+ struct st_lsm6dsx_sensor *cur_sensor;
+ int i, j = 0;
+
+ hub_settings = &hw->settings->shub_settings;
+ if (enable)
+ enable_mask = hw->enable_mask | BIT(sensor->id);
+ else
+ enable_mask = hw->enable_mask & ~BIT(sensor->id);
+
+ for (i = ST_LSM6DSX_ID_EXT0; i <= ST_LSM6DSX_ID_EXT2; i++) {
+ if (!hw->iio_devs[i])
+ continue;
+
+ cur_sensor = iio_priv(hw->iio_devs[i]);
+ if (!(enable_mask & BIT(cur_sensor->id)))
+ continue;
+
+ settings = cur_sensor->ext_info.settings;
+ config[j] = (sensor->ext_info.addr << 1) | 1;
+ config[j + 1] = settings->out.addr;
+ config[j + 2] = (settings->out.len & ST_LS6DSX_READ_OP_MASK) |
+ hub_settings->batch_en;
+ j += 3;
+ }
+
+ slv_addr = ST_LSM6DSX_SLV_ADDR(1, hub_settings->slv0_addr);
+ return st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+ sizeof(config));
+}
+
+int st_lsm6dsx_shub_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable)
+{
+ const struct st_lsm6dsx_ext_dev_settings *settings;
+ int err;
+
+ err = st_lsm6dsx_shub_config_channels(sensor, enable);
+ if (err < 0)
+ return err;
+
+ settings = sensor->ext_info.settings;
+ if (enable) {
+ err = st_lsm6dsx_shub_set_odr(sensor, sensor->odr);
+ if (err < 0)
+ return err;
+ } else {
+ err = st_lsm6dsx_shub_write_with_mask(sensor,
+ settings->odr_table.reg.addr,
+ settings->odr_table.reg.mask, 0);
+ if (err < 0)
+ return err;
+ }
+
+ if (settings->pwr_table.reg.addr) {
+ u8 val;
+
+ val = enable ? settings->pwr_table.on_val
+ : settings->pwr_table.off_val;
+ err = st_lsm6dsx_shub_write_with_mask(sensor,
+ settings->pwr_table.reg.addr,
+ settings->pwr_table.reg.mask, val);
+ if (err < 0)
+ return err;
+ }
+
+ return st_lsm6dsx_shub_master_enable(sensor, enable);
+}
+
+static int
+st_lsm6dsx_shub_read_oneshot(struct st_lsm6dsx_sensor *sensor,
+ struct iio_chan_spec const *ch,
+ int *val)
+{
+ int err, delay, len;
+ u8 data[4];
+
+ err = st_lsm6dsx_shub_set_enable(sensor, true);
+ if (err < 0)
+ return err;
+
+ delay = 1000000 / sensor->odr;
+ usleep_range(delay, 2 * delay);
+
+ len = min_t(int, sizeof(data), ch->scan_type.realbits >> 3);
+ err = st_lsm6dsx_shub_read(sensor, ch->address, data, len);
+
+ st_lsm6dsx_shub_set_enable(sensor, false);
+
+ if (err < 0)
+ return err;
+
+ switch (len) {
+ case 2:
+ *val = (s16)le16_to_cpu(*((__le16 *)data));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return IIO_VAL_INT;
+}
+
+static int
+st_lsm6dsx_shub_read_raw(struct iio_dev *iio_dev,
+ struct iio_chan_spec const *ch,
+ int *val, int *val2, long mask)
+{
+ struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_device_claim_direct_mode(iio_dev);
+ if (ret)
+ break;
+
+ ret = st_lsm6dsx_shub_read_oneshot(sensor, ch, val);
+ iio_device_release_direct_mode(iio_dev);
+ break;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = sensor->odr;
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = sensor->gain;
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int
+st_lsm6dsx_shub_write_raw(struct iio_dev *iio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
+ int err;
+
+ err = iio_device_claim_direct_mode(iio_dev);
+ if (err)
+ return err;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ u16 data;
+
+ err = st_lsm6dsx_shub_get_odr_val(sensor, val, &data);
+ if (!err)
+ sensor->odr = val;
+ break;
+ }
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ iio_device_release_direct_mode(iio_dev);
+
+ return err;
+}
+
+static ssize_t
+st_lsm6dsx_shub_sampling_freq_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
+ const struct st_lsm6dsx_ext_dev_settings *settings;
+ int i, len = 0;
+
+ settings = sensor->ext_info.settings;
+ for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++) {
+ u16 val = settings->odr_table.odr_avl[i].hz;
+
+ if (val > 0)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d ",
+ val);
+ }
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static ssize_t st_lsm6dsx_shub_scale_avail(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
+ const struct st_lsm6dsx_ext_dev_settings *settings;
+ int i, len = 0;
+
+ settings = sensor->ext_info.settings;
+ for (i = 0; i < ST_LSM6DSX_FS_LIST_SIZE; i++) {
+ u16 val = settings->fs_table.fs_avl[i].gain;
+
+ if (val > 0)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ",
+ val);
+ }
+ buf[len - 1] = '\n';
+
+ return len;
+}
+
+static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(st_lsm6dsx_shub_sampling_freq_avail);
+static IIO_DEVICE_ATTR(in_scale_available, 0444,
+ st_lsm6dsx_shub_scale_avail, NULL, 0);
+static struct attribute *st_lsm6dsx_ext_attributes[] = {
+ &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+ &iio_dev_attr_in_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group st_lsm6dsx_ext_attribute_group = {
+ .attrs = st_lsm6dsx_ext_attributes,
+};
+
+static const struct iio_info st_lsm6dsx_ext_info = {
+ .attrs = &st_lsm6dsx_ext_attribute_group,
+ .read_raw = st_lsm6dsx_shub_read_raw,
+ .write_raw = st_lsm6dsx_shub_write_raw,
+ .hwfifo_set_watermark = st_lsm6dsx_set_watermark,
+};
+
+static struct iio_dev *
+st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw,
+ enum st_lsm6dsx_sensor_id id,
+ const struct st_lsm6dsx_ext_dev_settings *info,
+ u8 i2c_addr, const char *name)
+{
+ struct iio_chan_spec *ext_channels;
+ struct st_lsm6dsx_sensor *sensor;
+ struct iio_dev *iio_dev;
+
+ iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor));
+ if (!iio_dev)
+ return NULL;
+
+ iio_dev->modes = INDIO_DIRECT_MODE;
+ iio_dev->dev.parent = hw->dev;
+ iio_dev->info = &st_lsm6dsx_ext_info;
+
+ sensor = iio_priv(iio_dev);
+ sensor->id = id;
+ sensor->hw = hw;
+ sensor->odr = info->odr_table.odr_avl[0].hz;
+ sensor->gain = info->fs_table.fs_avl[0].gain;
+ sensor->ext_info.settings = info;
+ sensor->ext_info.addr = i2c_addr;
+ sensor->watermark = 1;
+
+ switch (info->id) {
+ case ST_LSM6DSX_ID_MAGN: {
+ const struct iio_chan_spec magn_channels[] = {
+ ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr,
+ IIO_MOD_X, 0),
+ ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr + 2,
+ IIO_MOD_Y, 1),
+ ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr + 4,
+ IIO_MOD_Z, 2),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+ };
+
+ ext_channels = devm_kzalloc(hw->dev, sizeof(magn_channels),
+ GFP_KERNEL);
+ if (!ext_channels)
+ return NULL;
+
+ memcpy(ext_channels, magn_channels, sizeof(magn_channels));
+ iio_dev->available_scan_masks = st_lsm6dsx_available_scan_masks;
+ iio_dev->channels = ext_channels;
+ iio_dev->num_channels = ARRAY_SIZE(magn_channels);
+
+ scnprintf(sensor->name, sizeof(sensor->name), "%s_magn",
+ name);
+ break;
+ }
+ default:
+ return NULL;
+ }
+ iio_dev->name = sensor->name;
+
+ return iio_dev;
+}
+
+static int st_lsm6dsx_shub_init_device(struct st_lsm6dsx_sensor *sensor)
+{
+ const struct st_lsm6dsx_ext_dev_settings *settings;
+ int err;
+
+ settings = sensor->ext_info.settings;
+ if (settings->bdu.addr) {
+ err = st_lsm6dsx_shub_write_with_mask(sensor,
+ settings->bdu.addr,
+ settings->bdu.mask, 1);
+ if (err < 0)
+ return err;
+ }
+
+ if (settings->temp_comp.addr) {
+ err = st_lsm6dsx_shub_write_with_mask(sensor,
+ settings->temp_comp.addr,
+ settings->temp_comp.mask, 1);
+ if (err < 0)
+ return err;
+ }
+
+ if (settings->off_canc.addr) {
+ err = st_lsm6dsx_shub_write_with_mask(sensor,
+ settings->off_canc.addr,
+ settings->off_canc.mask, 1);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int
+st_lsm6dsx_shub_check_wai(struct st_lsm6dsx_hw *hw, u8 *i2c_addr,
+ const struct st_lsm6dsx_ext_dev_settings *settings)
+{
+ const struct st_lsm6dsx_shub_settings *hub_settings;
+ struct st_lsm6dsx_sensor *sensor;
+ u8 config[3], data, slv_addr;
+ bool found = false;
+ int i, err;
+
+ hub_settings = &hw->settings->shub_settings;
+ slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
+ sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
+
+ for (i = 0; i < ARRAY_SIZE(settings->i2c_addr); i++) {
+ if (!settings->i2c_addr[i])
+ continue;
+
+ /* read wai slave register */
+ config[0] = (settings->i2c_addr[i] << 1) | 0x1;
+ config[1] = settings->wai.addr;
+ config[2] = 0x1;
+
+ err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+ sizeof(config));
+ if (err < 0)
+ return err;
+
+ err = st_lsm6dsx_shub_master_enable(sensor, true);
+ if (err < 0)
+ return err;
+
+ st_lsm6dsx_shub_wait_complete(hw);
+
+ err = st_lsm6dsx_shub_read_reg(hw,
+ hub_settings->shub_out,
+ &data, sizeof(data));
+
+ st_lsm6dsx_shub_master_enable(sensor, false);
+
+ if (err < 0)
+ return err;
+
+ if (data != settings->wai.val)
+ continue;
+
+ *i2c_addr = settings->i2c_addr[i];
+ found = true;
+ break;
+ }
+
+ /* reset SLV0 channel */
+ memset(config, 0, sizeof(config));
+ err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+ sizeof(config));
+ if (err < 0)
+ return err;
+
+ return found ? 0 : -ENODEV;
+}
+
+int st_lsm6dsx_shub_probe(struct st_lsm6dsx_hw *hw, const char *name)
+{
+ enum st_lsm6dsx_sensor_id id = ST_LSM6DSX_ID_EXT0;
+ struct st_lsm6dsx_sensor *sensor;
+ int err, i, num_ext_dev = 0;
+ u8 i2c_addr = 0;
+
+ for (i = 0; i < ARRAY_SIZE(st_lsm6dsx_ext_dev_table); i++) {
+ err = st_lsm6dsx_shub_check_wai(hw, &i2c_addr,
+ &st_lsm6dsx_ext_dev_table[i]);
+ if (err == -ENODEV)
+ continue;
+ else if (err < 0)
+ return err;
+
+ hw->iio_devs[id] = st_lsm6dsx_shub_alloc_iiodev(hw, id,
+ &st_lsm6dsx_ext_dev_table[i],
+ i2c_addr, name);
+ if (!hw->iio_devs[id])
+ return -ENOMEM;
+
+ sensor = iio_priv(hw->iio_devs[id]);
+ err = st_lsm6dsx_shub_init_device(sensor);
+ if (err < 0)
+ return err;
+
+ if (++num_ext_dev >= ST_LSM6DSX_MAX_SLV_NUM)
+ break;
+ id++;
+ }
+
+ return 0;
+}
diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c
index cd5bfe39591b..dadd921a4a30 100644
--- a/drivers/iio/industrialio-buffer.c
+++ b/drivers/iio/industrialio-buffer.c
@@ -320,9 +320,8 @@ static int iio_scan_mask_set(struct iio_dev *indio_dev,
const unsigned long *mask;
unsigned long *trialmask;
- trialmask = kmalloc_array(BITS_TO_LONGS(indio_dev->masklength),
- sizeof(*trialmask),
- GFP_KERNEL);
+ trialmask = kcalloc(BITS_TO_LONGS(indio_dev->masklength),
+ sizeof(*trialmask), GFP_KERNEL);
if (trialmask == NULL)
return -ENOMEM;
if (!indio_dev->masklength) {
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index a062cfddc5af..9c4d92115504 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -87,6 +87,7 @@ static const char * const iio_chan_type_name_spec[] = {
[IIO_GRAVITY] = "gravity",
[IIO_POSITIONRELATIVE] = "positionrelative",
[IIO_PHASE] = "phase",
+ [IIO_MASSCONCENTRATION] = "massconcentration",
};
static const char * const iio_modifier_names[] = {
@@ -127,6 +128,10 @@ static const char * const iio_modifier_names[] = {
[IIO_MOD_Q] = "q",
[IIO_MOD_CO2] = "co2",
[IIO_MOD_VOC] = "voc",
+ [IIO_MOD_PM1] = "pm1",
+ [IIO_MOD_PM2P5] = "pm2p5",
+ [IIO_MOD_PM4] = "pm4",
+ [IIO_MOD_PM10] = "pm10",
};
/* relies on pairs of these shared then separate */
@@ -1671,6 +1676,9 @@ int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod)
if (ret < 0)
return ret;
+ if (!indio_dev->info)
+ return -EINVAL;
+
/* configure elements for the chrdev */
indio_dev->dev.devt = MKDEV(MAJOR(iio_devt), indio_dev->id);
@@ -1735,10 +1743,10 @@ EXPORT_SYMBOL(__iio_device_register);
**/
void iio_device_unregister(struct iio_dev *indio_dev)
{
- mutex_lock(&indio_dev->info_exist_lock);
-
cdev_device_del(&indio_dev->chrdev, &indio_dev->dev);
+ mutex_lock(&indio_dev->info_exist_lock);
+
iio_device_unregister_debugfs(indio_dev);
iio_disable_all_buffers(indio_dev);
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index d66ea754ffff..5190eacfeb0a 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -299,6 +299,16 @@ config MAX44000
To compile this driver as a module, choose M here:
the module will be called max44000.
+config MAX44009
+ tristate "MAX44009 Ambient Light Sensor"
+ depends on I2C
+ help
+ Say Y here if you want to build support for Maxim Integrated's
+ MAX44009 ambient light sensor device.
+
+ To compile this driver as a module, choose M here:
+ the module will be called max44009.
+
config OPT3001
tristate "Texas Instruments OPT3001 Light Sensor"
depends on I2C
@@ -460,6 +470,19 @@ config VCNL4000
To compile this driver as a module, choose M here: the
module will be called vcnl4000.
+config VCNL4035
+ tristate "VCNL4035 combined ALS and proximity sensor"
+ select IIO_TRIGGERED_BUFFER
+ select REGMAP_I2C
+ depends on I2C
+ help
+ Say Y here if you want to build a driver for the Vishay VCNL4035,
+ combined ambient light (ALS) and proximity sensor. Currently only ALS
+ function is available.
+
+ To compile this driver as a module, choose M here: the
+ module will be called vcnl4035.
+
config VEML6070
tristate "VEML6070 UV A light sensor"
depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 86337b114bc4..e40794fbb435 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
obj-$(CONFIG_LTR501) += ltr501.o
obj-$(CONFIG_LV0104CS) += lv0104cs.o
obj-$(CONFIG_MAX44000) += max44000.o
+obj-$(CONFIG_MAX44009) += max44009.o
obj-$(CONFIG_OPT3001) += opt3001.o
obj-$(CONFIG_PA12203001) += pa12203001.o
obj-$(CONFIG_RPR0521) += rpr0521.o
@@ -45,6 +46,7 @@ obj-$(CONFIG_TSL2772) += tsl2772.o
obj-$(CONFIG_TSL4531) += tsl4531.o
obj-$(CONFIG_US5182D) += us5182d.o
obj-$(CONFIG_VCNL4000) += vcnl4000.o
+obj-$(CONFIG_VCNL4035) += vcnl4035.o
obj-$(CONFIG_VEML6070) += veml6070.o
obj-$(CONFIG_VL6180) += vl6180.o
obj-$(CONFIG_ZOPT2201) += zopt2201.o
diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c
index b45400f8fef4..846df4dce48c 100644
--- a/drivers/iio/light/isl29018.c
+++ b/drivers/iio/light/isl29018.c
@@ -23,6 +23,7 @@
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
@@ -95,6 +96,7 @@ struct isl29018_chip {
struct isl29018_scale scale;
int prox_scheme;
bool suspended;
+ struct regulator *vcc_reg;
};
static int isl29018_set_integration_time(struct isl29018_chip *chip,
@@ -708,6 +710,16 @@ static const char *isl29018_match_acpi_device(struct device *dev, int *data)
return dev_name(dev);
}
+static void isl29018_disable_regulator_action(void *_data)
+{
+ struct isl29018_chip *chip = _data;
+ int err;
+
+ err = regulator_disable(chip->vcc_reg);
+ if (err)
+ pr_err("failed to disable isl29018's VCC regulator!\n");
+}
+
static int isl29018_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -742,6 +754,27 @@ static int isl29018_probe(struct i2c_client *client,
chip->scale = isl29018_scales[chip->int_time][0];
chip->suspended = false;
+ chip->vcc_reg = devm_regulator_get(&client->dev, "vcc");
+ if (IS_ERR(chip->vcc_reg)) {
+ err = PTR_ERR(chip->vcc_reg);
+ if (err != -EPROBE_DEFER)
+ dev_err(&client->dev, "failed to get VCC regulator!\n");
+ return err;
+ }
+
+ err = regulator_enable(chip->vcc_reg);
+ if (err) {
+ dev_err(&client->dev, "failed to enable VCC regulator!\n");
+ return err;
+ }
+
+ err = devm_add_action_or_reset(&client->dev, isl29018_disable_regulator_action,
+ chip);
+ if (err) {
+ dev_err(&client->dev, "failed to setup regulator cleanup action!\n");
+ return err;
+ }
+
chip->regmap = devm_regmap_init_i2c(client,
isl29018_chip_info_tbl[dev_id].regmap_cfg);
if (IS_ERR(chip->regmap)) {
@@ -768,6 +801,7 @@ static int isl29018_probe(struct i2c_client *client,
static int isl29018_suspend(struct device *dev)
{
struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev));
+ int ret;
mutex_lock(&chip->lock);
@@ -777,10 +811,13 @@ static int isl29018_suspend(struct device *dev)
* So we do not have much to do here.
*/
chip->suspended = true;
+ ret = regulator_disable(chip->vcc_reg);
+ if (ret)
+ dev_err(dev, "failed to disable VCC regulator\n");
mutex_unlock(&chip->lock);
- return 0;
+ return ret;
}
static int isl29018_resume(struct device *dev)
@@ -790,6 +827,13 @@ static int isl29018_resume(struct device *dev)
mutex_lock(&chip->lock);
+ err = regulator_enable(chip->vcc_reg);
+ if (err) {
+ dev_err(dev, "failed to enable VCC regulator\n");
+ mutex_unlock(&chip->lock);
+ return err;
+ }
+
err = isl29018_chip_init(chip);
if (!err)
chip->suspended = false;
diff --git a/drivers/iio/light/max44009.c b/drivers/iio/light/max44009.c
new file mode 100644
index 000000000000..00ba15499638
--- /dev/null
+++ b/drivers/iio/light/max44009.c
@@ -0,0 +1,555 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * max44009.c - Support for MAX44009 Ambient Light Sensor
+ *
+ * Copyright (c) 2019 Robert Eshleman <bobbyeshleman@gmail.com>
+ *
+ * Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX44009.pdf
+ *
+ * TODO: Support continuous mode and configuring from manual mode to
+ * automatic mode.
+ *
+ * Default I2C address: 0x4a
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/bits.h>
+#include <linux/i2c.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/util_macros.h>
+
+#define MAX44009_DRV_NAME "max44009"
+
+/* Registers in datasheet order */
+#define MAX44009_REG_INT_STATUS 0x0
+#define MAX44009_REG_INT_EN 0x1
+#define MAX44009_REG_CFG 0x2
+#define MAX44009_REG_LUX_HI 0x3
+#define MAX44009_REG_LUX_LO 0x4
+#define MAX44009_REG_UPPER_THR 0x5
+#define MAX44009_REG_LOWER_THR 0x6
+#define MAX44009_REG_THR_TIMER 0x7
+
+#define MAX44009_CFG_TIM_MASK GENMASK(2, 0)
+#define MAX44009_CFG_MAN_MODE_MASK BIT(6)
+
+/* The maximum rising threshold for the max44009 */
+#define MAX44009_MAXIMUM_THRESHOLD 7520256
+
+#define MAX44009_THRESH_EXP_MASK (0xf << 4)
+#define MAX44009_THRESH_EXP_RSHIFT 4
+#define MAX44009_THRESH_MANT_LSHIFT 4
+#define MAX44009_THRESH_MANT_MASK 0xf
+
+#define MAX44009_UPPER_THR_MINIMUM 15
+
+/* The max44009 always scales raw readings by 0.045 and is non-configurable */
+#define MAX44009_SCALE_NUMERATOR 45
+#define MAX44009_SCALE_DENOMINATOR 1000
+
+/* The fixed-point fractional multiplier for de-scaling threshold values */
+#define MAX44009_FRACT_MULT 1000000
+
+static const u32 max44009_int_time_ns_array[] = {
+ 800000000,
+ 400000000,
+ 200000000,
+ 100000000,
+ 50000000, /* Manual mode only */
+ 25000000, /* Manual mode only */
+ 12500000, /* Manual mode only */
+ 6250000, /* Manual mode only */
+};
+
+static const char max44009_int_time_str[] =
+ "0.8 "
+ "0.4 "
+ "0.2 "
+ "0.1 "
+ "0.05 "
+ "0.025 "
+ "0.0125 "
+ "0.00625";
+
+struct max44009_data {
+ struct i2c_client *client;
+ struct mutex lock;
+};
+
+static const struct iio_event_spec max44009_event_spec[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+static const struct iio_chan_spec max44009_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
+ .event_spec = max44009_event_spec,
+ .num_event_specs = ARRAY_SIZE(max44009_event_spec),
+ },
+};
+
+static int max44009_read_int_time(struct max44009_data *data)
+{
+
+ int ret = i2c_smbus_read_byte_data(data->client, MAX44009_REG_CFG);
+
+ if (ret < 0)
+ return ret;
+
+ return max44009_int_time_ns_array[ret & MAX44009_CFG_TIM_MASK];
+}
+
+static int max44009_write_int_time(struct max44009_data *data,
+ int val, int val2)
+{
+ struct i2c_client *client = data->client;
+ int ret, int_time, config;
+ s64 ns;
+
+ ns = val * NSEC_PER_SEC + val2;
+ int_time = find_closest_descending(
+ ns,
+ max44009_int_time_ns_array,
+ ARRAY_SIZE(max44009_int_time_ns_array));
+
+ ret = i2c_smbus_read_byte_data(client, MAX44009_REG_CFG);
+ if (ret < 0)
+ return ret;
+
+ config = ret;
+ config &= int_time;
+
+ /*
+ * To set the integration time, the device must also be in manual
+ * mode.
+ */
+ config |= MAX44009_CFG_MAN_MODE_MASK;
+
+ return i2c_smbus_write_byte_data(client, MAX44009_REG_CFG, config);
+}
+
+static int max44009_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct max44009_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT) {
+ mutex_lock(&data->lock);
+ ret = max44009_write_int_time(data, val, val2);
+ mutex_unlock(&data->lock);
+ return ret;
+ }
+ return -EINVAL;
+}
+
+static int max44009_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ return IIO_VAL_INT_PLUS_NANO;
+}
+
+static int max44009_lux_raw(u8 hi, u8 lo)
+{
+ int mantissa;
+ int exponent;
+
+ /*
+ * The mantissa consists of the low nibble of the Lux High Byte
+ * and the low nibble of the Lux Low Byte.
+ */
+ mantissa = ((hi & 0xf) << 4) | (lo & 0xf);
+
+ /* The exponent byte is just the upper nibble of the Lux High Byte */
+ exponent = (hi >> 4) & 0xf;
+
+ /*
+ * The exponent value is base 2 to the power of the raw exponent byte.
+ */
+ exponent = 1 << exponent;
+
+ return exponent * mantissa;
+}
+
+#define MAX44009_READ_LUX_XFER_LEN (4)
+
+static int max44009_read_lux_raw(struct max44009_data *data)
+{
+ int ret;
+ u8 hireg = MAX44009_REG_LUX_HI;
+ u8 loreg = MAX44009_REG_LUX_LO;
+ u8 lo = 0;
+ u8 hi = 0;
+
+ struct i2c_msg msgs[] = {
+ {
+ .addr = data->client->addr,
+ .flags = 0,
+ .len = sizeof(hireg),
+ .buf = &hireg,
+ },
+ {
+ .addr = data->client->addr,
+ .flags = I2C_M_RD,
+ .len = sizeof(hi),
+ .buf = &hi,
+ },
+ {
+ .addr = data->client->addr,
+ .flags = 0,
+ .len = sizeof(loreg),
+ .buf = &loreg,
+ },
+ {
+ .addr = data->client->addr,
+ .flags = I2C_M_RD,
+ .len = sizeof(lo),
+ .buf = &lo,
+ }
+ };
+
+ /*
+ * Use i2c_transfer instead of smbus read because i2c_transfer
+ * does NOT use a stop bit between address write and data read.
+ * Using a stop bit causes disjoint upper/lower byte reads and
+ * reduces accuracy.
+ */
+ ret = i2c_transfer(data->client->adapter,
+ msgs, MAX44009_READ_LUX_XFER_LEN);
+
+ if (ret != MAX44009_READ_LUX_XFER_LEN)
+ return -EIO;
+
+ return max44009_lux_raw(hi, lo);
+}
+
+static int max44009_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct max44009_data *data = iio_priv(indio_dev);
+ int lux_raw;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (chan->type) {
+ case IIO_LIGHT:
+ ret = max44009_read_lux_raw(data);
+ if (ret < 0)
+ return ret;
+ lux_raw = ret;
+
+ *val = lux_raw * MAX44009_SCALE_NUMERATOR;
+ *val2 = MAX44009_SCALE_DENOMINATOR;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_INT_TIME:
+ switch (chan->type) {
+ case IIO_LIGHT:
+ ret = max44009_read_int_time(data);
+ if (ret < 0)
+ return ret;
+
+ *val2 = ret;
+ *val = 0;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static IIO_CONST_ATTR(illuminance_integration_time_available,
+ max44009_int_time_str);
+
+static struct attribute *max44009_attributes[] = {
+ &iio_const_attr_illuminance_integration_time_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group max44009_attribute_group = {
+ .attrs = max44009_attributes,
+};
+
+static int max44009_threshold_byte_from_fraction(int integral, int fractional)
+{
+ int mantissa, exp;
+
+ if ((integral <= 0 && fractional <= 0) ||
+ integral > MAX44009_MAXIMUM_THRESHOLD ||
+ (integral == MAX44009_MAXIMUM_THRESHOLD && fractional != 0))
+ return -EINVAL;
+
+ /* Reverse scaling of fixed-point integral */
+ mantissa = integral * MAX44009_SCALE_DENOMINATOR;
+ mantissa /= MAX44009_SCALE_NUMERATOR;
+
+ /* Reverse scaling of fixed-point fractional */
+ mantissa += fractional / MAX44009_FRACT_MULT *
+ (MAX44009_SCALE_DENOMINATOR / MAX44009_SCALE_NUMERATOR);
+
+ for (exp = 0; mantissa > 0xff; exp++)
+ mantissa >>= 1;
+
+ mantissa >>= 4;
+ mantissa &= 0xf;
+ exp <<= 4;
+
+ return exp | mantissa;
+}
+
+static int max44009_get_thr_reg(enum iio_event_direction dir)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return MAX44009_REG_UPPER_THR;
+ case IIO_EV_DIR_FALLING:
+ return MAX44009_REG_LOWER_THR;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max44009_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 max44009_data *data = iio_priv(indio_dev);
+ int reg, threshold;
+
+ if (info != IIO_EV_INFO_VALUE || chan->type != IIO_LIGHT)
+ return -EINVAL;
+
+ threshold = max44009_threshold_byte_from_fraction(val, val2);
+ if (threshold < 0)
+ return threshold;
+
+ reg = max44009_get_thr_reg(dir);
+ if (reg < 0)
+ return reg;
+
+ return i2c_smbus_write_byte_data(data->client, reg, threshold);
+}
+
+static int max44009_read_threshold(struct iio_dev *indio_dev,
+ enum iio_event_direction dir)
+{
+ struct max44009_data *data = iio_priv(indio_dev);
+ int byte, reg;
+ int mantissa, exponent;
+
+ reg = max44009_get_thr_reg(dir);
+ if (reg < 0)
+ return reg;
+
+ byte = i2c_smbus_read_byte_data(data->client, reg);
+ if (byte < 0)
+ return byte;
+
+ mantissa = byte & MAX44009_THRESH_MANT_MASK;
+ mantissa <<= MAX44009_THRESH_MANT_LSHIFT;
+
+ /*
+ * To get the upper threshold, always adds the minimum upper threshold
+ * value to the shifted byte value (see datasheet).
+ */
+ if (dir == IIO_EV_DIR_RISING)
+ mantissa += MAX44009_UPPER_THR_MINIMUM;
+
+ /*
+ * Exponent is base 2 to the power of the threshold exponent byte
+ * value
+ */
+ exponent = byte & MAX44009_THRESH_EXP_MASK;
+ exponent >>= MAX44009_THRESH_EXP_RSHIFT;
+
+ return (1 << exponent) * mantissa;
+}
+
+static int max44009_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ int ret;
+ int threshold;
+
+ if (chan->type != IIO_LIGHT || type != IIO_EV_TYPE_THRESH)
+ return -EINVAL;
+
+ ret = max44009_read_threshold(indio_dev, dir);
+ if (ret < 0)
+ return ret;
+ threshold = ret;
+
+ *val = threshold * MAX44009_SCALE_NUMERATOR;
+ *val2 = MAX44009_SCALE_DENOMINATOR;
+
+ return IIO_VAL_FRACTIONAL;
+}
+
+static int max44009_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ int state)
+{
+ struct max44009_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (chan->type != IIO_LIGHT || type != IIO_EV_TYPE_THRESH)
+ return -EINVAL;
+
+ ret = i2c_smbus_write_byte_data(data->client,
+ MAX44009_REG_INT_EN, state);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Set device to trigger interrupt immediately upon exceeding
+ * the threshold limit.
+ */
+ return i2c_smbus_write_byte_data(data->client,
+ MAX44009_REG_THR_TIMER, 0);
+}
+
+static int max44009_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 max44009_data *data = iio_priv(indio_dev);
+
+ if (chan->type != IIO_LIGHT || type != IIO_EV_TYPE_THRESH)
+ return -EINVAL;
+
+ return i2c_smbus_read_byte_data(data->client, MAX44009_REG_INT_EN);
+}
+
+static const struct iio_info max44009_info = {
+ .read_raw = max44009_read_raw,
+ .write_raw = max44009_write_raw,
+ .write_raw_get_fmt = max44009_write_raw_get_fmt,
+ .read_event_value = max44009_read_event_value,
+ .read_event_config = max44009_read_event_config,
+ .write_event_value = max44009_write_event_value,
+ .write_event_config = max44009_write_event_config,
+ .attrs = &max44009_attribute_group,
+};
+
+static irqreturn_t max44009_threaded_irq_handler(int irq, void *p)
+{
+ struct iio_dev *indio_dev = p;
+ struct max44009_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(data->client, MAX44009_REG_INT_STATUS);
+ if (ret) {
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns(indio_dev));
+
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int max44009_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct max44009_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);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &max44009_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->name = MAX44009_DRV_NAME;
+ indio_dev->channels = max44009_channels;
+ indio_dev->num_channels = ARRAY_SIZE(max44009_channels);
+ mutex_init(&data->lock);
+
+ /* Clear any stale interrupt bit */
+ ret = i2c_smbus_read_byte_data(client, MAX44009_REG_CFG);
+ if (ret < 0)
+ return ret;
+
+ if (client->irq > 0) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL,
+ max44009_threaded_irq_handler,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT | IRQF_SHARED,
+ "max44009_event",
+ indio_dev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id max44009_id[] = {
+ { "max44009", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max44009_id);
+
+static struct i2c_driver max44009_driver = {
+ .driver = {
+ .name = MAX44009_DRV_NAME,
+ },
+ .probe = max44009_probe,
+ .id_table = max44009_id,
+};
+module_i2c_driver(max44009_driver);
+
+static const struct of_device_id max44009_of_match[] = {
+ { .compatible = "maxim,max44009" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max44009_of_match);
+
+MODULE_AUTHOR("Robert Eshleman <bobbyeshleman@gmail.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MAX44009 ambient light sensor driver");
diff --git a/drivers/iio/light/vcnl4035.c b/drivers/iio/light/vcnl4035.c
new file mode 100644
index 000000000000..cca4db312bd3
--- /dev/null
+++ b/drivers/iio/light/vcnl4035.c
@@ -0,0 +1,676 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * VCNL4035 Ambient Light and Proximity Sensor - 7-bit I2C slave address 0x60
+ *
+ * Copyright (c) 2018, DENX Software Engineering GmbH
+ * Author: Parthiban Nallathambi <pn@denx.de>
+ *
+ * TODO: Proximity
+ */
+#include <linux/bitops.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.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 VCNL4035_DRV_NAME "vcnl4035"
+#define VCNL4035_IRQ_NAME "vcnl4035_event"
+#define VCNL4035_REGMAP_NAME "vcnl4035_regmap"
+
+/* Device registers */
+#define VCNL4035_ALS_CONF 0x00
+#define VCNL4035_ALS_THDH 0x01
+#define VCNL4035_ALS_THDL 0x02
+#define VCNL4035_ALS_DATA 0x0B
+#define VCNL4035_WHITE_DATA 0x0C
+#define VCNL4035_INT_FLAG 0x0D
+#define VCNL4035_DEV_ID 0x0E
+
+/* Register masks */
+#define VCNL4035_MODE_ALS_MASK BIT(0)
+#define VCNL4035_MODE_ALS_WHITE_CHAN BIT(8)
+#define VCNL4035_MODE_ALS_INT_MASK BIT(1)
+#define VCNL4035_ALS_IT_MASK GENMASK(7, 5)
+#define VCNL4035_ALS_PERS_MASK GENMASK(3, 2)
+#define VCNL4035_INT_ALS_IF_H_MASK BIT(12)
+#define VCNL4035_INT_ALS_IF_L_MASK BIT(13)
+
+/* Default values */
+#define VCNL4035_MODE_ALS_ENABLE BIT(0)
+#define VCNL4035_MODE_ALS_DISABLE 0x00
+#define VCNL4035_MODE_ALS_INT_ENABLE BIT(1)
+#define VCNL4035_MODE_ALS_INT_DISABLE 0
+#define VCNL4035_DEV_ID_VAL 0x80
+#define VCNL4035_ALS_IT_DEFAULT 0x01
+#define VCNL4035_ALS_PERS_DEFAULT 0x00
+#define VCNL4035_ALS_THDH_DEFAULT 5000
+#define VCNL4035_ALS_THDL_DEFAULT 100
+#define VCNL4035_SLEEP_DELAY_MS 2000
+
+struct vcnl4035_data {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ unsigned int als_it_val;
+ unsigned int als_persistence;
+ unsigned int als_thresh_low;
+ unsigned int als_thresh_high;
+ struct iio_trigger *drdy_trigger0;
+};
+
+static inline bool vcnl4035_is_triggered(struct vcnl4035_data *data)
+{
+ int ret;
+ int reg;
+
+ ret = regmap_read(data->regmap, VCNL4035_INT_FLAG, &reg);
+ if (ret < 0)
+ return false;
+
+ return !!(reg &
+ (VCNL4035_INT_ALS_IF_H_MASK | VCNL4035_INT_ALS_IF_L_MASK));
+}
+
+static irqreturn_t vcnl4035_drdy_irq_thread(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct vcnl4035_data *data = iio_priv(indio_dev);
+
+ if (vcnl4035_is_triggered(data)) {
+ iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+ 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns(indio_dev));
+ iio_trigger_poll_chained(data->drdy_trigger0);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+/* Triggered buffer */
+static irqreturn_t vcnl4035_trigger_consumer_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct vcnl4035_data *data = iio_priv(indio_dev);
+ u8 buffer[ALIGN(sizeof(u16), sizeof(s64)) + sizeof(s64)];
+ int ret;
+
+ ret = regmap_read(data->regmap, VCNL4035_ALS_DATA, (int *)buffer);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Trigger consumer can't read from sensor.\n");
+ goto fail_read;
+ }
+ iio_push_to_buffers_with_timestamp(indio_dev, buffer,
+ iio_get_time_ns(indio_dev));
+
+fail_read:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int vcnl4035_als_drdy_set_state(struct iio_trigger *trigger,
+ bool enable_drdy)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trigger);
+ struct vcnl4035_data *data = iio_priv(indio_dev);
+ int val = enable_drdy ? VCNL4035_MODE_ALS_INT_ENABLE :
+ VCNL4035_MODE_ALS_INT_DISABLE;
+
+ return regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
+ VCNL4035_MODE_ALS_INT_MASK,
+ val);
+}
+
+static const struct iio_trigger_ops vcnl4035_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+ .set_trigger_state = vcnl4035_als_drdy_set_state,
+};
+
+static int vcnl4035_set_pm_runtime_state(struct vcnl4035_data *data, bool on)
+{
+ int ret;
+ struct device *dev = &data->client->dev;
+
+ if (on) {
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ pm_runtime_put_noidle(dev);
+ } else {
+ pm_runtime_mark_last_busy(dev);
+ ret = pm_runtime_put_autosuspend(dev);
+ }
+
+ return ret;
+}
+
+/*
+ * Device IT INT Time (ms) Scale (lux/step)
+ * 000 50 0.064
+ * 001 100 0.032
+ * 010 200 0.016
+ * 100 400 0.008
+ * 101 - 111 800 0.004
+ * Values are proportional, so ALS INT is selected for input due to
+ * simplicity reason. Integration time value and scaling is
+ * calculated based on device INT value
+ *
+ * Raw value needs to be scaled using ALS steps
+ */
+static int vcnl4035_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct vcnl4035_data *data = iio_priv(indio_dev);
+ int ret;
+ int raw_data;
+ unsigned int reg;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = vcnl4035_set_pm_runtime_state(data, true);
+ if (ret < 0)
+ return ret;
+
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (!ret) {
+ if (chan->channel)
+ reg = VCNL4035_ALS_DATA;
+ else
+ reg = VCNL4035_WHITE_DATA;
+ ret = regmap_read(data->regmap, reg, &raw_data);
+ iio_device_release_direct_mode(indio_dev);
+ if (!ret) {
+ *val = raw_data;
+ ret = IIO_VAL_INT;
+ }
+ }
+ vcnl4035_set_pm_runtime_state(data, false);
+ return ret;
+ case IIO_CHAN_INFO_INT_TIME:
+ *val = 50;
+ if (data->als_it_val)
+ *val = data->als_it_val * 100;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 64;
+ if (!data->als_it_val)
+ *val2 = 1000;
+ else
+ *val2 = data->als_it_val * 2 * 1000;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int vcnl4035_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int ret;
+ struct vcnl4035_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ if (val <= 0 || val > 800)
+ return -EINVAL;
+
+ ret = vcnl4035_set_pm_runtime_state(data, true);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
+ VCNL4035_ALS_IT_MASK,
+ val / 100);
+ if (!ret)
+ data->als_it_val = val / 100;
+
+ vcnl4035_set_pm_runtime_state(data, false);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+/* No direct ABI for persistence and threshold, so eventing */
+static int vcnl4035_read_thresh(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 vcnl4035_data *data = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ *val = data->als_thresh_high;
+ return IIO_VAL_INT;
+ case IIO_EV_DIR_FALLING:
+ *val = data->als_thresh_low;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case IIO_EV_INFO_PERIOD:
+ *val = data->als_persistence;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+
+}
+
+static int vcnl4035_write_thresh(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 vcnl4035_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ /* 16 bit threshold range 0 - 65535 */
+ if (val < 0 || val > 65535)
+ return -EINVAL;
+ if (dir == IIO_EV_DIR_RISING) {
+ if (val < data->als_thresh_low)
+ return -EINVAL;
+ ret = regmap_write(data->regmap, VCNL4035_ALS_THDH,
+ val);
+ if (ret)
+ return ret;
+ data->als_thresh_high = val;
+ } else {
+ if (val > data->als_thresh_high)
+ return -EINVAL;
+ ret = regmap_write(data->regmap, VCNL4035_ALS_THDL,
+ val);
+ if (ret)
+ return ret;
+ data->als_thresh_low = val;
+ }
+ return ret;
+ case IIO_EV_INFO_PERIOD:
+ /* allow only 1 2 4 8 as persistence value */
+ if (val < 0 || val > 8 || hweight8(val) != 1)
+ return -EINVAL;
+ ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
+ VCNL4035_ALS_PERS_MASK, val);
+ if (!ret)
+ data->als_persistence = val;
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static IIO_CONST_ATTR_INT_TIME_AVAIL("50 100 200 400 800");
+
+static struct attribute *vcnl4035_attributes[] = {
+ &iio_const_attr_integration_time_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group vcnl4035_attribute_group = {
+ .attrs = vcnl4035_attributes,
+};
+
+static const struct iio_info vcnl4035_info = {
+ .read_raw = vcnl4035_read_raw,
+ .write_raw = vcnl4035_write_raw,
+ .read_event_value = vcnl4035_read_thresh,
+ .write_event_value = vcnl4035_write_thresh,
+ .attrs = &vcnl4035_attribute_group,
+};
+
+static const struct iio_event_spec vcnl4035_event_spec[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ }, {
+ .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_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_PERIOD),
+ },
+};
+
+enum vcnl4035_scan_index_order {
+ VCNL4035_CHAN_INDEX_LIGHT,
+ VCNL4035_CHAN_INDEX_WHITE_LED,
+};
+
+static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = {
+ .validate_scan_mask = &iio_validate_scan_mask_onehot,
+};
+
+static const struct iio_chan_spec vcnl4035_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .event_spec = vcnl4035_event_spec,
+ .num_event_specs = ARRAY_SIZE(vcnl4035_event_spec),
+ .scan_index = VCNL4035_CHAN_INDEX_LIGHT,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
+ },
+ {
+ .type = IIO_INTENSITY,
+ .channel = 1,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_BOTH,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .scan_index = VCNL4035_CHAN_INDEX_WHITE_LED,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
+ },
+};
+
+static int vcnl4035_set_als_power_state(struct vcnl4035_data *data, u8 status)
+{
+ return regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
+ VCNL4035_MODE_ALS_MASK,
+ status);
+}
+
+static int vcnl4035_init(struct vcnl4035_data *data)
+{
+ int ret;
+ int id;
+
+ ret = regmap_read(data->regmap, VCNL4035_DEV_ID, &id);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Failed to read DEV_ID register\n");
+ return ret;
+ }
+
+ if (id != VCNL4035_DEV_ID_VAL) {
+ dev_err(&data->client->dev, "Wrong id, got %x, expected %x\n",
+ id, VCNL4035_DEV_ID_VAL);
+ return -ENODEV;
+ }
+
+ ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_ENABLE);
+ if (ret < 0)
+ return ret;
+
+ /* ALS white channel enable */
+ ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
+ VCNL4035_MODE_ALS_WHITE_CHAN,
+ 1);
+ if (ret) {
+ dev_err(&data->client->dev, "set white channel enable %d\n",
+ ret);
+ return ret;
+ }
+
+ /* set default integration time - 100 ms for ALS */
+ ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
+ VCNL4035_ALS_IT_MASK,
+ VCNL4035_ALS_IT_DEFAULT);
+ if (ret) {
+ dev_err(&data->client->dev, "set default ALS IT returned %d\n",
+ ret);
+ return ret;
+ }
+ data->als_it_val = VCNL4035_ALS_IT_DEFAULT;
+
+ /* set default persistence time - 1 for ALS */
+ ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
+ VCNL4035_ALS_PERS_MASK,
+ VCNL4035_ALS_PERS_DEFAULT);
+ if (ret) {
+ dev_err(&data->client->dev, "set default PERS returned %d\n",
+ ret);
+ return ret;
+ }
+ data->als_persistence = VCNL4035_ALS_PERS_DEFAULT;
+
+ /* set default HIGH threshold for ALS */
+ ret = regmap_write(data->regmap, VCNL4035_ALS_THDH,
+ VCNL4035_ALS_THDH_DEFAULT);
+ if (ret) {
+ dev_err(&data->client->dev, "set default THDH returned %d\n",
+ ret);
+ return ret;
+ }
+ data->als_thresh_high = VCNL4035_ALS_THDH_DEFAULT;
+
+ /* set default LOW threshold for ALS */
+ ret = regmap_write(data->regmap, VCNL4035_ALS_THDL,
+ VCNL4035_ALS_THDL_DEFAULT);
+ if (ret) {
+ dev_err(&data->client->dev, "set default THDL returned %d\n",
+ ret);
+ return ret;
+ }
+ data->als_thresh_low = VCNL4035_ALS_THDL_DEFAULT;
+
+ return 0;
+}
+
+static bool vcnl4035_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case VCNL4035_ALS_CONF:
+ case VCNL4035_DEV_ID:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config vcnl4035_regmap_config = {
+ .name = VCNL4035_REGMAP_NAME,
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = VCNL4035_DEV_ID,
+ .cache_type = REGCACHE_RBTREE,
+ .volatile_reg = vcnl4035_is_volatile_reg,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static int vcnl4035_probe_trigger(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct vcnl4035_data *data = iio_priv(indio_dev);
+
+ data->drdy_trigger0 = devm_iio_trigger_alloc(
+ indio_dev->dev.parent,
+ "%s-dev%d", indio_dev->name, indio_dev->id);
+ if (!data->drdy_trigger0)
+ return -ENOMEM;
+
+ data->drdy_trigger0->dev.parent = indio_dev->dev.parent;
+ data->drdy_trigger0->ops = &vcnl4035_trigger_ops;
+ iio_trigger_set_drvdata(data->drdy_trigger0, indio_dev);
+ ret = devm_iio_trigger_register(indio_dev->dev.parent,
+ data->drdy_trigger0);
+ if (ret) {
+ dev_err(&data->client->dev, "iio trigger register failed\n");
+ return ret;
+ }
+
+ /* Trigger setup */
+ ret = devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev,
+ NULL, vcnl4035_trigger_consumer_handler,
+ &iio_triggered_buffer_setup_ops);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "iio triggered buffer setup failed\n");
+ return ret;
+ }
+
+ /* IRQ to trigger mapping */
+ ret = devm_request_threaded_irq(&data->client->dev, data->client->irq,
+ NULL, vcnl4035_drdy_irq_thread,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ VCNL4035_IRQ_NAME, indio_dev);
+ if (ret < 0)
+ dev_err(&data->client->dev, "request irq %d for trigger0 failed\n",
+ data->client->irq);
+ return ret;
+}
+
+static int vcnl4035_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct vcnl4035_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, &vcnl4035_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "regmap_init failed!\n");
+ return PTR_ERR(regmap);
+ }
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+ data->regmap = regmap;
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->info = &vcnl4035_info;
+ indio_dev->name = VCNL4035_DRV_NAME;
+ indio_dev->channels = vcnl4035_channels;
+ indio_dev->num_channels = ARRAY_SIZE(vcnl4035_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = vcnl4035_init(data);
+ if (ret < 0) {
+ dev_err(&client->dev, "vcnl4035 chip init failed\n");
+ return ret;
+ }
+
+ if (client->irq > 0) {
+ ret = vcnl4035_probe_trigger(indio_dev);
+ if (ret < 0) {
+ dev_err(&client->dev, "vcnl4035 unable init trigger\n");
+ goto fail_poweroff;
+ }
+ }
+
+ ret = pm_runtime_set_active(&client->dev);
+ if (ret < 0)
+ goto fail_poweroff;
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0)
+ goto fail_poweroff;
+
+ pm_runtime_enable(&client->dev);
+ pm_runtime_set_autosuspend_delay(&client->dev, VCNL4035_SLEEP_DELAY_MS);
+ pm_runtime_use_autosuspend(&client->dev);
+
+ return 0;
+
+fail_poweroff:
+ vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_DISABLE);
+ return ret;
+}
+
+static int vcnl4035_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ pm_runtime_dont_use_autosuspend(&client->dev);
+ pm_runtime_disable(&client->dev);
+ iio_device_unregister(indio_dev);
+ pm_runtime_set_suspended(&client->dev);
+
+ return vcnl4035_set_als_power_state(iio_priv(indio_dev),
+ VCNL4035_MODE_ALS_DISABLE);
+}
+
+static int __maybe_unused vcnl4035_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct vcnl4035_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_DISABLE);
+ regcache_mark_dirty(data->regmap);
+
+ return ret;
+}
+
+static int __maybe_unused vcnl4035_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+ struct vcnl4035_data *data = iio_priv(indio_dev);
+ int ret;
+
+ regcache_sync(data->regmap);
+ ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_ENABLE);
+ if (ret < 0)
+ return ret;
+
+ /* wait for 1 ALS integration cycle */
+ msleep(data->als_it_val * 100);
+
+ return 0;
+}
+
+static const struct dev_pm_ops vcnl4035_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(vcnl4035_runtime_suspend,
+ vcnl4035_runtime_resume, NULL)
+};
+
+static const struct of_device_id vcnl4035_of_match[] = {
+ { .compatible = "vishay,vcnl4035", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, vcnl4035_of_match);
+
+static struct i2c_driver vcnl4035_driver = {
+ .driver = {
+ .name = VCNL4035_DRV_NAME,
+ .pm = &vcnl4035_pm_ops,
+ .of_match_table = vcnl4035_of_match,
+ },
+ .probe = vcnl4035_probe,
+ .remove = vcnl4035_remove,
+};
+
+module_i2c_driver(vcnl4035_driver);
+
+MODULE_AUTHOR("Parthiban Nallathambi <pn@denx.de>");
+MODULE_DESCRIPTION("VCNL4035 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig
index ed9d776d01af..8a63cbbca4b7 100644
--- a/drivers/iio/magnetometer/Kconfig
+++ b/drivers/iio/magnetometer/Kconfig
@@ -175,4 +175,33 @@ config SENSORS_HMC5843_SPI
- hmc5843_core (core functions)
- hmc5843_spi (support for HMC5983)
+config SENSORS_RM3100
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config SENSORS_RM3100_I2C
+ tristate "PNI RM3100 3-Axis Magnetometer (I2C)"
+ depends on I2C
+ select SENSORS_RM3100
+ select REGMAP_I2C
+ help
+ Say Y here to add support for the PNI RM3100 3-Axis Magnetometer.
+
+ This driver can also be compiled as a module.
+ To compile this driver as a module, choose M here: the module
+ will be called rm3100-i2c.
+
+config SENSORS_RM3100_SPI
+ tristate "PNI RM3100 3-Axis Magnetometer (SPI)"
+ depends on SPI_MASTER
+ select SENSORS_RM3100
+ select REGMAP_SPI
+ help
+ Say Y here to add support for the PNI RM3100 3-Axis Magnetometer.
+
+ This driver can also be compiled as a module.
+ To compile this driver as a module, choose M here: the module
+ will be called rm3100-spi.
+
endmenu
diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile
index 664b2f866472..ba1bc34b82fa 100644
--- a/drivers/iio/magnetometer/Makefile
+++ b/drivers/iio/magnetometer/Makefile
@@ -24,3 +24,7 @@ obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o
obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o
obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o
obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o
+
+obj-$(CONFIG_SENSORS_RM3100) += rm3100-core.o
+obj-$(CONFIG_SENSORS_RM3100_I2C) += rm3100-i2c.o
+obj-$(CONFIG_SENSORS_RM3100_SPI) += rm3100-spi.o
diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c
index 42a827a66512..d430b80808ef 100644
--- a/drivers/iio/magnetometer/ak8975.c
+++ b/drivers/iio/magnetometer/ak8975.c
@@ -790,6 +790,7 @@ static const struct acpi_device_id ak_acpi_match[] = {
{"INVN6500", AK8963},
{"AK009911", AK09911},
{"AK09911", AK09911},
+ {"AKM9911", AK09911},
{"AK09912", AK09912},
{ },
};
diff --git a/drivers/iio/magnetometer/mag3110.c b/drivers/iio/magnetometer/mag3110.c
index f063355480ba..dd990cdb04a8 100644
--- a/drivers/iio/magnetometer/mag3110.c
+++ b/drivers/iio/magnetometer/mag3110.c
@@ -20,6 +20,7 @@
#include <linux/iio/buffer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
#define MAG3110_STATUS 0x00
#define MAG3110_OUT_X 0x01 /* MSB first */
@@ -56,6 +57,8 @@ struct mag3110_data {
struct mutex lock;
u8 ctrl_reg1;
int sleep_val;
+ struct regulator *vdd_reg;
+ struct regulator *vddio_reg;
};
static int mag3110_request(struct mag3110_data *data)
@@ -469,17 +472,50 @@ static int mag3110_probe(struct i2c_client *client,
struct iio_dev *indio_dev;
int ret;
- ret = i2c_smbus_read_byte_data(client, MAG3110_WHO_AM_I);
- if (ret < 0)
- return ret;
- if (ret != MAG3110_DEVICE_ID)
- return -ENODEV;
-
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
+
+ data->vdd_reg = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(data->vdd_reg)) {
+ if (PTR_ERR(data->vdd_reg) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ dev_err(&client->dev, "failed to get VDD regulator!\n");
+ return PTR_ERR(data->vdd_reg);
+ }
+
+ data->vddio_reg = devm_regulator_get(&client->dev, "vddio");
+ if (IS_ERR(data->vddio_reg)) {
+ if (PTR_ERR(data->vddio_reg) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ dev_err(&client->dev, "failed to get VDDIO regulator!\n");
+ return PTR_ERR(data->vddio_reg);
+ }
+
+ ret = regulator_enable(data->vdd_reg);
+ if (ret) {
+ dev_err(&client->dev, "failed to enable VDD regulator!\n");
+ return ret;
+ }
+
+ ret = regulator_enable(data->vddio_reg);
+ if (ret) {
+ dev_err(&client->dev, "failed to enable VDDIO regulator!\n");
+ goto disable_regulator_vdd;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, MAG3110_WHO_AM_I);
+ if (ret < 0)
+ goto disable_regulators;
+ if (ret != MAG3110_DEVICE_ID) {
+ ret = -ENODEV;
+ goto disable_regulators;
+ }
+
data->client = client;
mutex_init(&data->lock);
@@ -499,7 +535,7 @@ static int mag3110_probe(struct i2c_client *client,
ret = mag3110_change_config(data, MAG3110_CTRL_REG1, data->ctrl_reg1);
if (ret < 0)
- return ret;
+ goto disable_regulators;
ret = i2c_smbus_write_byte_data(client, MAG3110_CTRL_REG2,
MAG3110_CTRL_AUTO_MRST_EN);
@@ -520,16 +556,24 @@ buffer_cleanup:
iio_triggered_buffer_cleanup(indio_dev);
standby_on_error:
mag3110_standby(iio_priv(indio_dev));
+disable_regulators:
+ regulator_disable(data->vddio_reg);
+disable_regulator_vdd:
+ regulator_disable(data->vdd_reg);
+
return ret;
}
static int mag3110_remove(struct i2c_client *client)
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct mag3110_data *data = iio_priv(indio_dev);
iio_device_unregister(indio_dev);
iio_triggered_buffer_cleanup(indio_dev);
mag3110_standby(iio_priv(indio_dev));
+ regulator_disable(data->vddio_reg);
+ regulator_disable(data->vdd_reg);
return 0;
}
@@ -537,14 +581,48 @@ static int mag3110_remove(struct i2c_client *client)
#ifdef CONFIG_PM_SLEEP
static int mag3110_suspend(struct device *dev)
{
- return mag3110_standby(iio_priv(i2c_get_clientdata(
+ struct mag3110_data *data = iio_priv(i2c_get_clientdata(
+ to_i2c_client(dev)));
+ int ret;
+
+ ret = mag3110_standby(iio_priv(i2c_get_clientdata(
to_i2c_client(dev))));
+ if (ret)
+ return ret;
+
+ 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 mag3110_resume(struct device *dev)
{
struct mag3110_data *data = iio_priv(i2c_get_clientdata(
to_i2c_client(dev)));
+ int ret;
+
+ 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;
+ }
return i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1,
data->ctrl_reg1);
diff --git a/drivers/iio/magnetometer/rm3100-core.c b/drivers/iio/magnetometer/rm3100-core.c
new file mode 100644
index 000000000000..7c20918d8108
--- /dev/null
+++ b/drivers/iio/magnetometer/rm3100-core.c
@@ -0,0 +1,616 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PNI RM3100 3-axis geomagnetic sensor driver core.
+ *
+ * Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
+ *
+ * User Manual available at
+ * <https://www.pnicorp.com/download/rm3100-user-manual/>
+ *
+ * TODO: event generation, pm.
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.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>
+
+#include "rm3100.h"
+
+/* Cycle Count Registers. */
+#define RM3100_REG_CC_X 0x05
+#define RM3100_REG_CC_Y 0x07
+#define RM3100_REG_CC_Z 0x09
+
+/* Poll Measurement Mode register. */
+#define RM3100_REG_POLL 0x00
+#define RM3100_POLL_X BIT(4)
+#define RM3100_POLL_Y BIT(5)
+#define RM3100_POLL_Z BIT(6)
+
+/* Continuous Measurement Mode register. */
+#define RM3100_REG_CMM 0x01
+#define RM3100_CMM_START BIT(0)
+#define RM3100_CMM_X BIT(4)
+#define RM3100_CMM_Y BIT(5)
+#define RM3100_CMM_Z BIT(6)
+
+/* TiMe Rate Configuration register. */
+#define RM3100_REG_TMRC 0x0B
+#define RM3100_TMRC_OFFSET 0x92
+
+/* Result Status register. */
+#define RM3100_REG_STATUS 0x34
+#define RM3100_STATUS_DRDY BIT(7)
+
+/* Measurement result registers. */
+#define RM3100_REG_MX2 0x24
+#define RM3100_REG_MY2 0x27
+#define RM3100_REG_MZ2 0x2a
+
+#define RM3100_W_REG_START RM3100_REG_POLL
+#define RM3100_W_REG_END RM3100_REG_TMRC
+#define RM3100_R_REG_START RM3100_REG_POLL
+#define RM3100_R_REG_END RM3100_REG_STATUS
+#define RM3100_V_REG_START RM3100_REG_POLL
+#define RM3100_V_REG_END RM3100_REG_STATUS
+
+/*
+ * This is computed by hand, is the sum of channel storage bits and padding
+ * bits, which is 4+4+4+12=24 in here.
+ */
+#define RM3100_SCAN_BYTES 24
+
+#define RM3100_CMM_AXIS_SHIFT 4
+
+struct rm3100_data {
+ struct regmap *regmap;
+ struct completion measuring_done;
+ bool use_interrupt;
+ int conversion_time;
+ int scale;
+ u8 buffer[RM3100_SCAN_BYTES];
+ struct iio_trigger *drdy_trig;
+
+ /*
+ * This lock is for protecting the consistency of series of i2c
+ * operations, that is, to make sure a measurement process will
+ * not be interrupted by a set frequency operation, which should
+ * be taken where a series of i2c operation starts, released where
+ * the operation ends.
+ */
+ struct mutex lock;
+};
+
+static const struct regmap_range rm3100_readable_ranges[] = {
+ regmap_reg_range(RM3100_R_REG_START, RM3100_R_REG_END),
+};
+
+const struct regmap_access_table rm3100_readable_table = {
+ .yes_ranges = rm3100_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(rm3100_readable_ranges),
+};
+EXPORT_SYMBOL_GPL(rm3100_readable_table);
+
+static const struct regmap_range rm3100_writable_ranges[] = {
+ regmap_reg_range(RM3100_W_REG_START, RM3100_W_REG_END),
+};
+
+const struct regmap_access_table rm3100_writable_table = {
+ .yes_ranges = rm3100_writable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(rm3100_writable_ranges),
+};
+EXPORT_SYMBOL_GPL(rm3100_writable_table);
+
+static const struct regmap_range rm3100_volatile_ranges[] = {
+ regmap_reg_range(RM3100_V_REG_START, RM3100_V_REG_END),
+};
+
+const struct regmap_access_table rm3100_volatile_table = {
+ .yes_ranges = rm3100_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(rm3100_volatile_ranges),
+};
+EXPORT_SYMBOL_GPL(rm3100_volatile_table);
+
+static irqreturn_t rm3100_thread_fn(int irq, void *d)
+{
+ struct iio_dev *indio_dev = d;
+ struct rm3100_data *data = iio_priv(indio_dev);
+
+ /*
+ * Write operation to any register or read operation
+ * to first byte of results will clear the interrupt.
+ */
+ regmap_write(data->regmap, RM3100_REG_POLL, 0);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rm3100_irq_handler(int irq, void *d)
+{
+ struct iio_dev *indio_dev = d;
+ struct rm3100_data *data = iio_priv(indio_dev);
+
+ switch (indio_dev->currentmode) {
+ case INDIO_DIRECT_MODE:
+ complete(&data->measuring_done);
+ break;
+ case INDIO_BUFFER_TRIGGERED:
+ iio_trigger_poll(data->drdy_trig);
+ break;
+ default:
+ dev_err(indio_dev->dev.parent,
+ "device mode out of control, current mode: %d",
+ indio_dev->currentmode);
+ }
+
+ return IRQ_WAKE_THREAD;
+}
+
+static int rm3100_wait_measurement(struct rm3100_data *data)
+{
+ struct regmap *regmap = data->regmap;
+ unsigned int val;
+ int tries = 20;
+ int ret;
+
+ /*
+ * A read cycle of 400kbits i2c bus is about 20us, plus the time
+ * used for scheduling, a read cycle of fast mode of this device
+ * can reach 1.7ms, it may be possible for data to arrive just
+ * after we check the RM3100_REG_STATUS. In this case, irq_handler is
+ * called before measuring_done is reinitialized, it will wait
+ * forever for data that has already been ready.
+ * Reinitialize measuring_done before looking up makes sure we
+ * will always capture interrupt no matter when it happens.
+ */
+ if (data->use_interrupt)
+ reinit_completion(&data->measuring_done);
+
+ ret = regmap_read(regmap, RM3100_REG_STATUS, &val);
+ if (ret < 0)
+ return ret;
+
+ if ((val & RM3100_STATUS_DRDY) != RM3100_STATUS_DRDY) {
+ if (data->use_interrupt) {
+ ret = wait_for_completion_timeout(&data->measuring_done,
+ msecs_to_jiffies(data->conversion_time));
+ if (!ret)
+ return -ETIMEDOUT;
+ } else {
+ do {
+ usleep_range(1000, 5000);
+
+ ret = regmap_read(regmap, RM3100_REG_STATUS,
+ &val);
+ if (ret < 0)
+ return ret;
+
+ if (val & RM3100_STATUS_DRDY)
+ break;
+ } while (--tries);
+ if (!tries)
+ return -ETIMEDOUT;
+ }
+ }
+ return 0;
+}
+
+static int rm3100_read_mag(struct rm3100_data *data, int idx, int *val)
+{
+ struct regmap *regmap = data->regmap;
+ u8 buffer[3];
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = regmap_write(regmap, RM3100_REG_POLL, BIT(4 + idx));
+ if (ret < 0)
+ goto unlock_return;
+
+ ret = rm3100_wait_measurement(data);
+ if (ret < 0)
+ goto unlock_return;
+
+ ret = regmap_bulk_read(regmap, RM3100_REG_MX2 + 3 * idx, buffer, 3);
+ if (ret < 0)
+ goto unlock_return;
+ mutex_unlock(&data->lock);
+
+ *val = sign_extend32((buffer[0] << 16) | (buffer[1] << 8) | buffer[2],
+ 23);
+
+ return IIO_VAL_INT;
+
+unlock_return:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+#define RM3100_CHANNEL(axis, idx) \
+ { \
+ .type = IIO_MAGN, \
+ .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 = idx, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 24, \
+ .storagebits = 32, \
+ .shift = 8, \
+ .endianness = IIO_BE, \
+ }, \
+ }
+
+static const struct iio_chan_spec rm3100_channels[] = {
+ RM3100_CHANNEL(X, 0),
+ RM3100_CHANNEL(Y, 1),
+ RM3100_CHANNEL(Z, 2),
+ IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(
+ "600 300 150 75 37 18 9 4.5 2.3 1.2 0.6 0.3 0.015 0.075"
+);
+
+static struct attribute *rm3100_attributes[] = {
+ &iio_const_attr_sampling_frequency_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group rm3100_attribute_group = {
+ .attrs = rm3100_attributes,
+};
+
+#define RM3100_SAMP_NUM 14
+
+/*
+ * Frequency : rm3100_samp_rates[][0].rm3100_samp_rates[][1]Hz.
+ * Time between reading: rm3100_sam_rates[][2]ms.
+ * The first one is actually 1.7ms.
+ */
+static const int rm3100_samp_rates[RM3100_SAMP_NUM][3] = {
+ {600, 0, 2}, {300, 0, 3}, {150, 0, 7}, {75, 0, 13}, {37, 0, 27},
+ {18, 0, 55}, {9, 0, 110}, {4, 500000, 220}, {2, 300000, 440},
+ {1, 200000, 800}, {0, 600000, 1600}, {0, 300000, 3300},
+ {0, 15000, 6700}, {0, 75000, 13000}
+};
+
+static int rm3100_get_samp_freq(struct rm3100_data *data, int *val, int *val2)
+{
+ unsigned int tmp;
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = regmap_read(data->regmap, RM3100_REG_TMRC, &tmp);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+ *val = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][0];
+ *val2 = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int rm3100_set_cycle_count(struct rm3100_data *data, int val)
+{
+ int ret;
+ u8 i;
+
+ for (i = 0; i < 3; i++) {
+ ret = regmap_write(data->regmap, RM3100_REG_CC_X + 2 * i, val);
+ if (ret < 0)
+ return ret;
+ }
+
+ /*
+ * The scale of this sensor depends on the cycle count value, these
+ * three values are corresponding to the cycle count value 50, 100,
+ * 200. scale = output / gain * 10^4.
+ */
+ switch (val) {
+ case 50:
+ data->scale = 500;
+ break;
+ case 100:
+ data->scale = 263;
+ break;
+ /*
+ * case 200:
+ * This function will never be called by users' code, so here we
+ * assume that it will never get a wrong parameter.
+ */
+ default:
+ data->scale = 133;
+ }
+
+ return 0;
+}
+
+static int rm3100_set_samp_freq(struct iio_dev *indio_dev, int val, int val2)
+{
+ struct rm3100_data *data = iio_priv(indio_dev);
+ struct regmap *regmap = data->regmap;
+ unsigned int cycle_count;
+ int ret;
+ int i;
+
+ mutex_lock(&data->lock);
+ /* All cycle count registers use the same value. */
+ ret = regmap_read(regmap, RM3100_REG_CC_X, &cycle_count);
+ if (ret < 0)
+ goto unlock_return;
+
+ for (i = 0; i < RM3100_SAMP_NUM; i++) {
+ if (val == rm3100_samp_rates[i][0] &&
+ val2 == rm3100_samp_rates[i][1])
+ break;
+ }
+ if (i == RM3100_SAMP_NUM) {
+ ret = -EINVAL;
+ goto unlock_return;
+ }
+
+ ret = regmap_write(regmap, RM3100_REG_TMRC, i + RM3100_TMRC_OFFSET);
+ if (ret < 0)
+ goto unlock_return;
+
+ /* Checking if cycle count registers need changing. */
+ if (val == 600 && cycle_count == 200) {
+ ret = rm3100_set_cycle_count(data, 100);
+ if (ret < 0)
+ goto unlock_return;
+ } else if (val != 600 && cycle_count == 100) {
+ ret = rm3100_set_cycle_count(data, 200);
+ if (ret < 0)
+ goto unlock_return;
+ }
+
+ if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) {
+ /* Writing TMRC registers requires CMM reset. */
+ ret = regmap_write(regmap, RM3100_REG_CMM, 0);
+ if (ret < 0)
+ goto unlock_return;
+ ret = regmap_write(data->regmap, RM3100_REG_CMM,
+ (*indio_dev->active_scan_mask & 0x7) <<
+ RM3100_CMM_AXIS_SHIFT | RM3100_CMM_START);
+ if (ret < 0)
+ goto unlock_return;
+ }
+ mutex_unlock(&data->lock);
+
+ data->conversion_time = rm3100_samp_rates[i][2] * 2;
+ return 0;
+
+unlock_return:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int rm3100_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct rm3100_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret < 0)
+ return ret;
+
+ ret = rm3100_read_mag(data, chan->scan_index, val);
+ iio_device_release_direct_mode(indio_dev);
+
+ return ret;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = data->scale;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return rm3100_get_samp_freq(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int rm3100_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_SAMP_FREQ:
+ return rm3100_set_samp_freq(indio_dev, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info rm3100_info = {
+ .attrs = &rm3100_attribute_group,
+ .read_raw = rm3100_read_raw,
+ .write_raw = rm3100_write_raw,
+};
+
+static int rm3100_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct rm3100_data *data = iio_priv(indio_dev);
+
+ /* Starting channels enabled. */
+ return regmap_write(data->regmap, RM3100_REG_CMM,
+ (*indio_dev->active_scan_mask & 0x7) << RM3100_CMM_AXIS_SHIFT |
+ RM3100_CMM_START);
+}
+
+static int rm3100_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct rm3100_data *data = iio_priv(indio_dev);
+
+ return regmap_write(data->regmap, RM3100_REG_CMM, 0);
+}
+
+static const struct iio_buffer_setup_ops rm3100_buffer_ops = {
+ .preenable = rm3100_buffer_preenable,
+ .postenable = iio_triggered_buffer_postenable,
+ .predisable = iio_triggered_buffer_predisable,
+ .postdisable = rm3100_buffer_postdisable,
+};
+
+static irqreturn_t rm3100_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ unsigned long scan_mask = *indio_dev->active_scan_mask;
+ unsigned int mask_len = indio_dev->masklength;
+ struct rm3100_data *data = iio_priv(indio_dev);
+ struct regmap *regmap = data->regmap;
+ int ret, i, bit;
+
+ mutex_lock(&data->lock);
+ switch (scan_mask) {
+ case BIT(0) | BIT(1) | BIT(2):
+ ret = regmap_bulk_read(regmap, RM3100_REG_MX2, data->buffer, 9);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ goto done;
+ /* Convert XXXYYYZZZxxx to XXXxYYYxZZZx. x for paddings. */
+ for (i = 2; i > 0; i--)
+ memmove(data->buffer + i * 4, data->buffer + i * 3, 3);
+ break;
+ case BIT(0) | BIT(1):
+ ret = regmap_bulk_read(regmap, RM3100_REG_MX2, data->buffer, 6);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ goto done;
+ memmove(data->buffer + 4, data->buffer + 3, 3);
+ break;
+ case BIT(1) | BIT(2):
+ ret = regmap_bulk_read(regmap, RM3100_REG_MY2, data->buffer, 6);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ goto done;
+ memmove(data->buffer + 4, data->buffer + 3, 3);
+ break;
+ case BIT(0) | BIT(2):
+ ret = regmap_bulk_read(regmap, RM3100_REG_MX2, data->buffer, 9);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ goto done;
+ memmove(data->buffer + 4, data->buffer + 6, 3);
+ break;
+ default:
+ for_each_set_bit(bit, &scan_mask, mask_len) {
+ ret = regmap_bulk_read(regmap, RM3100_REG_MX2 + 3 * bit,
+ data->buffer, 3);
+ if (ret < 0) {
+ mutex_unlock(&data->lock);
+ goto done;
+ }
+ }
+ mutex_unlock(&data->lock);
+ }
+ /*
+ * Always using the same buffer so that we wouldn't need to set the
+ * paddings to 0 in case of leaking any data.
+ */
+ iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
+ pf->timestamp);
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+int rm3100_common_probe(struct device *dev, struct regmap *regmap, int irq)
+{
+ struct iio_dev *indio_dev;
+ struct rm3100_data *data;
+ unsigned int tmp;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->regmap = regmap;
+
+ mutex_init(&data->lock);
+
+ indio_dev->dev.parent = dev;
+ indio_dev->name = "rm3100";
+ indio_dev->info = &rm3100_info;
+ indio_dev->channels = rm3100_channels;
+ indio_dev->num_channels = ARRAY_SIZE(rm3100_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_TRIGGERED;
+ indio_dev->currentmode = INDIO_DIRECT_MODE;
+
+ if (!irq)
+ data->use_interrupt = false;
+ else {
+ data->use_interrupt = true;
+
+ init_completion(&data->measuring_done);
+ ret = devm_request_threaded_irq(dev,
+ irq,
+ rm3100_irq_handler,
+ rm3100_thread_fn,
+ IRQF_TRIGGER_HIGH |
+ IRQF_ONESHOT,
+ indio_dev->name,
+ indio_dev);
+ if (ret < 0) {
+ dev_err(dev, "request irq line failed.\n");
+ return ret;
+ }
+
+ data->drdy_trig = devm_iio_trigger_alloc(dev, "%s-drdy%d",
+ indio_dev->name,
+ indio_dev->id);
+ if (!data->drdy_trig)
+ return -ENOMEM;
+
+ data->drdy_trig->dev.parent = dev;
+ ret = devm_iio_trigger_register(dev, data->drdy_trig);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ &iio_pollfunc_store_time,
+ rm3100_trigger_handler,
+ &rm3100_buffer_ops);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, RM3100_REG_TMRC, &tmp);
+ if (ret < 0)
+ return ret;
+ /* Initializing max wait time, which is double conversion time. */
+ data->conversion_time = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][2]
+ * 2;
+
+ /* Cycle count values may not be what we want. */
+ if ((tmp - RM3100_TMRC_OFFSET) == 0)
+ rm3100_set_cycle_count(data, 100);
+ else
+ rm3100_set_cycle_count(data, 200);
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_GPL(rm3100_common_probe);
+
+MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
+MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer i2c driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/magnetometer/rm3100-i2c.c b/drivers/iio/magnetometer/rm3100-i2c.c
new file mode 100644
index 000000000000..1ac622c6d6c9
--- /dev/null
+++ b/drivers/iio/magnetometer/rm3100-i2c.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for PNI RM3100 3-axis geomagnetic sensor on a i2c bus.
+ *
+ * Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
+ *
+ * i2c slave address: 0x20 + SA1 << 1 + SA0.
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+
+#include "rm3100.h"
+
+static const struct regmap_config rm3100_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .rd_table = &rm3100_readable_table,
+ .wr_table = &rm3100_writable_table,
+ .volatile_table = &rm3100_volatile_table,
+
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int rm3100_probe(struct i2c_client *client)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_i2c(client, &rm3100_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return rm3100_common_probe(&client->dev, regmap, client->irq);
+}
+
+static const struct of_device_id rm3100_dt_match[] = {
+ { .compatible = "pni,rm3100", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rm3100_dt_match);
+
+static struct i2c_driver rm3100_driver = {
+ .driver = {
+ .name = "rm3100-i2c",
+ .of_match_table = rm3100_dt_match,
+ },
+ .probe_new = rm3100_probe,
+};
+module_i2c_driver(rm3100_driver);
+
+MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
+MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer i2c driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/magnetometer/rm3100-spi.c b/drivers/iio/magnetometer/rm3100-spi.c
new file mode 100644
index 000000000000..65d5eb9e4f5e
--- /dev/null
+++ b/drivers/iio/magnetometer/rm3100-spi.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for PNI RM3100 3-axis geomagnetic sensor on a spi bus.
+ *
+ * Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include "rm3100.h"
+
+static const struct regmap_config rm3100_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .rd_table = &rm3100_readable_table,
+ .wr_table = &rm3100_writable_table,
+ .volatile_table = &rm3100_volatile_table,
+
+ .read_flag_mask = 0x80,
+
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int rm3100_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+ int ret;
+
+ /* Actually this device supports both mode 0 and mode 3. */
+ spi->mode = SPI_MODE_0;
+ /* Data rates cannot exceed 1Mbits. */
+ spi->max_speed_hz = 1000000;
+ spi->bits_per_word = 8;
+ ret = spi_setup(spi);
+ if (ret)
+ return ret;
+
+ regmap = devm_regmap_init_spi(spi, &rm3100_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return rm3100_common_probe(&spi->dev, regmap, spi->irq);
+}
+
+static const struct of_device_id rm3100_dt_match[] = {
+ { .compatible = "pni,rm3100", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rm3100_dt_match);
+
+static struct spi_driver rm3100_driver = {
+ .driver = {
+ .name = "rm3100-spi",
+ .of_match_table = rm3100_dt_match,
+ },
+ .probe = rm3100_probe,
+};
+module_spi_driver(rm3100_driver);
+
+MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
+MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer spi driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/magnetometer/rm3100.h b/drivers/iio/magnetometer/rm3100.h
new file mode 100644
index 000000000000..c3508218bc77
--- /dev/null
+++ b/drivers/iio/magnetometer/rm3100.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
+ */
+
+#ifndef RM3100_CORE_H
+#define RM3100_CORE_H
+
+#include <linux/regmap.h>
+
+extern const struct regmap_access_table rm3100_readable_table;
+extern const struct regmap_access_table rm3100_writable_table;
+extern const struct regmap_access_table rm3100_volatile_table;
+
+int rm3100_common_probe(struct device *dev, struct regmap *regmap, int irq);
+
+#endif /* RM3100_CORE_H */
diff --git a/drivers/iio/magnetometer/st_magn.h b/drivers/iio/magnetometer/st_magn.h
index 8fe51ce427bd..bc14ad4f1b26 100644
--- a/drivers/iio/magnetometer/st_magn.h
+++ b/drivers/iio/magnetometer/st_magn.h
@@ -20,6 +20,7 @@
#define LIS3MDL_MAGN_DEV_NAME "lis3mdl"
#define LSM303AGR_MAGN_DEV_NAME "lsm303agr_magn"
#define LIS2MDL_MAGN_DEV_NAME "lis2mdl"
+#define LSM9DS1_MAGN_DEV_NAME "lsm9ds1_magn"
int st_magn_common_probe(struct iio_dev *indio_dev);
void st_magn_common_remove(struct iio_dev *indio_dev);
diff --git a/drivers/iio/magnetometer/st_magn_core.c b/drivers/iio/magnetometer/st_magn_core.c
index 72f6d1335a04..5d056bdb3b37 100644
--- a/drivers/iio/magnetometer/st_magn_core.c
+++ b/drivers/iio/magnetometer/st_magn_core.c
@@ -29,9 +29,9 @@
#define ST_MAGN_NUMBER_DATA_CHANNELS 3
/* DEFAULT VALUE FOR SENSORS */
-#define ST_MAGN_DEFAULT_OUT_X_H_ADDR 0X03
-#define ST_MAGN_DEFAULT_OUT_Y_H_ADDR 0X07
-#define ST_MAGN_DEFAULT_OUT_Z_H_ADDR 0X05
+#define ST_MAGN_DEFAULT_OUT_X_H_ADDR 0x03
+#define ST_MAGN_DEFAULT_OUT_Y_H_ADDR 0x07
+#define ST_MAGN_DEFAULT_OUT_Z_H_ADDR 0x05
/* FULLSCALE */
#define ST_MAGN_FS_AVL_1300MG 1300
@@ -267,6 +267,7 @@ static const struct st_sensor_settings st_magn_sensors_settings[] = {
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
.sensors_supported = {
[0] = LIS3MDL_MAGN_DEV_NAME,
+ [1] = LSM9DS1_MAGN_DEV_NAME,
},
.ch = (struct iio_chan_spec *)st_magn_2_16bit_channels,
.odr = {
@@ -315,6 +316,10 @@ static const struct st_sensor_settings st_magn_sensors_settings[] = {
},
},
},
+ .bdu = {
+ .addr = 0x24,
+ .mask = 0x40,
+ },
.drdy_irq = {
/* drdy line is routed drdy pin */
.stat_drdy = {
diff --git a/drivers/iio/magnetometer/st_magn_i2c.c b/drivers/iio/magnetometer/st_magn_i2c.c
index feaa28cf6a77..68650f5f5c19 100644
--- a/drivers/iio/magnetometer/st_magn_i2c.c
+++ b/drivers/iio/magnetometer/st_magn_i2c.c
@@ -44,6 +44,10 @@ static const struct of_device_id st_magn_of_match[] = {
.compatible = "st,lis2mdl",
.data = LIS2MDL_MAGN_DEV_NAME,
},
+ {
+ .compatible = "st,lsm9ds1-magn",
+ .data = LSM9DS1_MAGN_DEV_NAME,
+ },
{},
};
MODULE_DEVICE_TABLE(of, st_magn_of_match);
@@ -90,6 +94,7 @@ static const struct i2c_device_id st_magn_id_table[] = {
{ LIS3MDL_MAGN_DEV_NAME },
{ LSM303AGR_MAGN_DEV_NAME },
{ LIS2MDL_MAGN_DEV_NAME },
+ { LSM9DS1_MAGN_DEV_NAME },
{},
};
MODULE_DEVICE_TABLE(i2c, st_magn_id_table);
diff --git a/drivers/iio/magnetometer/st_magn_spi.c b/drivers/iio/magnetometer/st_magn_spi.c
index 7b7cd08fcc32..cb05fcd9ddfe 100644
--- a/drivers/iio/magnetometer/st_magn_spi.c
+++ b/drivers/iio/magnetometer/st_magn_spi.c
@@ -23,6 +23,8 @@
* For new single-chip sensors use <device_name> as compatible string.
* For old single-chip devices keep <device_name>-magn to maintain
* compatibility
+ * For multi-chip devices, use <device_name>-magn to distinguish which
+ * capability is being used
*/
static const struct of_device_id st_magn_of_match[] = {
{
@@ -37,6 +39,10 @@ static const struct of_device_id st_magn_of_match[] = {
.compatible = "st,lis2mdl",
.data = LIS2MDL_MAGN_DEV_NAME,
},
+ {
+ .compatible = "st,lsm9ds1-magn",
+ .data = LSM9DS1_MAGN_DEV_NAME,
+ },
{}
};
MODULE_DEVICE_TABLE(of, st_magn_of_match);
@@ -79,6 +85,7 @@ static const struct spi_device_id st_magn_id_table[] = {
{ LIS3MDL_MAGN_DEV_NAME },
{ LSM303AGR_MAGN_DEV_NAME },
{ LIS2MDL_MAGN_DEV_NAME },
+ { LSM9DS1_MAGN_DEV_NAME },
{},
};
MODULE_DEVICE_TABLE(spi, st_magn_id_table);
diff --git a/drivers/iio/potentiometer/Kconfig b/drivers/iio/potentiometer/Kconfig
index 79ec2eba4969..6303cbe79903 100644
--- a/drivers/iio/potentiometer/Kconfig
+++ b/drivers/iio/potentiometer/Kconfig
@@ -90,6 +90,18 @@ config MCP4531
To compile this driver as a module, choose M here: the
module will be called mcp4531.
+config MCP41010
+ tristate "Microchip MCP41xxx/MCP42xxx Digital Potentiometer driver"
+ depends on SPI
+ help
+ Say yes here to build support for the Microchip
+ MCP41010, MCP41050, MCP41100,
+ MCP42010, MCP42050, MCP42100
+ digital potentiometer chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mcp41010.
+
config TPL0102
tristate "Texas Instruments digital potentiometer driver"
depends on I2C
diff --git a/drivers/iio/potentiometer/Makefile b/drivers/iio/potentiometer/Makefile
index 4af657883c3f..8ff55138cf12 100644
--- a/drivers/iio/potentiometer/Makefile
+++ b/drivers/iio/potentiometer/Makefile
@@ -11,4 +11,5 @@ obj-$(CONFIG_MAX5487) += max5487.o
obj-$(CONFIG_MCP4018) += mcp4018.o
obj-$(CONFIG_MCP4131) += mcp4131.o
obj-$(CONFIG_MCP4531) += mcp4531.o
+obj-$(CONFIG_MCP41010) += mcp41010.o
obj-$(CONFIG_TPL0102) += tpl0102.o
diff --git a/drivers/iio/potentiometer/mcp41010.c b/drivers/iio/potentiometer/mcp41010.c
new file mode 100644
index 000000000000..2368b39debf5
--- /dev/null
+++ b/drivers/iio/potentiometer/mcp41010.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Industrial I/O driver for Microchip digital potentiometers
+ *
+ * Copyright (c) 2018 Chris Coffey <cmc@babblebit.net>
+ * Based on: Slawomir Stepien's code from mcp4131.c
+ *
+ * Datasheet: http://ww1.microchip.com/downloads/en/devicedoc/11195c.pdf
+ *
+ * DEVID #Wipers #Positions Resistance (kOhm)
+ * mcp41010 1 256 10
+ * mcp41050 1 256 50
+ * mcp41100 1 256 100
+ * mcp42010 2 256 10
+ * mcp42050 2 256 50
+ * mcp42100 2 256 100
+ */
+
+#include <linux/cache.h>
+#include <linux/err.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+
+#define MCP41010_MAX_WIPERS 2
+#define MCP41010_WRITE BIT(4)
+#define MCP41010_WIPER_MAX 255
+#define MCP41010_WIPER_CHANNEL BIT(0)
+
+struct mcp41010_cfg {
+ char name[16];
+ int wipers;
+ int kohms;
+};
+
+enum mcp41010_type {
+ MCP41010,
+ MCP41050,
+ MCP41100,
+ MCP42010,
+ MCP42050,
+ MCP42100,
+};
+
+static const struct mcp41010_cfg mcp41010_cfg[] = {
+ [MCP41010] = { .name = "mcp41010", .wipers = 1, .kohms = 10, },
+ [MCP41050] = { .name = "mcp41050", .wipers = 1, .kohms = 50, },
+ [MCP41100] = { .name = "mcp41100", .wipers = 1, .kohms = 100, },
+ [MCP42010] = { .name = "mcp42010", .wipers = 2, .kohms = 10, },
+ [MCP42050] = { .name = "mcp42050", .wipers = 2, .kohms = 50, },
+ [MCP42100] = { .name = "mcp42100", .wipers = 2, .kohms = 100, },
+};
+
+struct mcp41010_data {
+ struct spi_device *spi;
+ const struct mcp41010_cfg *cfg;
+ struct mutex lock; /* Protect write sequences */
+ unsigned int value[MCP41010_MAX_WIPERS]; /* Cache wiper values */
+ u8 buf[2] ____cacheline_aligned;
+};
+
+#define MCP41010_CHANNEL(ch) { \
+ .type = IIO_RESISTANCE, \
+ .indexed = 1, \
+ .output = 1, \
+ .channel = (ch), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+}
+
+static const struct iio_chan_spec mcp41010_channels[] = {
+ MCP41010_CHANNEL(0),
+ MCP41010_CHANNEL(1),
+};
+
+static int mcp41010_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mcp41010_data *data = iio_priv(indio_dev);
+ int channel = chan->channel;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *val = data->value[channel];
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = 1000 * data->cfg->kohms;
+ *val2 = MCP41010_WIPER_MAX;
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+}
+
+static int mcp41010_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ int err;
+ struct mcp41010_data *data = iio_priv(indio_dev);
+ int channel = chan->channel;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ if (val > MCP41010_WIPER_MAX || val < 0)
+ return -EINVAL;
+
+ mutex_lock(&data->lock);
+
+ data->buf[0] = MCP41010_WIPER_CHANNEL << channel;
+ data->buf[0] |= MCP41010_WRITE;
+ data->buf[1] = val & 0xff;
+
+ err = spi_write(data->spi, data->buf, sizeof(data->buf));
+ if (!err)
+ data->value[channel] = val;
+
+ mutex_unlock(&data->lock);
+
+ return err;
+}
+
+static const struct iio_info mcp41010_info = {
+ .read_raw = mcp41010_read_raw,
+ .write_raw = mcp41010_write_raw,
+};
+
+static int mcp41010_probe(struct spi_device *spi)
+{
+ int err;
+ struct device *dev = &spi->dev;
+ struct mcp41010_data *data;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ spi_set_drvdata(spi, indio_dev);
+ data->spi = spi;
+ data->cfg = of_device_get_match_data(&spi->dev);
+ if (!data->cfg)
+ data->cfg = &mcp41010_cfg[spi_get_device_id(spi)->driver_data];
+
+ mutex_init(&data->lock);
+
+ indio_dev->dev.parent = dev;
+ indio_dev->info = &mcp41010_info;
+ indio_dev->channels = mcp41010_channels;
+ indio_dev->num_channels = data->cfg->wipers;
+ indio_dev->name = data->cfg->name;
+
+ err = devm_iio_device_register(dev, indio_dev);
+ if (err)
+ dev_info(&spi->dev, "Unable to register %s\n", indio_dev->name);
+
+ return err;
+}
+
+static const struct of_device_id mcp41010_match[] = {
+ { .compatible = "microchip,mcp41010", .data = &mcp41010_cfg[MCP41010] },
+ { .compatible = "microchip,mcp41050", .data = &mcp41010_cfg[MCP41050] },
+ { .compatible = "microchip,mcp41100", .data = &mcp41010_cfg[MCP41100] },
+ { .compatible = "microchip,mcp42010", .data = &mcp41010_cfg[MCP42010] },
+ { .compatible = "microchip,mcp42050", .data = &mcp41010_cfg[MCP42050] },
+ { .compatible = "microchip,mcp42100", .data = &mcp41010_cfg[MCP42100] },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mcp41010_match);
+
+static const struct spi_device_id mcp41010_id[] = {
+ { "mcp41010", MCP41010 },
+ { "mcp41050", MCP41050 },
+ { "mcp41100", MCP41100 },
+ { "mcp42010", MCP42010 },
+ { "mcp42050", MCP42050 },
+ { "mcp42100", MCP42100 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, mcp41010_id);
+
+static struct spi_driver mcp41010_driver = {
+ .driver = {
+ .name = "mcp41010",
+ .of_match_table = mcp41010_match,
+ },
+ .probe = mcp41010_probe,
+ .id_table = mcp41010_id,
+};
+
+module_spi_driver(mcp41010_driver);
+
+MODULE_AUTHOR("Chris Coffey <cmc@babblebit.net>");
+MODULE_DESCRIPTION("MCP41010 digital potentiometer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/potentiometer/mcp4131.c b/drivers/iio/potentiometer/mcp4131.c
index b3e30db246cc..efe035ce010d 100644
--- a/drivers/iio/potentiometer/mcp4131.c
+++ b/drivers/iio/potentiometer/mcp4131.c
@@ -42,6 +42,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
+#include <linux/of_device.h>
#include <linux/spi/spi.h>
#define MCP4131_WRITE (0x00 << 2)
@@ -243,7 +244,7 @@ static int mcp4131_probe(struct spi_device *spi)
{
int err;
struct device *dev = &spi->dev;
- unsigned long devid = spi_get_device_id(spi)->driver_data;
+ unsigned long devid;
struct mcp4131_data *data;
struct iio_dev *indio_dev;
@@ -254,7 +255,11 @@ static int mcp4131_probe(struct spi_device *spi)
data = iio_priv(indio_dev);
spi_set_drvdata(spi, indio_dev);
data->spi = spi;
- data->cfg = &mcp4131_cfg[devid];
+ data->cfg = of_device_get_match_data(&spi->dev);
+ if (!data->cfg) {
+ devid = spi_get_device_id(spi)->driver_data;
+ data->cfg = &mcp4131_cfg[devid];
+ }
mutex_init(&data->lock);
@@ -273,7 +278,6 @@ static int mcp4131_probe(struct spi_device *spi)
return 0;
}
-#if defined(CONFIG_OF)
static const struct of_device_id mcp4131_dt_ids[] = {
{ .compatible = "microchip,mcp4131-502",
.data = &mcp4131_cfg[MCP413x_502] },
@@ -406,7 +410,6 @@ static const struct of_device_id mcp4131_dt_ids[] = {
{}
};
MODULE_DEVICE_TABLE(of, mcp4131_dt_ids);
-#endif /* CONFIG_OF */
static const struct spi_device_id mcp4131_id[] = {
{ "mcp4131-502", MCP413x_502 },
diff --git a/drivers/iio/potentiometer/tpl0102.c b/drivers/iio/potentiometer/tpl0102.c
index ca1cce58fe20..a0a07e47f13f 100644
--- a/drivers/iio/potentiometer/tpl0102.c
+++ b/drivers/iio/potentiometer/tpl0102.c
@@ -15,7 +15,7 @@
struct tpl0102_cfg {
int wipers;
- int max_pos;
+ int avail[3];
int kohms;
};
@@ -28,16 +28,16 @@ enum tpl0102_type {
static const struct tpl0102_cfg tpl0102_cfg[] = {
/* on-semiconductor parts */
- [CAT5140_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, },
- [CAT5140_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, },
+ [CAT5140_503] = { .wipers = 1, .avail = { 0, 1, 255 }, .kohms = 50, },
+ [CAT5140_104] = { .wipers = 1, .avail = { 0, 1, 255 }, .kohms = 100, },
/* ti parts */
- [TPL0102_104] = { .wipers = 2, .max_pos = 256, .kohms = 100 },
- [TPL0401_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, },
+ [TPL0102_104] = { .wipers = 2, .avail = { 0, 1, 255 }, .kohms = 100 },
+ [TPL0401_103] = { .wipers = 1, .avail = { 0, 1, 127 }, .kohms = 10, },
};
struct tpl0102_data {
struct regmap *regmap;
- unsigned long devid;
+ const struct tpl0102_cfg *cfg;
};
static const struct regmap_config tpl0102_regmap_config = {
@@ -52,6 +52,7 @@ static const struct regmap_config tpl0102_regmap_config = {
.channel = (ch), \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), \
}
static const struct iio_chan_spec tpl0102_channels[] = {
@@ -72,14 +73,32 @@ static int tpl0102_read_raw(struct iio_dev *indio_dev,
return ret ? ret : IIO_VAL_INT;
}
case IIO_CHAN_INFO_SCALE:
- *val = 1000 * tpl0102_cfg[data->devid].kohms;
- *val2 = tpl0102_cfg[data->devid].max_pos;
+ *val = 1000 * data->cfg->kohms;
+ *val2 = data->cfg->avail[2] + 1;
return IIO_VAL_FRACTIONAL;
}
return -EINVAL;
}
+static int tpl0102_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct tpl0102_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *length = ARRAY_SIZE(data->cfg->avail);
+ *vals = data->cfg->avail;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
+ }
+
+ return -EINVAL;
+}
+
static int tpl0102_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
@@ -89,7 +108,7 @@ static int tpl0102_write_raw(struct iio_dev *indio_dev,
if (mask != IIO_CHAN_INFO_RAW)
return -EINVAL;
- if (val >= tpl0102_cfg[data->devid].max_pos || val < 0)
+ if (val > data->cfg->avail[2] || val < 0)
return -EINVAL;
return regmap_write(data->regmap, chan->channel, val);
@@ -97,6 +116,7 @@ static int tpl0102_write_raw(struct iio_dev *indio_dev,
static const struct iio_info tpl0102_info = {
.read_raw = tpl0102_read_raw,
+ .read_avail = tpl0102_read_avail,
.write_raw = tpl0102_write_raw,
};
@@ -113,7 +133,7 @@ static int tpl0102_probe(struct i2c_client *client,
data = iio_priv(indio_dev);
i2c_set_clientdata(client, indio_dev);
- data->devid = id->driver_data;
+ data->cfg = &tpl0102_cfg[id->driver_data];
data->regmap = devm_regmap_init_i2c(client, &tpl0102_regmap_config);
if (IS_ERR(data->regmap)) {
dev_err(dev, "regmap initialization failed\n");
@@ -123,7 +143,7 @@ static int tpl0102_probe(struct i2c_client *client,
indio_dev->dev.parent = dev;
indio_dev->info = &tpl0102_info;
indio_dev->channels = tpl0102_channels;
- indio_dev->num_channels = tpl0102_cfg[data->devid].wipers;
+ indio_dev->num_channels = data->cfg->wipers;
indio_dev->name = client->name;
return devm_iio_device_register(dev, indio_dev);
diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig
index eaa7cfcb4c2a..efeb89f3df71 100644
--- a/drivers/iio/pressure/Kconfig
+++ b/drivers/iio/pressure/Kconfig
@@ -165,7 +165,7 @@ config IIO_ST_PRESS
select IIO_TRIGGERED_BUFFER if (IIO_BUFFER)
help
Say yes here to build support for STMicroelectronics pressure
- sensors: LPS001WP, LPS25H, LPS331AP, LPS22HB.
+ sensors: LPS001WP, LPS25H, LPS331AP, LPS22HB, LPS22HH.
This driver can also be built as a module. If so, these modules
will be created:
diff --git a/drivers/iio/pressure/st_pressure.h b/drivers/iio/pressure/st_pressure.h
index e67eb0d971bf..57946605f3ba 100644
--- a/drivers/iio/pressure/st_pressure.h
+++ b/drivers/iio/pressure/st_pressure.h
@@ -21,6 +21,7 @@ enum st_press_type {
LPS22HB,
LPS33HW,
LPS35HW,
+ LPS22HH,
ST_PRESS_MAX,
};
@@ -30,6 +31,7 @@ enum st_press_type {
#define LPS22HB_PRESS_DEV_NAME "lps22hb"
#define LPS33HW_PRESS_DEV_NAME "lps33hw"
#define LPS35HW_PRESS_DEV_NAME "lps35hw"
+#define LPS22HH_PRESS_DEV_NAME "lps22hh"
/**
* struct st_sensors_platform_data - default press platform data
diff --git a/drivers/iio/pressure/st_pressure_core.c b/drivers/iio/pressure/st_pressure_core.c
index 4ddb6cf7d401..38dcdb7c000e 100644
--- a/drivers/iio/pressure/st_pressure_core.c
+++ b/drivers/iio/pressure/st_pressure_core.c
@@ -492,6 +492,75 @@ static const struct st_sensor_settings st_press_sensors_settings[] = {
.multi_read_bit = false,
.bootime = 2,
},
+ {
+ /*
+ * CUSTOM VALUES FOR LPS22HH SENSOR
+ * See LPS22HH datasheet:
+ * http://www2.st.com/resource/en/datasheet/lps22hh.pdf
+ */
+ .wai = 0xb3,
+ .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
+ .sensors_supported = {
+ [0] = LPS22HH_PRESS_DEV_NAME,
+ },
+ .ch = (struct iio_chan_spec *)st_press_lps22hb_channels,
+ .num_ch = ARRAY_SIZE(st_press_lps22hb_channels),
+ .odr = {
+ .addr = 0x10,
+ .mask = 0x70,
+ .odr_avl = {
+ { .hz = 1, .value = 0x01 },
+ { .hz = 10, .value = 0x02 },
+ { .hz = 25, .value = 0x03 },
+ { .hz = 50, .value = 0x04 },
+ { .hz = 75, .value = 0x05 },
+ { .hz = 100, .value = 0x06 },
+ { .hz = 200, .value = 0x07 },
+ },
+ },
+ .pw = {
+ .addr = 0x10,
+ .mask = 0x70,
+ .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
+ },
+ .fs = {
+ .fs_avl = {
+ /*
+ * Pressure and temperature sensitivity values
+ * as defined in table 3 of LPS22HH datasheet.
+ */
+ [0] = {
+ .num = ST_PRESS_FS_AVL_1260MB,
+ .gain = ST_PRESS_KPASCAL_NANO_SCALE,
+ .gain2 = ST_PRESS_LPS22HB_LSB_PER_CELSIUS,
+ },
+ },
+ },
+ .bdu = {
+ .addr = 0x10,
+ .mask = BIT(1),
+ },
+ .drdy_irq = {
+ .int1 = {
+ .addr = 0x12,
+ .mask = BIT(2),
+ .addr_od = 0x11,
+ .mask_od = BIT(5),
+ },
+ .addr_ihl = 0x11,
+ .mask_ihl = BIT(6),
+ .stat_drdy = {
+ .addr = ST_SENSORS_DEFAULT_STAT_ADDR,
+ .mask = 0x03,
+ },
+ },
+ .sim = {
+ .addr = 0x10,
+ .value = BIT(0),
+ },
+ .multi_read_bit = false,
+ .bootime = 2,
+ },
};
static int st_press_write_raw(struct iio_dev *indio_dev,
diff --git a/drivers/iio/pressure/st_pressure_i2c.c b/drivers/iio/pressure/st_pressure_i2c.c
index 2026a1012012..a60849dd4ea7 100644
--- a/drivers/iio/pressure/st_pressure_i2c.c
+++ b/drivers/iio/pressure/st_pressure_i2c.c
@@ -45,6 +45,10 @@ static const struct of_device_id st_press_of_match[] = {
.compatible = "st,lps35hw",
.data = LPS35HW_PRESS_DEV_NAME,
},
+ {
+ .compatible = "st,lps22hh",
+ .data = LPS22HH_PRESS_DEV_NAME,
+ },
{},
};
MODULE_DEVICE_TABLE(of, st_press_of_match);
@@ -69,6 +73,7 @@ static const struct i2c_device_id st_press_id_table[] = {
{ LPS22HB_PRESS_DEV_NAME, LPS22HB },
{ LPS33HW_PRESS_DEV_NAME, LPS33HW },
{ LPS35HW_PRESS_DEV_NAME, LPS35HW },
+ { LPS22HH_PRESS_DEV_NAME, LPS22HH },
{},
};
MODULE_DEVICE_TABLE(i2c, st_press_id_table);
diff --git a/drivers/iio/pressure/st_pressure_spi.c b/drivers/iio/pressure/st_pressure_spi.c
index 9a3441b128e7..79a12ed46e54 100644
--- a/drivers/iio/pressure/st_pressure_spi.c
+++ b/drivers/iio/pressure/st_pressure_spi.c
@@ -49,6 +49,10 @@ static const struct of_device_id st_press_of_match[] = {
.compatible = "st,lps35hw",
.data = LPS35HW_PRESS_DEV_NAME,
},
+ {
+ .compatible = "st,lps22hh",
+ .data = LPS22HH_PRESS_DEV_NAME,
+ },
{},
};
MODULE_DEVICE_TABLE(of, st_press_of_match);
@@ -93,6 +97,7 @@ static const struct spi_device_id st_press_id_table[] = {
{ LPS22HB_PRESS_DEV_NAME },
{ LPS33HW_PRESS_DEV_NAME },
{ LPS35HW_PRESS_DEV_NAME },
+ { LPS22HH_PRESS_DEV_NAME },
{},
};
MODULE_DEVICE_TABLE(spi, st_press_id_table);
diff --git a/drivers/iio/resolver/Kconfig b/drivers/iio/resolver/Kconfig
index 2ced9f22aa70..786801be54f6 100644
--- a/drivers/iio/resolver/Kconfig
+++ b/drivers/iio/resolver/Kconfig
@@ -3,6 +3,16 @@
#
menu "Resolver to digital converters"
+config AD2S90
+ tristate "Analog Devices ad2s90 driver"
+ depends on SPI
+ help
+ Say yes here to build support for Analog Devices spi resolver
+ to digital converters, ad2s90, provides direct access via sysfs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad2s90.
+
config AD2S1200
tristate "Analog Devices ad2s1200/ad2s1205 driver"
depends on SPI
diff --git a/drivers/iio/resolver/Makefile b/drivers/iio/resolver/Makefile
index 4e1dccae07e7..398d82d50028 100644
--- a/drivers/iio/resolver/Makefile
+++ b/drivers/iio/resolver/Makefile
@@ -2,4 +2,5 @@
# Makefile for Resolver/Synchro drivers
#
+obj-$(CONFIG_AD2S90) += ad2s90.o
obj-$(CONFIG_AD2S1200) += ad2s1200.o
diff --git a/drivers/iio/resolver/ad2s90.c b/drivers/iio/resolver/ad2s90.c
new file mode 100644
index 000000000000..a41f5cb10da5
--- /dev/null
+++ b/drivers/iio/resolver/ad2s90.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ad2s90.c simple support for the ADI Resolver to Digital Converters: AD2S90
+ *
+ * Copyright (c) 2010-2010 Analog Devices Inc.
+ */
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+/*
+ * Although chip's max frequency is 2Mhz, it needs 600ns between CS and the
+ * first falling edge of SCLK, so frequency should be at most 1 / (2 * 6e-7)
+ */
+#define AD2S90_MAX_SPI_FREQ_HZ 830000
+
+struct ad2s90_state {
+ struct mutex lock; /* lock to protect rx buffer */
+ struct spi_device *sdev;
+ u8 rx[2] ____cacheline_aligned;
+};
+
+static int ad2s90_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long m)
+{
+ int ret;
+ struct ad2s90_state *st = iio_priv(indio_dev);
+
+ if (chan->type != IIO_ANGL)
+ return -EINVAL;
+
+ switch (m) {
+ case IIO_CHAN_INFO_SCALE:
+ /* 2 * Pi / 2^12 */
+ *val = 6283; /* mV */
+ *val2 = 12;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&st->lock);
+ ret = spi_read(st->sdev, st->rx, 2);
+ if (ret < 0) {
+ mutex_unlock(&st->lock);
+ return ret;
+ }
+ *val = (((u16)(st->rx[0])) << 4) | ((st->rx[1] & 0xF0) >> 4);
+
+ mutex_unlock(&st->lock);
+
+ return IIO_VAL_INT;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info ad2s90_info = {
+ .read_raw = ad2s90_read_raw,
+};
+
+static const struct iio_chan_spec ad2s90_chan = {
+ .type = IIO_ANGL,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+};
+
+static int ad2s90_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct ad2s90_state *st;
+
+ if (spi->max_speed_hz > AD2S90_MAX_SPI_FREQ_HZ) {
+ dev_err(&spi->dev, "SPI CLK, %d Hz exceeds %d Hz\n",
+ spi->max_speed_hz, AD2S90_MAX_SPI_FREQ_HZ);
+ return -EINVAL;
+ }
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ st = iio_priv(indio_dev);
+ spi_set_drvdata(spi, indio_dev);
+
+ mutex_init(&st->lock);
+ st->sdev = spi;
+ indio_dev->dev.parent = &spi->dev;
+ indio_dev->info = &ad2s90_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = &ad2s90_chan;
+ indio_dev->num_channels = 1;
+ indio_dev->name = spi_get_device_id(spi)->name;
+
+ return devm_iio_device_register(indio_dev->dev.parent, indio_dev);
+}
+
+static const struct of_device_id ad2s90_of_match[] = {
+ { .compatible = "adi,ad2s90", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ad2s90_of_match);
+
+static const struct spi_device_id ad2s90_id[] = {
+ { "ad2s90" },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ad2s90_id);
+
+static struct spi_driver ad2s90_driver = {
+ .driver = {
+ .name = "ad2s90",
+ .of_match_table = ad2s90_of_match,
+ },
+ .probe = ad2s90_probe,
+ .id_table = ad2s90_id,
+};
+module_spi_driver(ad2s90_driver);
+
+MODULE_AUTHOR("Graff Yang <graff.yang@gmail.com>");
+MODULE_DESCRIPTION("Analog Devices AD2S90 Resolver to Digital SPI driver");
+MODULE_LICENSE("GPL v2");