From c6988eb0e7d2ae1c892874d92ee9a7e8a380896d Mon Sep 17 00:00:00 2001 From: Karim Eshapa Date: Mon, 25 Jun 2018 21:11:23 +0200 Subject: [PATCH 01/39] staging:iio:impedance-analyzer:ad5933: Macro replacement Cleanups. Doing some macro replacement to start an array of structures so it can be reused by manipulating it with different values. Signed-off-by: Karim Eshapa Signed-off-by: Jonathan Cameron --- .../staging/iio/impedance-analyzer/ad5933.c | 57 +++++++------------ 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/drivers/staging/iio/impedance-analyzer/ad5933.c b/drivers/staging/iio/impedance-analyzer/ad5933.c index 3bcf49466361..14df89510396 100644 --- a/drivers/staging/iio/impedance-analyzer/ad5933.c +++ b/drivers/staging/iio/impedance-analyzer/ad5933.c @@ -116,45 +116,26 @@ static struct ad5933_platform_data ad5933_default_pdata = { .vref_mv = 3300, }; +#define AD5933_CHANNEL(_type, _extend_name, _info_mask_separate, _address, \ + _scan_index, _realbits) { \ + .type = (_type), \ + .extend_name = (_extend_name), \ + .info_mask_separate = (_info_mask_separate), \ + .address = (_address), \ + .scan_index = (_scan_index), \ + .scan_type = { \ + .sign = 's', \ + .realbits = (_realbits), \ + .storagebits = 16, \ + }, \ +} + static const struct iio_chan_spec ad5933_channels[] = { - { - .type = IIO_TEMP, - .indexed = 1, - .channel = 0, - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | - BIT(IIO_CHAN_INFO_SCALE), - .address = AD5933_REG_TEMP_DATA, - .scan_index = -1, - .scan_type = { - .sign = 's', - .realbits = 14, - .storagebits = 16, - }, - }, { /* Ring Channels */ - .type = IIO_VOLTAGE, - .indexed = 1, - .channel = 0, - .extend_name = "real", - .address = AD5933_REG_REAL_DATA, - .scan_index = 0, - .scan_type = { - .sign = 's', - .realbits = 16, - .storagebits = 16, - }, - }, { - .type = IIO_VOLTAGE, - .indexed = 1, - .channel = 0, - .extend_name = "imag", - .address = AD5933_REG_IMAG_DATA, - .scan_index = 1, - .scan_type = { - .sign = 's', - .realbits = 16, - .storagebits = 16, - }, - }, + AD5933_CHANNEL(IIO_TEMP, NULL, BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), AD5933_REG_TEMP_DATA, -1, 14), + /* Ring Channels */ + AD5933_CHANNEL(IIO_VOLTAGE, "real", 0, AD5933_REG_REAL_DATA, 0, 16), + AD5933_CHANNEL(IIO_VOLTAGE, "imag", 0, AD5933_REG_IMAG_DATA, 1, 16), }; static int ad5933_i2c_write(struct i2c_client *client, u8 reg, u8 len, u8 *data) From 4b5de1fa97b9932db0e989afe7dba3029fbd4545 Mon Sep 17 00:00:00 2001 From: Akinobu Mita Date: Tue, 26 Jun 2018 00:22:40 +0900 Subject: [PATCH 02/39] iio: accel: adxl345: add link to datasheet Add a link to the ADXL345 datasheet Cc: Eva Rachel Retuya Cc: Andy Shevchenko Cc: Jonathan Cameron Signed-off-by: Akinobu Mita Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345_core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index 7251d0e63d74..8e0d56b89201 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -6,6 +6,8 @@ * 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. + * + * Datasheet: http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf */ #include From 9048f1f18a70a01eaa3c8e7166fdb2538929d780 Mon Sep 17 00:00:00 2001 From: Akinobu Mita Date: Tue, 26 Jun 2018 00:22:41 +0900 Subject: [PATCH 03/39] iio: accel: adxl345: convert address field usage in iio_chan_spec Currently the address field in iio_chan_spec is filled with an accel data register address for the corresponding axis. In preparation for adding calibration offset support, this sets the address field to the index of accel data registers instead of the actual register address. This change makes it easier to access both accel registers and calibration offset registers with fewer lines of code as these are located in X-axis, Y-axis, Z-axis order. Cc: Eva Rachel Retuya Cc: Andy Shevchenko Cc: Jonathan Cameron Signed-off-by: Akinobu Mita Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345_core.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index 8e0d56b89201..7a25840d806f 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -23,6 +23,8 @@ #define ADXL345_REG_DATAX0 0x32 #define ADXL345_REG_DATAY0 0x34 #define ADXL345_REG_DATAZ0 0x36 +#define ADXL345_REG_DATA_AXIS(index) \ + (ADXL345_REG_DATAX0 + (index) * sizeof(__le16)) #define ADXL345_POWER_CTL_MEASURE BIT(3) #define ADXL345_POWER_CTL_STANDBY 0x00 @@ -49,19 +51,19 @@ struct adxl345_data { u8 data_range; }; -#define ADXL345_CHANNEL(reg, axis) { \ +#define ADXL345_CHANNEL(index, axis) { \ .type = IIO_ACCEL, \ .modified = 1, \ .channel2 = IIO_MOD_##axis, \ - .address = reg, \ + .address = index, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ } static const struct iio_chan_spec adxl345_channels[] = { - ADXL345_CHANNEL(ADXL345_REG_DATAX0, X), - ADXL345_CHANNEL(ADXL345_REG_DATAY0, Y), - ADXL345_CHANNEL(ADXL345_REG_DATAZ0, Z), + ADXL345_CHANNEL(0, X), + ADXL345_CHANNEL(1, Y), + ADXL345_CHANNEL(2, Z), }; static int adxl345_read_raw(struct iio_dev *indio_dev, @@ -69,7 +71,7 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long mask) { struct adxl345_data *data = iio_priv(indio_dev); - __le16 regval; + __le16 accel; int ret; switch (mask) { @@ -79,12 +81,13 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, * ADXL345_REG_DATA(X0/Y0/Z0) contain the least significant byte * and ADXL345_REG_DATA(X0/Y0/Z0) + 1 the most significant byte */ - ret = regmap_bulk_read(data->regmap, chan->address, ®val, - sizeof(regval)); + ret = regmap_bulk_read(data->regmap, + ADXL345_REG_DATA_AXIS(chan->address), + &accel, sizeof(accel)); if (ret < 0) return ret; - *val = sign_extend32(le16_to_cpu(regval), 12); + *val = sign_extend32(le16_to_cpu(accel), 12); return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = 0; From 732238e22e4c5497bd76d02adcf5c4fcba6dcb67 Mon Sep 17 00:00:00 2001 From: Akinobu Mita Date: Tue, 26 Jun 2018 00:22:42 +0900 Subject: [PATCH 04/39] iio: accel: adxl345: add calibration offset support The ADXL345 provides the offset adjustment registers for each axis. This adds the iio channel information for the calibraion offsets with that feature. Cc: Eva Rachel Retuya Cc: Andy Shevchenko Cc: Jonathan Cameron Signed-off-by: Akinobu Mita Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345_core.c | 41 +++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index 7a25840d806f..7b29ae8375e9 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -18,6 +18,10 @@ #include "adxl345.h" #define ADXL345_REG_DEVID 0x00 +#define ADXL345_REG_OFSX 0x1e +#define ADXL345_REG_OFSY 0x1f +#define ADXL345_REG_OFSZ 0x20 +#define ADXL345_REG_OFS_AXIS(index) (ADXL345_REG_OFSX + (index)) #define ADXL345_REG_POWER_CTL 0x2D #define ADXL345_REG_DATA_FORMAT 0x31 #define ADXL345_REG_DATAX0 0x32 @@ -56,7 +60,8 @@ struct adxl345_data { .modified = 1, \ .channel2 = IIO_MOD_##axis, \ .address = index, \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ } @@ -72,6 +77,7 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, { struct adxl345_data *data = iio_priv(indio_dev); __le16 accel; + unsigned int regval; int ret; switch (mask) { @@ -94,6 +100,38 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, *val2 = adxl345_uscale; return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + ret = regmap_read(data->regmap, + ADXL345_REG_OFS_AXIS(chan->address), ®val); + if (ret < 0) + return ret; + /* + * 8-bit resolution at +/- 2g, that is 4x accel data scale + * factor + */ + *val = sign_extend32(regval, 7) * 4; + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int adxl345_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adxl345_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + /* + * 8-bit resolution at +/- 2g, that is 4x accel data scale + * factor + */ + return regmap_write(data->regmap, + ADXL345_REG_OFS_AXIS(chan->address), + val / 4); } return -EINVAL; @@ -101,6 +139,7 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, static const struct iio_info adxl345_info = { .read_raw = adxl345_read_raw, + .write_raw = adxl345_write_raw, }; int adxl345_core_probe(struct device *dev, struct regmap *regmap, From d93de07a12e8b00e99ff9d7522c326ba6a5747c0 Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Thu, 21 Jun 2018 11:14:04 +0800 Subject: [PATCH 05/39] dt-bindings: iio: Add Spreadtrum SC27XX PMICs ADC controller documentation This patch adds the binding documentation for Spreadtrum SC27XX series PMICs ADC controller device. Signed-off-by: Baolin Wang Reviewed-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/sprd,sc27xx-adc.txt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/sprd,sc27xx-adc.txt diff --git a/Documentation/devicetree/bindings/iio/adc/sprd,sc27xx-adc.txt b/Documentation/devicetree/bindings/iio/adc/sprd,sc27xx-adc.txt new file mode 100644 index 000000000000..8aad960de50b --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/sprd,sc27xx-adc.txt @@ -0,0 +1,36 @@ +Spreadtrum SC27XX series PMICs ADC binding + +Required properties: +- compatible: Should be one of the following. + "sprd,sc2720-adc" + "sprd,sc2721-adc" + "sprd,sc2723-adc" + "sprd,sc2730-adc" + "sprd,sc2731-adc" +- reg: The address offset of ADC controller. +- interrupt-parent: The interrupt controller. +- interrupts: The interrupt number for the ADC device. +- #io-channel-cells: Number of cells in an IIO specifier. +- hwlocks: Reference to a phandle of a hwlock provider node. + +Example: + + sc2731_pmic: pmic@0 { + compatible = "sprd,sc2731"; + reg = <0>; + spi-max-frequency = <26000000>; + interrupts = ; + interrupt-controller; + #interrupt-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + + pmic_adc: adc@480 { + compatible = "sprd,sc2731-adc"; + reg = <0x480>; + interrupt-parent = <&sc2731_pmic>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + #io-channel-cells = <1>; + hwlocks = <&hwlock 4>; + }; + }; From 5df362a6cf49ca8d2e69a2f68e605310e3c576cb Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Thu, 21 Jun 2018 11:14:05 +0800 Subject: [PATCH 06/39] iio: adc: Add Spreadtrum SC27XX PMICs ADC support The Spreadtrum SC27XX PMICs ADC controller contains 32 channels, which is used to sample voltages with 12 bits conversion. [Baolin Wang did lots of improvements] Signed-off-by: Freeman Liu Signed-off-by: Baolin Wang Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/sc27xx_adc.c | 522 +++++++++++++++++++++++++++++++++++ 3 files changed, 533 insertions(+) create mode 100644 drivers/iio/adc/sc27xx_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 084638e4ddf2..4a754921fb6f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -620,6 +620,16 @@ config ROCKCHIP_SARADC To compile this driver as a module, choose M here: the module will be called rockchip_saradc. +config SC27XX_ADC + tristate "Spreadtrum SC27xx series PMICs ADC" + depends on MFD_SC27XX_PMIC || COMPILE_TEST + help + Say yes here to build support for the integrated ADC inside the + Spreadtrum SC27xx series PMICs. + + This driver can also be built as a module. If so, the module + will be called sc27xx_adc. + config SPEAR_ADC tristate "ST SPEAr ADC" depends on PLAT_SPEAR || COMPILE_TEST diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 28a9423997f3..03db7b578f9c 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o obj-$(CONFIG_QCOM_PM8XXX_XOADC) += qcom-pm8xxx-xoadc.o obj-$(CONFIG_RCAR_GYRO_ADC) += rcar-gyroadc.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o +obj-$(CONFIG_SC27XX_ADC) += sc27xx_adc.o obj-$(CONFIG_SPEAR_ADC) += spear_adc.o obj-$(CONFIG_STX104) += stx104.o obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o diff --git a/drivers/iio/adc/sc27xx_adc.c b/drivers/iio/adc/sc27xx_adc.c new file mode 100644 index 000000000000..2b60efea0c39 --- /dev/null +++ b/drivers/iio/adc/sc27xx_adc.c @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Spreadtrum Communications Inc. + +#include +#include +#include +#include +#include +#include +#include +#include + +/* PMIC global registers definition */ +#define SC27XX_MODULE_EN 0xc08 +#define SC27XX_MODULE_ADC_EN BIT(5) +#define SC27XX_ARM_CLK_EN 0xc10 +#define SC27XX_CLK_ADC_EN BIT(5) +#define SC27XX_CLK_ADC_CLK_EN BIT(6) + +/* ADC controller registers definition */ +#define SC27XX_ADC_CTL 0x0 +#define SC27XX_ADC_CH_CFG 0x4 +#define SC27XX_ADC_DATA 0x4c +#define SC27XX_ADC_INT_EN 0x50 +#define SC27XX_ADC_INT_CLR 0x54 +#define SC27XX_ADC_INT_STS 0x58 +#define SC27XX_ADC_INT_RAW 0x5c + +/* Bits and mask definition for SC27XX_ADC_CTL register */ +#define SC27XX_ADC_EN BIT(0) +#define SC27XX_ADC_CHN_RUN BIT(1) +#define SC27XX_ADC_12BIT_MODE BIT(2) +#define SC27XX_ADC_RUN_NUM_MASK GENMASK(7, 4) +#define SC27XX_ADC_RUN_NUM_SHIFT 4 + +/* Bits and mask definition for SC27XX_ADC_CH_CFG register */ +#define SC27XX_ADC_CHN_ID_MASK GENMASK(4, 0) +#define SC27XX_ADC_SCALE_MASK GENMASK(10, 8) +#define SC27XX_ADC_SCALE_SHIFT 8 + +/* Bits definitions for SC27XX_ADC_INT_EN registers */ +#define SC27XX_ADC_IRQ_EN BIT(0) + +/* Bits definitions for SC27XX_ADC_INT_CLR registers */ +#define SC27XX_ADC_IRQ_CLR BIT(0) + +/* Mask definition for SC27XX_ADC_DATA register */ +#define SC27XX_ADC_DATA_MASK GENMASK(11, 0) + +/* Timeout (ms) for the trylock of hardware spinlocks */ +#define SC27XX_ADC_HWLOCK_TIMEOUT 5000 + +/* Maximum ADC channel number */ +#define SC27XX_ADC_CHANNEL_MAX 32 + +/* ADC voltage ratio definition */ +#define SC27XX_VOLT_RATIO(n, d) \ + (((n) << SC27XX_RATIO_NUMERATOR_OFFSET) | (d)) +#define SC27XX_RATIO_NUMERATOR_OFFSET 16 +#define SC27XX_RATIO_DENOMINATOR_MASK GENMASK(15, 0) + +struct sc27xx_adc_data { + struct device *dev; + struct regmap *regmap; + /* + * One hardware spinlock to synchronize between the multiple + * subsystems which will access the unique ADC controller. + */ + struct hwspinlock *hwlock; + struct completion completion; + int channel_scale[SC27XX_ADC_CHANNEL_MAX]; + u32 base; + int value; + int irq; +}; + +struct sc27xx_adc_linear_graph { + int volt0; + int adc0; + int volt1; + int adc1; +}; + +/* + * According to the datasheet, we can convert one ADC value to one voltage value + * through 2 points in the linear graph. If the voltage is less than 1.2v, we + * should use the small-scale graph, and if more than 1.2v, we should use the + * big-scale graph. + */ +static const struct sc27xx_adc_linear_graph big_scale_graph = { + 4200, 3310, + 3600, 2832, +}; + +static const struct sc27xx_adc_linear_graph small_scale_graph = { + 1000, 3413, + 100, 341, +}; + +static int sc27xx_adc_get_ratio(int channel, int scale) +{ + switch (channel) { + case 1: + case 2: + case 3: + case 4: + return scale ? SC27XX_VOLT_RATIO(400, 1025) : + SC27XX_VOLT_RATIO(1, 1); + case 5: + return SC27XX_VOLT_RATIO(7, 29); + case 6: + return SC27XX_VOLT_RATIO(375, 9000); + case 7: + case 8: + return scale ? SC27XX_VOLT_RATIO(100, 125) : + SC27XX_VOLT_RATIO(1, 1); + case 19: + return SC27XX_VOLT_RATIO(1, 3); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + return SC27XX_VOLT_RATIO(1, 1); +} + +static int sc27xx_adc_read(struct sc27xx_adc_data *data, int channel, + int scale, int *val) +{ + int ret; + u32 tmp; + + reinit_completion(&data->completion); + + ret = hwspin_lock_timeout_raw(data->hwlock, SC27XX_ADC_HWLOCK_TIMEOUT); + if (ret) { + dev_err(data->dev, "timeout to get the hwspinlock\n"); + return ret; + } + + ret = regmap_update_bits(data->regmap, data->base + SC27XX_ADC_CTL, + SC27XX_ADC_EN, SC27XX_ADC_EN); + if (ret) + goto unlock_adc; + + /* Configure the channel id and scale */ + tmp = (scale << SC27XX_ADC_SCALE_SHIFT) & SC27XX_ADC_SCALE_MASK; + tmp |= channel & SC27XX_ADC_CHN_ID_MASK; + ret = regmap_update_bits(data->regmap, data->base + SC27XX_ADC_CH_CFG, + SC27XX_ADC_CHN_ID_MASK | SC27XX_ADC_SCALE_MASK, + tmp); + if (ret) + goto disable_adc; + + /* Select 12bit conversion mode, and only sample 1 time */ + tmp = SC27XX_ADC_12BIT_MODE; + tmp |= (0 << SC27XX_ADC_RUN_NUM_SHIFT) & SC27XX_ADC_RUN_NUM_MASK; + ret = regmap_update_bits(data->regmap, data->base + SC27XX_ADC_CTL, + SC27XX_ADC_RUN_NUM_MASK | SC27XX_ADC_12BIT_MODE, + tmp); + if (ret) + goto disable_adc; + + ret = regmap_update_bits(data->regmap, data->base + SC27XX_ADC_CTL, + SC27XX_ADC_CHN_RUN, SC27XX_ADC_CHN_RUN); + if (ret) + goto disable_adc; + + wait_for_completion(&data->completion); + +disable_adc: + regmap_update_bits(data->regmap, data->base + SC27XX_ADC_CTL, + SC27XX_ADC_EN, 0); +unlock_adc: + hwspin_unlock_raw(data->hwlock); + + if (!ret) + *val = data->value; + + return ret; +} + +static irqreturn_t sc27xx_adc_isr(int irq, void *dev_id) +{ + struct sc27xx_adc_data *data = dev_id; + int ret; + + ret = regmap_update_bits(data->regmap, data->base + SC27XX_ADC_INT_CLR, + SC27XX_ADC_IRQ_CLR, SC27XX_ADC_IRQ_CLR); + if (ret) + return IRQ_RETVAL(ret); + + ret = regmap_read(data->regmap, data->base + SC27XX_ADC_DATA, + &data->value); + if (ret) + return IRQ_RETVAL(ret); + + data->value &= SC27XX_ADC_DATA_MASK; + complete(&data->completion); + + return IRQ_HANDLED; +} + +static void sc27xx_adc_volt_ratio(struct sc27xx_adc_data *data, + int channel, int scale, + u32 *div_numerator, u32 *div_denominator) +{ + u32 ratio = sc27xx_adc_get_ratio(channel, scale); + + *div_numerator = ratio >> SC27XX_RATIO_NUMERATOR_OFFSET; + *div_denominator = ratio & SC27XX_RATIO_DENOMINATOR_MASK; +} + +static int sc27xx_adc_to_volt(const struct sc27xx_adc_linear_graph *graph, + int raw_adc) +{ + int tmp; + + tmp = (graph->volt0 - graph->volt1) * (raw_adc - graph->adc1); + tmp /= (graph->adc0 - graph->adc1); + tmp += graph->volt1; + + return tmp < 0 ? 0 : tmp; +} + +static int sc27xx_adc_convert_volt(struct sc27xx_adc_data *data, int channel, + int scale, int raw_adc) +{ + u32 numerator, denominator; + u32 volt; + + /* + * Convert ADC values to voltage values according to the linear graph, + * and channel 5 and channel 1 has been calibrated, so we can just + * return the voltage values calculated by the linear graph. But other + * channels need be calculated to the real voltage values with the + * voltage ratio. + */ + switch (channel) { + case 5: + return sc27xx_adc_to_volt(&big_scale_graph, raw_adc); + + case 1: + return sc27xx_adc_to_volt(&small_scale_graph, raw_adc); + + default: + volt = sc27xx_adc_to_volt(&small_scale_graph, raw_adc); + break; + } + + sc27xx_adc_volt_ratio(data, channel, scale, &numerator, &denominator); + + return (volt * denominator + numerator / 2) / numerator; +} + +static int sc27xx_adc_read_processed(struct sc27xx_adc_data *data, + int channel, int scale, int *val) +{ + int ret, raw_adc; + + ret = sc27xx_adc_read(data, channel, scale, &raw_adc); + if (ret) + return ret; + + *val = sc27xx_adc_convert_volt(data, channel, scale, raw_adc); + return 0; +} + +static int sc27xx_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct sc27xx_adc_data *data = iio_priv(indio_dev); + int scale = data->channel_scale[chan->channel]; + int ret, tmp; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&indio_dev->mlock); + ret = sc27xx_adc_read_processed(data, chan->channel, scale, + &tmp); + mutex_unlock(&indio_dev->mlock); + + if (ret) + return ret; + + *val = tmp; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = scale; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int sc27xx_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct sc27xx_adc_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + data->channel_scale[chan->channel] = val; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static const struct iio_info sc27xx_info = { + .read_raw = &sc27xx_adc_read_raw, + .write_raw = &sc27xx_adc_write_raw, +}; + +#define SC27XX_ADC_CHANNEL(index) { \ + .type = IIO_VOLTAGE, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "CH##index", \ + .indexed = 1, \ +} + +static const struct iio_chan_spec sc27xx_channels[] = { + SC27XX_ADC_CHANNEL(0), + SC27XX_ADC_CHANNEL(1), + SC27XX_ADC_CHANNEL(2), + SC27XX_ADC_CHANNEL(3), + SC27XX_ADC_CHANNEL(4), + SC27XX_ADC_CHANNEL(5), + SC27XX_ADC_CHANNEL(6), + SC27XX_ADC_CHANNEL(7), + SC27XX_ADC_CHANNEL(8), + SC27XX_ADC_CHANNEL(9), + SC27XX_ADC_CHANNEL(10), + SC27XX_ADC_CHANNEL(11), + SC27XX_ADC_CHANNEL(12), + SC27XX_ADC_CHANNEL(13), + SC27XX_ADC_CHANNEL(14), + SC27XX_ADC_CHANNEL(15), + SC27XX_ADC_CHANNEL(16), + SC27XX_ADC_CHANNEL(17), + SC27XX_ADC_CHANNEL(18), + SC27XX_ADC_CHANNEL(19), + SC27XX_ADC_CHANNEL(20), + SC27XX_ADC_CHANNEL(21), + SC27XX_ADC_CHANNEL(22), + SC27XX_ADC_CHANNEL(23), + SC27XX_ADC_CHANNEL(24), + SC27XX_ADC_CHANNEL(25), + SC27XX_ADC_CHANNEL(26), + SC27XX_ADC_CHANNEL(27), + SC27XX_ADC_CHANNEL(28), + SC27XX_ADC_CHANNEL(29), + SC27XX_ADC_CHANNEL(30), + SC27XX_ADC_CHANNEL(31), +}; + +static int sc27xx_adc_enable(struct sc27xx_adc_data *data) +{ + int ret; + + ret = regmap_update_bits(data->regmap, SC27XX_MODULE_EN, + SC27XX_MODULE_ADC_EN, SC27XX_MODULE_ADC_EN); + if (ret) + return ret; + + /* Enable ADC work clock and controller clock */ + ret = regmap_update_bits(data->regmap, SC27XX_ARM_CLK_EN, + SC27XX_CLK_ADC_EN | SC27XX_CLK_ADC_CLK_EN, + SC27XX_CLK_ADC_EN | SC27XX_CLK_ADC_CLK_EN); + if (ret) + goto disable_adc; + + ret = regmap_update_bits(data->regmap, data->base + SC27XX_ADC_INT_EN, + SC27XX_ADC_IRQ_EN, SC27XX_ADC_IRQ_EN); + if (ret) + goto disable_clk; + + return 0; + +disable_clk: + regmap_update_bits(data->regmap, SC27XX_ARM_CLK_EN, + SC27XX_CLK_ADC_EN | SC27XX_CLK_ADC_CLK_EN, 0); +disable_adc: + regmap_update_bits(data->regmap, SC27XX_MODULE_EN, + SC27XX_MODULE_ADC_EN, 0); + + return ret; +} + +static void sc27xx_adc_disable(void *_data) +{ + struct sc27xx_adc_data *data = _data; + + regmap_update_bits(data->regmap, data->base + SC27XX_ADC_INT_EN, + SC27XX_ADC_IRQ_EN, 0); + + /* Disable ADC work clock and controller clock */ + regmap_update_bits(data->regmap, SC27XX_ARM_CLK_EN, + SC27XX_CLK_ADC_EN | SC27XX_CLK_ADC_CLK_EN, 0); + + regmap_update_bits(data->regmap, SC27XX_MODULE_EN, + SC27XX_MODULE_ADC_EN, 0); +} + +static void sc27xx_adc_free_hwlock(void *_data) +{ + struct hwspinlock *hwlock = _data; + + hwspin_lock_free(hwlock); +} + +static int sc27xx_adc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct sc27xx_adc_data *sc27xx_data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*sc27xx_data)); + if (!indio_dev) + return -ENOMEM; + + sc27xx_data = iio_priv(indio_dev); + + sc27xx_data->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!sc27xx_data->regmap) { + dev_err(&pdev->dev, "failed to get ADC regmap\n"); + return -ENODEV; + } + + ret = of_property_read_u32(np, "reg", &sc27xx_data->base); + if (ret) { + dev_err(&pdev->dev, "failed to get ADC base address\n"); + return ret; + } + + sc27xx_data->irq = platform_get_irq(pdev, 0); + if (sc27xx_data->irq < 0) { + dev_err(&pdev->dev, "failed to get ADC irq number\n"); + return sc27xx_data->irq; + } + + ret = of_hwspin_lock_get_id(np, 0); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get hwspinlock id\n"); + return ret; + } + + sc27xx_data->hwlock = hwspin_lock_request_specific(ret); + if (!sc27xx_data->hwlock) { + dev_err(&pdev->dev, "failed to request hwspinlock\n"); + return -ENXIO; + } + + ret = devm_add_action(&pdev->dev, sc27xx_adc_free_hwlock, + sc27xx_data->hwlock); + if (ret) { + sc27xx_adc_free_hwlock(sc27xx_data->hwlock); + dev_err(&pdev->dev, "failed to add hwspinlock action\n"); + return ret; + } + + init_completion(&sc27xx_data->completion); + sc27xx_data->dev = &pdev->dev; + + ret = sc27xx_adc_enable(sc27xx_data); + if (ret) { + dev_err(&pdev->dev, "failed to enable ADC module\n"); + return ret; + } + + ret = devm_add_action(&pdev->dev, sc27xx_adc_disable, sc27xx_data); + if (ret) { + sc27xx_adc_disable(sc27xx_data); + dev_err(&pdev->dev, "failed to add ADC disable action\n"); + return ret; + } + + ret = devm_request_threaded_irq(&pdev->dev, sc27xx_data->irq, NULL, + sc27xx_adc_isr, IRQF_ONESHOT, + pdev->name, sc27xx_data); + if (ret) { + dev_err(&pdev->dev, "failed to request ADC irq\n"); + return ret; + } + + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = dev_name(&pdev->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &sc27xx_info; + indio_dev->channels = sc27xx_channels; + indio_dev->num_channels = ARRAY_SIZE(sc27xx_channels); + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret) + dev_err(&pdev->dev, "could not register iio (ADC)"); + + return ret; +} + +static const struct of_device_id sc27xx_adc_of_match[] = { + { .compatible = "sprd,sc2731-adc", }, + { } +}; + +static struct platform_driver sc27xx_adc_driver = { + .probe = sc27xx_adc_probe, + .driver = { + .name = "sc27xx-adc", + .of_match_table = sc27xx_adc_of_match, + }, +}; + +module_platform_driver(sc27xx_adc_driver); + +MODULE_AUTHOR("Freeman Liu "); +MODULE_DESCRIPTION("Spreadtrum SC27XX ADC Driver"); +MODULE_LICENSE("GPL v2"); From 7d6cd21d82bacab2d1786fe5e989e4815b75d9a3 Mon Sep 17 00:00:00 2001 From: Akinobu Mita Date: Mon, 25 Jun 2018 00:05:21 +0900 Subject: [PATCH 07/39] iio: adc: ina2xx: avoid kthread_stop() with stale task_struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the buffer is enabled for ina2xx driver, a dedicated kthread is invoked to capture mesurement data. When the buffer is disabled, the kthread is stopped. However if the kthread gets register access errors, it immediately exits and when the malfunctional buffer is disabled, the stale task_struct pointer is accessed as there is no kthread to be stopped. A similar issue in the usbip driver is prevented by kthread_get_run and kthread_stop_put helpers by increasing usage count of the task_struct. This change applies the same solution. Cc: Stefan Brüns Cc: Jonathan Cameron Signed-off-by: Akinobu Mita Fixes: c43a102e67db ("iio: ina2xx: add support for TI INA2xx Power Monitors") Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ina2xx-adc.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c index 0635a79864bf..d1239624187d 100644 --- a/drivers/iio/adc/ina2xx-adc.c +++ b/drivers/iio/adc/ina2xx-adc.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -826,6 +827,7 @@ static int ina2xx_buffer_enable(struct iio_dev *indio_dev) { struct ina2xx_chip_info *chip = iio_priv(indio_dev); unsigned int sampling_us = SAMPLING_PERIOD(chip); + struct task_struct *task; dev_dbg(&indio_dev->dev, "Enabling buffer w/ scan_mask %02x, freq = %d, avg =%u\n", (unsigned int)(*indio_dev->active_scan_mask), @@ -835,11 +837,17 @@ static int ina2xx_buffer_enable(struct iio_dev *indio_dev) dev_dbg(&indio_dev->dev, "Async readout mode: %d\n", chip->allow_async_readout); - chip->task = kthread_run(ina2xx_capture_thread, (void *)indio_dev, - "%s:%d-%uus", indio_dev->name, indio_dev->id, - sampling_us); + task = kthread_create(ina2xx_capture_thread, (void *)indio_dev, + "%s:%d-%uus", indio_dev->name, indio_dev->id, + sampling_us); + if (IS_ERR(task)) + return PTR_ERR(task); - return PTR_ERR_OR_ZERO(chip->task); + get_task_struct(task); + wake_up_process(task); + chip->task = task; + + return 0; } static int ina2xx_buffer_disable(struct iio_dev *indio_dev) @@ -848,6 +856,7 @@ static int ina2xx_buffer_disable(struct iio_dev *indio_dev) if (chip->task) { kthread_stop(chip->task); + put_task_struct(chip->task); chip->task = NULL; } From 19868faad4c59646a9fbebc34c3bf07733db341e Mon Sep 17 00:00:00 2001 From: Nikolaus Voss Date: Tue, 3 Jul 2018 07:41:00 +0200 Subject: [PATCH 08/39] IIO: st_accel_i2c.c: Simplify access to driver data Use device_get_match_data API to simplify access to driver data. Let acpi_device_id table entries point to the same driver data as of_device_id table entries and uniquify access to driver data by using device_get_match_data API. Remove unused i2c_device_id .driver_data fields. Signed-off-by: Nikolaus Voss Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/st_accel_i2c.c | 59 ++++++++++++++------------------ 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c index 056dddb27236..d02298f0256c 100644 --- a/drivers/iio/accel/st_accel_i2c.c +++ b/drivers/iio/accel/st_accel_i2c.c @@ -14,8 +14,8 @@ #include #include #include +#include -#include #include #include "st_accel.h" @@ -107,8 +107,8 @@ MODULE_DEVICE_TABLE(of, st_accel_of_match); #ifdef CONFIG_ACPI static const struct acpi_device_id st_accel_acpi_match[] = { - {"SMO8840", LNG2DM}, - {"SMO8A90", LNG2DM}, + {"SMO8840", (kernel_ulong_t)LNG2DM_ACCEL_DEV_NAME}, + {"SMO8A90", (kernel_ulong_t)LNG2DM_ACCEL_DEV_NAME}, { }, }; MODULE_DEVICE_TABLE(acpi, st_accel_acpi_match); @@ -117,24 +117,24 @@ MODULE_DEVICE_TABLE(acpi, st_accel_acpi_match); #endif static const struct i2c_device_id st_accel_id_table[] = { - { LSM303DLH_ACCEL_DEV_NAME, LSM303DLH }, - { LSM303DLHC_ACCEL_DEV_NAME, LSM303DLHC }, - { LIS3DH_ACCEL_DEV_NAME, LIS3DH }, - { LSM330D_ACCEL_DEV_NAME, LSM330D }, - { LSM330DL_ACCEL_DEV_NAME, LSM330DL }, - { LSM330DLC_ACCEL_DEV_NAME, LSM330DLC }, - { LIS331DLH_ACCEL_DEV_NAME, LIS331DLH }, - { LSM303DL_ACCEL_DEV_NAME, LSM303DL }, - { LSM303DLM_ACCEL_DEV_NAME, LSM303DLM }, - { LSM330_ACCEL_DEV_NAME, LSM330 }, - { LSM303AGR_ACCEL_DEV_NAME, LSM303AGR }, - { LIS2DH12_ACCEL_DEV_NAME, LIS2DH12 }, - { LIS3L02DQ_ACCEL_DEV_NAME, LIS3L02DQ }, - { LNG2DM_ACCEL_DEV_NAME, LNG2DM }, - { H3LIS331DL_ACCEL_DEV_NAME, H3LIS331DL }, - { LIS331DL_ACCEL_DEV_NAME, LIS331DL }, - { LIS3LV02DL_ACCEL_DEV_NAME, LIS3LV02DL }, - { LIS2DW12_ACCEL_DEV_NAME, LIS2DW12 }, + { LSM303DLH_ACCEL_DEV_NAME }, + { LSM303DLHC_ACCEL_DEV_NAME }, + { LIS3DH_ACCEL_DEV_NAME }, + { LSM330D_ACCEL_DEV_NAME }, + { LSM330DL_ACCEL_DEV_NAME }, + { LSM330DLC_ACCEL_DEV_NAME }, + { LIS331DLH_ACCEL_DEV_NAME }, + { LSM303DL_ACCEL_DEV_NAME }, + { LSM303DLM_ACCEL_DEV_NAME }, + { LSM330_ACCEL_DEV_NAME }, + { LSM303AGR_ACCEL_DEV_NAME }, + { LIS2DH12_ACCEL_DEV_NAME }, + { LIS3L02DQ_ACCEL_DEV_NAME }, + { LNG2DM_ACCEL_DEV_NAME }, + { H3LIS331DL_ACCEL_DEV_NAME }, + { LIS331DL_ACCEL_DEV_NAME }, + { LIS3LV02DL_ACCEL_DEV_NAME }, + { LIS2DW12_ACCEL_DEV_NAME }, {}, }; MODULE_DEVICE_TABLE(i2c, st_accel_id_table); @@ -144,6 +144,7 @@ static int st_accel_i2c_probe(struct i2c_client *client, { struct iio_dev *indio_dev; struct st_sensor_data *adata; + const char *match; int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adata)); @@ -152,19 +153,9 @@ static int st_accel_i2c_probe(struct i2c_client *client, adata = iio_priv(indio_dev); - if (client->dev.of_node) { - st_sensors_of_name_probe(&client->dev, st_accel_of_match, - client->name, sizeof(client->name)); - } else if (ACPI_HANDLE(&client->dev)) { - ret = st_sensors_match_acpi_device(&client->dev); - if ((ret < 0) || (ret >= ST_ACCEL_MAX)) - return -ENODEV; - - strlcpy(client->name, st_accel_id_table[ret].name, - sizeof(client->name)); - } else if (!id) - return -ENODEV; - + match = device_get_match_data(&client->dev); + if (match) + strlcpy(client->name, match, sizeof(client->name)); st_sensors_i2c_configure(indio_dev, client, adata); From 6b0b3e378e16f75338665fa519c2a06e4a071889 Mon Sep 17 00:00:00 2001 From: Nikolaus Voss Date: Tue, 3 Jul 2018 08:06:57 +0200 Subject: [PATCH 09/39] IIO: st_accel_i2c.c: Use probe_new() instead of probe() struct i2c_device_id argument of probe() is not used, so use probe_new() instead. Signed-off-by: Nikolaus Voss Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/st_accel_i2c.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c index d02298f0256c..2ca5d1f6ade0 100644 --- a/drivers/iio/accel/st_accel_i2c.c +++ b/drivers/iio/accel/st_accel_i2c.c @@ -139,8 +139,7 @@ static const struct i2c_device_id st_accel_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, st_accel_id_table); -static int st_accel_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int st_accel_i2c_probe(struct i2c_client *client) { struct iio_dev *indio_dev; struct st_sensor_data *adata; @@ -179,7 +178,7 @@ static struct i2c_driver st_accel_driver = { .of_match_table = of_match_ptr(st_accel_of_match), .acpi_match_table = ACPI_PTR(st_accel_acpi_match), }, - .probe = st_accel_i2c_probe, + .probe_new = st_accel_i2c_probe, .remove = st_accel_i2c_remove, .id_table = st_accel_id_table, }; From 366995df8b9ed7743386813412dcfa991c170d28 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 2 Jul 2018 11:24:35 +0100 Subject: [PATCH 10/39] iio: dac: ti-dac5571 remove redundant variable 'shift' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Variable shift is being assigned but is never used hence it is redundant and can be removed. Cleans up two clang warnings: warning: variable ‘shift’ set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Reviewed-by: Sean Nyekjaer Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ti-dac5571.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/iio/dac/ti-dac5571.c b/drivers/iio/dac/ti-dac5571.c index dd21eebed6a8..e39d1e901353 100644 --- a/drivers/iio/dac/ti-dac5571.c +++ b/drivers/iio/dac/ti-dac5571.c @@ -97,9 +97,6 @@ static int dac5571_cmd_quad(struct dac5571_data *data, int channel, u16 val) static int dac5571_pwrdwn_single(struct dac5571_data *data, int channel, u8 pwrdwn) { - unsigned int shift; - - shift = 12 - data->spec->resolution; data->buf[1] = 0; data->buf[0] = pwrdwn << DAC5571_SINGLE_PWRDWN_BITS; @@ -111,9 +108,6 @@ static int dac5571_pwrdwn_single(struct dac5571_data *data, int channel, u8 pwrd static int dac5571_pwrdwn_quad(struct dac5571_data *data, int channel, u8 pwrdwn) { - unsigned int shift; - - shift = 16 - data->spec->resolution; data->buf[2] = 0; data->buf[1] = pwrdwn << DAC5571_QUAD_PWRDWN_BITS; data->buf[0] = (channel << DAC5571_CHANNEL_SELECT) | From 5f0d4fd6b2c6f4ce14af861b17b9bebc8ba1265a Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 2 Jul 2018 11:13:59 +0100 Subject: [PATCH 11/39] iio: dac: ltc2632 remove redundant pointer chip_info and spi_dev_id Pointers chip_info and spi_dev_id are being assigned but are never used hence they are redundant and can be removed. Cleans up clang warning: warning: variable 'chip_info' set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ltc2632.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/iio/dac/ltc2632.c b/drivers/iio/dac/ltc2632.c index cca278eaa138..28e9b7656b20 100644 --- a/drivers/iio/dac/ltc2632.c +++ b/drivers/iio/dac/ltc2632.c @@ -87,12 +87,7 @@ static int ltc2632_read_raw(struct iio_dev *indio_dev, int *val2, long m) { - struct ltc2632_chip_info *chip_info; - const struct ltc2632_state *st = iio_priv(indio_dev); - const struct spi_device_id *spi_dev_id = spi_get_device_id(st->spi_dev); - - chip_info = (struct ltc2632_chip_info *)spi_dev_id->driver_data; switch (m) { case IIO_CHAN_INFO_SCALE: From 82d65f9d552818b7e56c85edaaf6c3d2b104d07d Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 3 Jul 2018 14:35:50 -0500 Subject: [PATCH 12/39] iio:imu:adis: Mark expected switch fall-throughs In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Signed-off-by: Gustavo A. R. Silva Signed-off-by: Jonathan Cameron --- drivers/iio/imu/adis.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c index ad6f91d06185..c771ae6803a9 100644 --- a/drivers/iio/imu/adis.c +++ b/drivers/iio/imu/adis.c @@ -81,9 +81,11 @@ int adis_write_reg(struct adis *adis, unsigned int reg, adis->tx[9] = (value >> 24) & 0xff; adis->tx[6] = ADIS_WRITE_REG(reg + 2); adis->tx[7] = (value >> 16) & 0xff; + /* fall through */ case 2: adis->tx[4] = ADIS_WRITE_REG(reg + 1); adis->tx[5] = (value >> 8) & 0xff; + /* fall through */ case 1: adis->tx[2] = ADIS_WRITE_REG(reg); adis->tx[3] = value & 0xff; @@ -167,6 +169,7 @@ int adis_read_reg(struct adis *adis, unsigned int reg, adis->tx[2] = ADIS_READ_REG(reg + 2); adis->tx[3] = 0; spi_message_add_tail(&xfers[1], &msg); + /* fall through */ case 2: adis->tx[4] = ADIS_READ_REG(reg); adis->tx[5] = 0; From e161ef7c3c376f6807cdef7e8a2804b964dd7584 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 3 Jul 2018 14:44:36 -0500 Subject: [PATCH 13/39] iio: mma8452: Mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Warning level 2 was used in this case: -Wimplicit-fallthrough=2 Signed-off-by: Gustavo A. R. Silva Signed-off-by: Jonathan Cameron --- drivers/iio/accel/mma8452.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index 7e3d82cff3d5..31eccb8533d2 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -1547,6 +1547,7 @@ static int mma8452_probe(struct i2c_client *client, case FXLS8471_DEVICE_ID: if (ret == data->chip_info->chip_id) break; + /* else: fall through */ default: return -ENODEV; } From 28d1a7ac2a0d9589e04dd36a83f242b3e14da1eb Mon Sep 17 00:00:00 2001 From: Stefan Popa Date: Wed, 4 Jul 2018 17:32:03 +0300 Subject: [PATCH 14/39] iio: dac: Add AD5758 support The AD5758 is a single channel DAC with 16-bit precision which uses the SPI interface that operates at clock rates up to 50MHz. The output can be configured as voltage or current and is available on a single terminal. Datasheet: http://www.analog.com/media/en/technical-documentation/data-sheets/ad5758.pdf Signed-off-by: Stefan Popa Signed-off-by: Jonathan Cameron --- MAINTAINERS | 7 + drivers/iio/dac/Kconfig | 10 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/ad5758.c | 897 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 915 insertions(+) create mode 100644 drivers/iio/dac/ad5758.c diff --git a/MAINTAINERS b/MAINTAINERS index a21cadbf39e7..728ae34b7b32 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -796,6 +796,13 @@ M: Michael Hanselmann S: Supported F: drivers/macintosh/ams/ +ANALOG DEVICES INC AD5758 DRIVER +M: Stefan Popa +L: linux-iio@vger.kernel.org +W: http://ez.analog.com/community/linux-device-drivers +S: Supported +F: drivers/iio/dac/ad5758.c + ANALOG DEVICES INC AD5686 DRIVER M: Stefan Popa L: linux-pm@vger.kernel.org diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 06e90debb9f5..80beb64e9e0c 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -167,6 +167,16 @@ config AD5755 To compile this driver as a module, choose M here: the module will be called ad5755. +config AD5758 + tristate "Analog Devices AD5758 DAC driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5758 single channel + Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5758. + config AD5761 tristate "Analog Devices AD5761/61R/21/21R DAC driver" depends on SPI_MASTER diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 57aa230d34ab..a1b37cf99441 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_AD5592R_BASE) += ad5592r-base.o obj-$(CONFIG_AD5592R) += ad5592r.o obj-$(CONFIG_AD5593R) += ad5593r.o obj-$(CONFIG_AD5755) += ad5755.o +obj-$(CONFIG_AD5755) += ad5758.o obj-$(CONFIG_AD5761) += ad5761.o obj-$(CONFIG_AD5764) += ad5764.o obj-$(CONFIG_AD5791) += ad5791.o diff --git a/drivers/iio/dac/ad5758.c b/drivers/iio/dac/ad5758.c new file mode 100644 index 000000000000..bd36333257af --- /dev/null +++ b/drivers/iio/dac/ad5758.c @@ -0,0 +1,897 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AD5758 Digital to analog converters driver + * + * Copyright 2018 Analog Devices Inc. + * + * TODO: Currently CRC is not supported in this driver + */ +#include +#include +#include +#include +#include +#include + +#include +#include + +/* AD5758 registers definition */ +#define AD5758_NOP 0x00 +#define AD5758_DAC_INPUT 0x01 +#define AD5758_DAC_OUTPUT 0x02 +#define AD5758_CLEAR_CODE 0x03 +#define AD5758_USER_GAIN 0x04 +#define AD5758_USER_OFFSET 0x05 +#define AD5758_DAC_CONFIG 0x06 +#define AD5758_SW_LDAC 0x07 +#define AD5758_KEY 0x08 +#define AD5758_GP_CONFIG1 0x09 +#define AD5758_GP_CONFIG2 0x0A +#define AD5758_DCDC_CONFIG1 0x0B +#define AD5758_DCDC_CONFIG2 0x0C +#define AD5758_WDT_CONFIG 0x0F +#define AD5758_DIGITAL_DIAG_CONFIG 0x10 +#define AD5758_ADC_CONFIG 0x11 +#define AD5758_FAULT_PIN_CONFIG 0x12 +#define AD5758_TWO_STAGE_READBACK_SELECT 0x13 +#define AD5758_DIGITAL_DIAG_RESULTS 0x14 +#define AD5758_ANALOG_DIAG_RESULTS 0x15 +#define AD5758_STATUS 0x16 +#define AD5758_CHIP_ID 0x17 +#define AD5758_FREQ_MONITOR 0x18 +#define AD5758_DEVICE_ID_0 0x19 +#define AD5758_DEVICE_ID_1 0x1A +#define AD5758_DEVICE_ID_2 0x1B +#define AD5758_DEVICE_ID_3 0x1C + +/* AD5758_DAC_CONFIG */ +#define AD5758_DAC_CONFIG_RANGE_MSK GENMASK(3, 0) +#define AD5758_DAC_CONFIG_RANGE_MODE(x) (((x) & 0xF) << 0) +#define AD5758_DAC_CONFIG_INT_EN_MSK BIT(5) +#define AD5758_DAC_CONFIG_INT_EN_MODE(x) (((x) & 0x1) << 5) +#define AD5758_DAC_CONFIG_OUT_EN_MSK BIT(6) +#define AD5758_DAC_CONFIG_OUT_EN_MODE(x) (((x) & 0x1) << 6) +#define AD5758_DAC_CONFIG_SR_EN_MSK BIT(8) +#define AD5758_DAC_CONFIG_SR_EN_MODE(x) (((x) & 0x1) << 8) +#define AD5758_DAC_CONFIG_SR_CLOCK_MSK GENMASK(12, 9) +#define AD5758_DAC_CONFIG_SR_CLOCK_MODE(x) (((x) & 0xF) << 9) +#define AD5758_DAC_CONFIG_SR_STEP_MSK GENMASK(15, 13) +#define AD5758_DAC_CONFIG_SR_STEP_MODE(x) (((x) & 0x7) << 13) + +/* AD5758_KEY */ +#define AD5758_KEY_CODE_RESET_1 0x15FA +#define AD5758_KEY_CODE_RESET_2 0xAF51 +#define AD5758_KEY_CODE_SINGLE_ADC_CONV 0x1ADC +#define AD5758_KEY_CODE_RESET_WDT 0x0D06 +#define AD5758_KEY_CODE_CALIB_MEM_REFRESH 0xFCBA + +/* AD5758_DCDC_CONFIG1 */ +#define AD5758_DCDC_CONFIG1_DCDC_VPROG_MSK GENMASK(4, 0) +#define AD5758_DCDC_CONFIG1_DCDC_VPROG_MODE(x) (((x) & 0x1F) << 0) +#define AD5758_DCDC_CONFIG1_DCDC_MODE_MSK GENMASK(6, 5) +#define AD5758_DCDC_CONFIG1_DCDC_MODE_MODE(x) (((x) & 0x3) << 5) +#define AD5758_DCDC_CONFIG1_PROT_SW_EN_MSK BIT(7) +#define AD5758_DCDC_CONFIG1_PROT_SW_EN_MODE(x) (((x) & 0x1) << 7) + +/* AD5758_DCDC_CONFIG2 */ +#define AD5758_DCDC_CONFIG2_ILIMIT_MSK GENMASK(3, 1) +#define AD5758_DCDC_CONFIG2_ILIMIT_MODE(x) (((x) & 0x7) << 1) +#define AD5758_DCDC_CONFIG2_INTR_SAT_3WI_MSK BIT(11) +#define AD5758_DCDC_CONFIG2_BUSY_3WI_MSK BIT(12) + +/* AD5758_DIGITAL_DIAG_RESULTS */ +#define AD5758_CAL_MEM_UNREFRESHED_MSK BIT(15) + +#define AD5758_WR_FLAG_MSK(x) (0x80 | ((x) & 0x1F)) + +#define AD5758_FULL_SCALE_MICRO 65535000000ULL + +/** + * struct ad5758_state - driver instance specific data + * @spi: spi_device + * @lock: mutex lock + * @out_range: struct which stores the output range + * @dc_dc_mode: variable which stores the mode of operation + * @dc_dc_ilim: variable which stores the dc-to-dc converter current limit + * @slew_time: variable which stores the target slew time + * @pwr_down: variable which contains whether a channel is powered down or not + * @data: spi transfer buffers + */ + +struct ad5758_range { + int reg; + int min; + int max; +}; + +struct ad5758_state { + struct spi_device *spi; + struct mutex lock; + struct ad5758_range out_range; + unsigned int dc_dc_mode; + unsigned int dc_dc_ilim; + unsigned int slew_time; + bool pwr_down; + __be32 d32[3]; +}; + +/** + * Output ranges corresponding to bits [3:0] from DAC_CONFIG register + * 0000: 0 V to 5 V voltage range + * 0001: 0 V to 10 V voltage range + * 0010: ±5 V voltage range + * 0011: ±10 V voltage range + * 1000: 0 mA to 20 mA current range + * 1001: 0 mA to 24 mA current range + * 1010: 4 mA to 20 mA current range + * 1011: ±20 mA current range + * 1100: ±24 mA current range + * 1101: -1 mA to +22 mA current range + */ +enum ad5758_output_range { + AD5758_RANGE_0V_5V, + AD5758_RANGE_0V_10V, + AD5758_RANGE_PLUSMINUS_5V, + AD5758_RANGE_PLUSMINUS_10V, + AD5758_RANGE_0mA_20mA = 8, + AD5758_RANGE_0mA_24mA, + AD5758_RANGE_4mA_24mA, + AD5758_RANGE_PLUSMINUS_20mA, + AD5758_RANGE_PLUSMINUS_24mA, + AD5758_RANGE_MINUS_1mA_PLUS_22mA, +}; + +enum ad5758_dc_dc_mode { + AD5758_DCDC_MODE_POWER_OFF, + AD5758_DCDC_MODE_DPC_CURRENT, + AD5758_DCDC_MODE_DPC_VOLTAGE, + AD5758_DCDC_MODE_PPC_CURRENT, +}; + +static const struct ad5758_range ad5758_voltage_range[] = { + { AD5758_RANGE_0V_5V, 0, 5000000 }, + { AD5758_RANGE_0V_10V, 0, 10000000 }, + { AD5758_RANGE_PLUSMINUS_5V, -5000000, 5000000 }, + { AD5758_RANGE_PLUSMINUS_10V, -10000000, 10000000 } +}; + +static const struct ad5758_range ad5758_current_range[] = { + { AD5758_RANGE_0mA_20mA, 0, 20000}, + { AD5758_RANGE_0mA_24mA, 0, 24000 }, + { AD5758_RANGE_4mA_24mA, 4, 24000 }, + { AD5758_RANGE_PLUSMINUS_20mA, -20000, 20000 }, + { AD5758_RANGE_PLUSMINUS_24mA, -24000, 24000 }, + { AD5758_RANGE_MINUS_1mA_PLUS_22mA, -1000, 22000 }, +}; + +static const int ad5758_sr_clk[16] = { + 240000, 200000, 150000, 128000, 64000, 32000, 16000, 8000, 4000, 2000, + 1000, 512, 256, 128, 64, 16 +}; + +static const int ad5758_sr_step[8] = { + 4, 12, 64, 120, 256, 500, 1820, 2048 +}; + +static const int ad5758_dc_dc_ilim[6] = { + 150000, 200000, 250000, 300000, 350000, 400000 +}; + +static int ad5758_spi_reg_read(struct ad5758_state *st, unsigned int addr) +{ + struct spi_transfer t[] = { + { + .tx_buf = &st->d32[0], + .len = 4, + .cs_change = 1, + }, { + .tx_buf = &st->d32[1], + .rx_buf = &st->d32[2], + .len = 4, + }, + }; + int ret; + + st->d32[0] = cpu_to_be32( + (AD5758_WR_FLAG_MSK(AD5758_TWO_STAGE_READBACK_SELECT) << 24) | + (addr << 8)); + st->d32[1] = cpu_to_be32(AD5758_WR_FLAG_MSK(AD5758_NOP) << 24); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + return (be32_to_cpu(st->d32[2]) >> 8) & 0xFFFF; +} + +static int ad5758_spi_reg_write(struct ad5758_state *st, + unsigned int addr, + unsigned int val) +{ + st->d32[0] = cpu_to_be32((AD5758_WR_FLAG_MSK(addr) << 24) | + ((val & 0xFFFF) << 8)); + + return spi_write(st->spi, &st->d32[0], sizeof(st->d32[0])); +} + +static int ad5758_spi_write_mask(struct ad5758_state *st, + unsigned int addr, + unsigned long int mask, + unsigned int val) +{ + int regval; + + regval = ad5758_spi_reg_read(st, addr); + if (regval < 0) + return regval; + + regval &= ~mask; + regval |= val; + + return ad5758_spi_reg_write(st, addr, regval); +} + +static int cmpfunc(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +static int ad5758_find_closest_match(const int *array, + unsigned int size, int val) +{ + int i; + + for (i = 0; i < size; i++) { + if (val <= array[i]) + return i; + } + + return size - 1; +} + +static int ad5758_wait_for_task_complete(struct ad5758_state *st, + unsigned int reg, + unsigned int mask) +{ + unsigned int timeout; + int ret; + + timeout = 10; + do { + ret = ad5758_spi_reg_read(st, reg); + if (ret < 0) + return ret; + + if (!(ret & mask)) + return 0; + + usleep_range(100, 1000); + } while (--timeout); + + dev_err(&st->spi->dev, + "Error reading bit 0x%x in 0x%x register\n", mask, reg); + + return -EIO; +} + +static int ad5758_calib_mem_refresh(struct ad5758_state *st) +{ + int ret; + + ret = ad5758_spi_reg_write(st, AD5758_KEY, + AD5758_KEY_CODE_CALIB_MEM_REFRESH); + if (ret < 0) { + dev_err(&st->spi->dev, + "Failed to initiate a calibration memory refresh\n"); + return ret; + } + + /* Wait to allow time for the internal calibrations to complete */ + return ad5758_wait_for_task_complete(st, AD5758_DIGITAL_DIAG_RESULTS, + AD5758_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5758_soft_reset(struct ad5758_state *st) +{ + int ret; + + ret = ad5758_spi_reg_write(st, AD5758_KEY, AD5758_KEY_CODE_RESET_1); + if (ret < 0) + return ret; + + ret = ad5758_spi_reg_write(st, AD5758_KEY, AD5758_KEY_CODE_RESET_2); + + /* Perform a software reset and wait at least 100us */ + usleep_range(100, 1000); + + return ret; +} + +static int ad5758_set_dc_dc_conv_mode(struct ad5758_state *st, + enum ad5758_dc_dc_mode mode) +{ + int ret; + + ret = ad5758_spi_write_mask(st, AD5758_DCDC_CONFIG1, + AD5758_DCDC_CONFIG1_DCDC_MODE_MSK, + AD5758_DCDC_CONFIG1_DCDC_MODE_MODE(mode)); + if (ret < 0) + return ret; + + /* + * Poll the BUSY_3WI bit in the DCDC_CONFIG2 register until it is 0. + * This allows the 3-wire interface communication to complete. + */ + ret = ad5758_wait_for_task_complete(st, AD5758_DCDC_CONFIG2, + AD5758_DCDC_CONFIG2_BUSY_3WI_MSK); + if (ret < 0) + return ret; + + st->dc_dc_mode = mode; + + return ret; +} + +static int ad5758_set_dc_dc_ilim(struct ad5758_state *st, unsigned int ilim) +{ + int ret; + + ret = ad5758_spi_write_mask(st, AD5758_DCDC_CONFIG2, + AD5758_DCDC_CONFIG2_ILIMIT_MSK, + AD5758_DCDC_CONFIG2_ILIMIT_MODE(ilim)); + if (ret < 0) + return ret; + /* + * Poll the BUSY_3WI bit in the DCDC_CONFIG2 register until it is 0. + * This allows the 3-wire interface communication to complete. + */ + return ad5758_wait_for_task_complete(st, AD5758_DCDC_CONFIG2, + AD5758_DCDC_CONFIG2_BUSY_3WI_MSK); +} + +static int ad5758_slew_rate_set(struct ad5758_state *st, + unsigned int sr_clk_idx, + unsigned int sr_step_idx) +{ + unsigned int mode; + unsigned long int mask; + int ret; + + mask = AD5758_DAC_CONFIG_SR_EN_MSK | + AD5758_DAC_CONFIG_SR_CLOCK_MSK | + AD5758_DAC_CONFIG_SR_STEP_MSK; + mode = AD5758_DAC_CONFIG_SR_EN_MODE(1) | + AD5758_DAC_CONFIG_SR_STEP_MODE(sr_step_idx) | + AD5758_DAC_CONFIG_SR_CLOCK_MODE(sr_clk_idx); + + ret = ad5758_spi_write_mask(st, AD5758_DAC_CONFIG, mask, mode); + if (ret < 0) + return ret; + + /* Wait to allow time for the internal calibrations to complete */ + return ad5758_wait_for_task_complete(st, AD5758_DIGITAL_DIAG_RESULTS, + AD5758_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5758_slew_rate_config(struct ad5758_state *st) +{ + unsigned int sr_clk_idx, sr_step_idx; + int i, res; + s64 diff_new, diff_old; + u64 sr_step, calc_slew_time; + + sr_clk_idx = 0; + sr_step_idx = 0; + diff_old = S64_MAX; + /* + * The slew time can be determined by using the formula: + * Slew Time = (Full Scale Out / (Step Size x Update Clk Freq)) + * where Slew time is expressed in microseconds + * Given the desired slew time, the following algorithm determines the + * best match for the step size and the update clock frequency. + */ + for (i = 0; i < ARRAY_SIZE(ad5758_sr_clk); i++) { + /* + * Go through each valid update clock freq and determine a raw + * value for the step size by using the formula: + * Step Size = Full Scale Out / (Update Clk Freq * Slew Time) + */ + sr_step = AD5758_FULL_SCALE_MICRO; + do_div(sr_step, ad5758_sr_clk[i]); + do_div(sr_step, st->slew_time); + /* + * After a raw value for step size was determined, find the + * closest valid match + */ + res = ad5758_find_closest_match(ad5758_sr_step, + ARRAY_SIZE(ad5758_sr_step), + sr_step); + /* Calculate the slew time */ + calc_slew_time = AD5758_FULL_SCALE_MICRO; + do_div(calc_slew_time, ad5758_sr_step[res]); + do_div(calc_slew_time, ad5758_sr_clk[i]); + /* + * Determine with how many microseconds the calculated slew time + * is different from the desired slew time and store the diff + * for the next iteration + */ + diff_new = abs(st->slew_time - calc_slew_time); + if (diff_new < diff_old) { + diff_old = diff_new; + sr_clk_idx = i; + sr_step_idx = res; + } + } + + return ad5758_slew_rate_set(st, sr_clk_idx, sr_step_idx); +} + +static int ad5758_set_out_range(struct ad5758_state *st, int range) +{ + int ret; + + ret = ad5758_spi_write_mask(st, AD5758_DAC_CONFIG, + AD5758_DAC_CONFIG_RANGE_MSK, + AD5758_DAC_CONFIG_RANGE_MODE(range)); + if (ret < 0) + return ret; + + /* Wait to allow time for the internal calibrations to complete */ + return ad5758_wait_for_task_complete(st, AD5758_DIGITAL_DIAG_RESULTS, + AD5758_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5758_fault_prot_switch_en(struct ad5758_state *st, bool enable) +{ + int ret; + + ret = ad5758_spi_write_mask(st, AD5758_DCDC_CONFIG1, + AD5758_DCDC_CONFIG1_PROT_SW_EN_MSK, + AD5758_DCDC_CONFIG1_PROT_SW_EN_MODE(enable)); + if (ret < 0) + return ret; + /* + * Poll the BUSY_3WI bit in the DCDC_CONFIG2 register until it is 0. + * This allows the 3-wire interface communication to complete. + */ + return ad5758_wait_for_task_complete(st, AD5758_DCDC_CONFIG2, + AD5758_DCDC_CONFIG2_BUSY_3WI_MSK); +} + +static int ad5758_internal_buffers_en(struct ad5758_state *st, bool enable) +{ + int ret; + + ret = ad5758_spi_write_mask(st, AD5758_DAC_CONFIG, + AD5758_DAC_CONFIG_INT_EN_MSK, + AD5758_DAC_CONFIG_INT_EN_MODE(enable)); + if (ret < 0) + return ret; + + /* Wait to allow time for the internal calibrations to complete */ + return ad5758_wait_for_task_complete(st, AD5758_DIGITAL_DIAG_RESULTS, + AD5758_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5758_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad5758_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + if (readval) { + ret = ad5758_spi_reg_read(st, reg); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + + *readval = ret; + ret = 0; + } else { + ret = ad5758_spi_reg_write(st, reg, writeval); + } + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5758_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad5758_state *st = iio_priv(indio_dev); + int max, min, ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + ret = ad5758_spi_reg_read(st, AD5758_DAC_INPUT); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + min = st->out_range.min; + max = st->out_range.max; + *val = (max - min) / 1000; + *val2 = 16; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + min = st->out_range.min; + max = st->out_range.max; + *val = ((min * (1 << 16)) / (max - min)) / 1000; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad5758_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad5758_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + ret = ad5758_spi_reg_write(st, AD5758_DAC_INPUT, val); + mutex_unlock(&st->lock); + return ret; + default: + return -EINVAL; + } +} + +static ssize_t ad5758_read_powerdown(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad5758_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5758_write_powerdown(struct iio_dev *indio_dev, + uintptr_t priv, + struct iio_chan_spec const *chan, + const char *buf, size_t len) +{ + struct ad5758_state *st = iio_priv(indio_dev); + bool pwr_down; + unsigned int dcdc_config1_mode, dc_dc_mode, dac_config_mode, val; + unsigned long int dcdc_config1_msk, dac_config_msk; + int ret; + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + mutex_lock(&st->lock); + if (pwr_down) { + dc_dc_mode = AD5758_DCDC_MODE_POWER_OFF; + val = 0; + } else { + dc_dc_mode = st->dc_dc_mode; + val = 1; + } + + dcdc_config1_mode = AD5758_DCDC_CONFIG1_DCDC_MODE_MODE(dc_dc_mode) | + AD5758_DCDC_CONFIG1_PROT_SW_EN_MODE(val); + dcdc_config1_msk = AD5758_DCDC_CONFIG1_DCDC_MODE_MSK | + AD5758_DCDC_CONFIG1_PROT_SW_EN_MSK; + + ret = ad5758_spi_write_mask(st, AD5758_DCDC_CONFIG1, + dcdc_config1_msk, + dcdc_config1_mode); + if (ret < 0) + goto err_unlock; + + dac_config_mode = AD5758_DAC_CONFIG_OUT_EN_MODE(val) | + AD5758_DAC_CONFIG_INT_EN_MODE(val); + dac_config_msk = AD5758_DAC_CONFIG_OUT_EN_MSK | + AD5758_DAC_CONFIG_INT_EN_MSK; + + ret = ad5758_spi_write_mask(st, AD5758_DAC_CONFIG, + dac_config_msk, + dac_config_mode); + if (ret < 0) + goto err_unlock; + + st->pwr_down = pwr_down; + +err_unlock: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static const struct iio_info ad5758_info = { + .read_raw = ad5758_read_raw, + .write_raw = ad5758_write_raw, + .debugfs_reg_access = &ad5758_reg_access, +}; + +static const struct iio_chan_spec_ext_info ad5758_ext_info[] = { + { + .name = "powerdown", + .read = ad5758_read_powerdown, + .write = ad5758_write_powerdown, + .shared = IIO_SHARED_BY_TYPE, + }, + { } +}; + +#define AD5758_DAC_CHAN(_chan_type) { \ + .type = (_chan_type), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .indexed = 1, \ + .output = 1, \ + .ext_info = ad5758_ext_info, \ +} + +static const struct iio_chan_spec ad5758_voltage_ch[] = { + AD5758_DAC_CHAN(IIO_VOLTAGE) +}; + +static const struct iio_chan_spec ad5758_current_ch[] = { + AD5758_DAC_CHAN(IIO_CURRENT) +}; + +static bool ad5758_is_valid_mode(enum ad5758_dc_dc_mode mode) +{ + switch (mode) { + case AD5758_DCDC_MODE_DPC_CURRENT: + case AD5758_DCDC_MODE_DPC_VOLTAGE: + case AD5758_DCDC_MODE_PPC_CURRENT: + return true; + default: + return false; + } +} + +static int ad5758_crc_disable(struct ad5758_state *st) +{ + unsigned int mask; + + mask = (AD5758_WR_FLAG_MSK(AD5758_DIGITAL_DIAG_CONFIG) << 24) | 0x5C3A; + st->d32[0] = cpu_to_be32(mask); + + return spi_write(st->spi, &st->d32[0], 4); +} + +static int ad5758_find_out_range(struct ad5758_state *st, + const struct ad5758_range *range, + unsigned int size, + int min, int max) +{ + int i; + + for (i = 0; i < size; i++) { + if ((min == range[i].min) && (max == range[i].max)) { + st->out_range.reg = range[i].reg; + st->out_range.min = range[i].min; + st->out_range.max = range[i].max; + + return 0; + } + } + + return -EINVAL; +} + +static int ad5758_parse_dt(struct ad5758_state *st) +{ + unsigned int tmp, tmparray[2], size; + const struct ad5758_range *range; + int *index, ret; + + st->dc_dc_ilim = 0; + ret = device_property_read_u32(&st->spi->dev, + "adi,dc-dc-ilim-microamp", &tmp); + if (ret) { + dev_dbg(&st->spi->dev, + "Missing \"dc-dc-ilim-microamp\" property\n"); + } else { + index = bsearch(&tmp, ad5758_dc_dc_ilim, + ARRAY_SIZE(ad5758_dc_dc_ilim), + sizeof(int), cmpfunc); + if (!index) + dev_dbg(&st->spi->dev, "dc-dc-ilim out of range\n"); + else + st->dc_dc_ilim = index - ad5758_dc_dc_ilim; + } + + ret = device_property_read_u32(&st->spi->dev, "adi,dc-dc-mode", + &st->dc_dc_mode); + if (ret) { + dev_err(&st->spi->dev, "Missing \"dc-dc-mode\" property\n"); + return ret; + } + + if (!ad5758_is_valid_mode(st->dc_dc_mode)) + return -EINVAL; + + if (st->dc_dc_mode == AD5758_DCDC_MODE_DPC_VOLTAGE) { + ret = device_property_read_u32_array(&st->spi->dev, + "adi,range-microvolt", + tmparray, 2); + if (ret) { + dev_err(&st->spi->dev, + "Missing \"range-microvolt\" property\n"); + return ret; + } + range = ad5758_voltage_range; + size = ARRAY_SIZE(ad5758_voltage_range); + } else { + ret = device_property_read_u32_array(&st->spi->dev, + "adi,range-microamp", + tmparray, 2); + if (ret) { + dev_err(&st->spi->dev, + "Missing \"range-microamp\" property\n"); + return ret; + } + range = ad5758_current_range; + size = ARRAY_SIZE(ad5758_current_range); + } + + ret = ad5758_find_out_range(st, range, size, tmparray[0], tmparray[1]); + if (ret) { + dev_err(&st->spi->dev, "range invalid\n"); + return ret; + } + + ret = device_property_read_u32(&st->spi->dev, "adi,slew-time-us", &tmp); + if (ret) { + dev_dbg(&st->spi->dev, "Missing \"slew-time-us\" property\n"); + st->slew_time = 0; + } else { + st->slew_time = tmp; + } + + return 0; +} + +static int ad5758_init(struct ad5758_state *st) +{ + int regval, ret; + + /* Disable CRC checks */ + ret = ad5758_crc_disable(st); + if (ret < 0) + return ret; + + /* Perform a software reset */ + ret = ad5758_soft_reset(st); + if (ret < 0) + return ret; + + /* Disable CRC checks */ + ret = ad5758_crc_disable(st); + if (ret < 0) + return ret; + + /* Perform a calibration memory refresh */ + ret = ad5758_calib_mem_refresh(st); + if (ret < 0) + return ret; + + regval = ad5758_spi_reg_read(st, AD5758_DIGITAL_DIAG_RESULTS); + if (regval < 0) + return regval; + + /* Clear all the error flags */ + ret = ad5758_spi_reg_write(st, AD5758_DIGITAL_DIAG_RESULTS, regval); + if (ret < 0) + return ret; + + /* Set the dc-to-dc current limit */ + ret = ad5758_set_dc_dc_ilim(st, st->dc_dc_ilim); + if (ret < 0) + return ret; + + /* Configure the dc-to-dc controller mode */ + ret = ad5758_set_dc_dc_conv_mode(st, st->dc_dc_mode); + if (ret < 0) + return ret; + + /* Configure the output range */ + ret = ad5758_set_out_range(st, st->out_range.reg); + if (ret < 0) + return ret; + + /* Enable Slew Rate Control, set the slew rate clock and step */ + if (st->slew_time) { + ret = ad5758_slew_rate_config(st); + if (ret < 0) + return ret; + } + + /* Enable the VIOUT fault protection switch (FPS is closed) */ + ret = ad5758_fault_prot_switch_en(st, 1); + if (ret < 0) + return ret; + + /* Power up the DAC and internal (INT) amplifiers */ + ret = ad5758_internal_buffers_en(st, 1); + if (ret < 0) + return ret; + + /* Enable VIOUT */ + return ad5758_spi_write_mask(st, AD5758_DAC_CONFIG, + AD5758_DAC_CONFIG_OUT_EN_MSK, + AD5758_DAC_CONFIG_OUT_EN_MODE(1)); +} + +static int ad5758_probe(struct spi_device *spi) +{ + struct ad5758_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); + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + mutex_init(&st->lock); + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5758_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = 1; + + ret = ad5758_parse_dt(st); + if (ret < 0) + return ret; + + if (st->dc_dc_mode == AD5758_DCDC_MODE_DPC_VOLTAGE) + indio_dev->channels = ad5758_voltage_ch; + else + indio_dev->channels = ad5758_current_ch; + + ret = ad5758_init(st); + if (ret < 0) { + dev_err(&spi->dev, "AD5758 init failed\n"); + return ret; + } + + return devm_iio_device_register(&st->spi->dev, indio_dev); +} + +static const struct spi_device_id ad5758_id[] = { + { "ad5758", 0 }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5758_id); + +static struct spi_driver ad5758_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = ad5758_probe, + .id_table = ad5758_id, +}; + +module_spi_driver(ad5758_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_DESCRIPTION("Analog Devices AD5758 DAC"); +MODULE_LICENSE("GPL v2"); From 777baca07ef179301e363b10a14c428e5ebbd6a6 Mon Sep 17 00:00:00 2001 From: Stefan Popa Date: Wed, 4 Jul 2018 17:32:53 +0300 Subject: [PATCH 15/39] dt-bindings: iio: dac: Add docs for AD5758 DAC Signed-off-by: Stefan Popa Reviewed-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/dac/ad5758.txt | 78 +++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 79 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/ad5758.txt diff --git a/Documentation/devicetree/bindings/iio/dac/ad5758.txt b/Documentation/devicetree/bindings/iio/dac/ad5758.txt new file mode 100644 index 000000000000..bba01a5cab1b --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/ad5758.txt @@ -0,0 +1,78 @@ +Analog Devices AD5758 DAC device driver + +Required properties for the AD5758: + - compatible: Must be "adi,ad5758" + - reg: SPI chip select number for the device + - spi-max-frequency: Max SPI frequency to use (< 50000000) + - spi-cpha: is the only mode that is supported + +Required properties: + + - adi,dc-dc-mode: Mode of operation of the dc-to-dc converter + Dynamic Power Control (DPC) + In this mode, the AD5758 circuitry senses the output + voltage and dynamically regulates the supply voltage, + VDPC+, to meet compliance requirements plus an optimized + headroom voltage for the output buffer. + + Programmable Power Control (PPC) + In this mode, the VDPC+ voltage is user-programmable to + a fixed level that needs to accommodate the maximum output + load required. + + The output of the DAC core is either converted to a + current or voltage output at the VIOUT pin. Only one mode + can be enabled at any one time. + + The following values are currently supported: + * 1: DPC current mode + * 2: DPC voltage mode + * 3: PPC current mode + + Depending on the selected output mode (voltage or current) one of the + two properties must + be present: + + - adi,range-microvolt: Voltage output range + The array of voltage output ranges must contain two fields: + * <0 5000000>: 0 V to 5 V voltage range + * <0 10000000>: 0 V to 10 V voltage range + * <(-5000000) 5000000>: ±5 V voltage range + * <(-10000000) 10000000>: ±10 V voltage range + - adi,range-microamp: Current output range + The array of current output ranges must contain two fields: + * <0 20000>: 0 mA to 20 mA current range + * <0 24000>: 0 mA to 24 mA current range + * <4 24000>: 4 mA to 20 mA current range + * <(-20000) 20000>: ±20 mA current range + * <(-24000) 24000>: ±24 mA current range + * <(-1000) 22000>: −1 mA to +22 mA current range + +Optional properties: + + - adi,dc-dc-ilim-microamp: The dc-to-dc converter current limit + The following values are currently supported [uA]: + * 150000 + * 200000 + * 250000 + * 300000 + * 350000 + * 400000 + + - adi,slew-time-us: The time it takes for the output to reach the + full scale [uS] + The supported range is between 133us up to 1023984375us + +AD5758 Example: + + dac@0 { + compatible = "adi,ad5758"; + reg = <0>; + spi-max-frequency = <1000000>; + spi-cpha; + + adi,dc-dc-mode = <2>; + adi,range-microvolt = <0 10000000>; + adi,dc-dc-ilim-microamp = <200000>; + adi,slew-time-us = <125000>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 728ae34b7b32..83a439c65925 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -802,6 +802,7 @@ L: linux-iio@vger.kernel.org W: http://ez.analog.com/community/linux-device-drivers S: Supported F: drivers/iio/dac/ad5758.c +F: Documentation/devicetree/bindings/iio/dac/ad5758.txt ANALOG DEVICES INC AD5686 DRIVER M: Stefan Popa From 794ac821cc44e4d148df36eb330a7e9729c06a82 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Mon, 21 May 2018 11:49:09 +0200 Subject: [PATCH 16/39] iio: adc: max1363: merge calls to of_match_device and of_device_get_match_data Drop call to of_match_device, which is subsumed by the subsequent call to of_device_get_match_data. The code becomes simpler, and a temporary variable can be dropped. The semantic match that makes this change is as follows: (http://coccinelle.lip6.fr/) // @r@ local idexpression match; identifier i; expression x, dev, e, e1; @@ - match@i = of_match_device(x, dev); - if (match) e = of_device_get_match_data(dev); - else e = e1; + e = of_device_get_match_data(dev); + if (!e) e = e1; @@ identifier r.i; @@ - const struct of_device_id *i; ... when != i // Signed-off-by: Julia Lawall Signed-off-by: Jonathan Cameron --- drivers/iio/adc/max1363.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/max1363.c b/drivers/iio/adc/max1363.c index 7fb4f525714a..a8d35aebee80 100644 --- a/drivers/iio/adc/max1363.c +++ b/drivers/iio/adc/max1363.c @@ -1577,7 +1577,6 @@ static int max1363_probe(struct i2c_client *client, struct max1363_state *st; struct iio_dev *indio_dev; struct regulator *vref; - const struct of_device_id *match; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(struct max1363_state)); @@ -1604,11 +1603,8 @@ static int max1363_probe(struct i2c_client *client, /* this is only used for device removal purposes */ i2c_set_clientdata(client, indio_dev); - match = of_match_device(of_match_ptr(max1363_of_match), - &client->dev); - if (match) - st->chip_info = of_device_get_match_data(&client->dev); - else + st->chip_info = of_device_get_match_data(&client->dev); + if (!st->chip_info) st->chip_info = &max1363_chip_info_tbl[id->driver_data]; st->client = client; From 382fa5812583b6c3a4d02a5830b70ec7ee2f07a3 Mon Sep 17 00:00:00 2001 From: Akinobu Mita Date: Tue, 10 Jul 2018 00:10:08 +0900 Subject: [PATCH 17/39] iio: accel: adxl345: add sampling frequency support The ADXL345 provides selectable output data rate. This adds the iio channel information for the sampling frequency with that feature. Cc: Eva Rachel Retuya Cc: Andy Shevchenko Cc: Jonathan Cameron Signed-off-by: Akinobu Mita Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345_core.c | 57 +++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index 7b29ae8375e9..3359f33db4e3 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -14,6 +14,7 @@ #include #include +#include #include "adxl345.h" @@ -22,6 +23,7 @@ #define ADXL345_REG_OFSY 0x1f #define ADXL345_REG_OFSZ 0x20 #define ADXL345_REG_OFS_AXIS(index) (ADXL345_REG_OFSX + (index)) +#define ADXL345_REG_BW_RATE 0x2C #define ADXL345_REG_POWER_CTL 0x2D #define ADXL345_REG_DATA_FORMAT 0x31 #define ADXL345_REG_DATAX0 0x32 @@ -30,6 +32,10 @@ #define ADXL345_REG_DATA_AXIS(index) \ (ADXL345_REG_DATAX0 + (index) * sizeof(__le16)) +#define ADXL345_BW_RATE GENMASK(3, 0) +#define ADXL345_BASE_RATE_NANO_HZ 97656250LL +#define NHZ_PER_HZ 1000000000LL + #define ADXL345_POWER_CTL_MEASURE BIT(3) #define ADXL345_POWER_CTL_STANDBY 0x00 @@ -62,7 +68,8 @@ struct adxl345_data { .address = index, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ BIT(IIO_CHAN_INFO_CALIBBIAS), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ } static const struct iio_chan_spec adxl345_channels[] = { @@ -77,6 +84,7 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, { struct adxl345_data *data = iio_priv(indio_dev); __le16 accel; + long long samp_freq_nhz; unsigned int regval; int ret; @@ -112,6 +120,16 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, *val = sign_extend32(regval, 7) * 4; return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(data->regmap, ADXL345_REG_BW_RATE, ®val); + if (ret < 0) + return ret; + + samp_freq_nhz = ADXL345_BASE_RATE_NANO_HZ << + (regval & ADXL345_BW_RATE); + *val = div_s64_rem(samp_freq_nhz, NHZ_PER_HZ, val2); + + return IIO_VAL_INT_PLUS_NANO; } return -EINVAL; @@ -122,6 +140,7 @@ static int adxl345_write_raw(struct iio_dev *indio_dev, int val, int val2, long mask) { struct adxl345_data *data = iio_priv(indio_dev); + s64 n; switch (mask) { case IIO_CHAN_INFO_CALIBBIAS: @@ -132,14 +151,50 @@ static int adxl345_write_raw(struct iio_dev *indio_dev, return regmap_write(data->regmap, ADXL345_REG_OFS_AXIS(chan->address), val / 4); + case IIO_CHAN_INFO_SAMP_FREQ: + n = div_s64(val * NHZ_PER_HZ + val2, ADXL345_BASE_RATE_NANO_HZ); + + return regmap_update_bits(data->regmap, ADXL345_REG_BW_RATE, + ADXL345_BW_RATE, + clamp_val(ilog2(n), 0, + ADXL345_BW_RATE)); } return -EINVAL; } +static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( +"0.09765625 0.1953125 0.390625 0.78125 1.5625 3.125 6.25 12.5 25 50 100 200 400 800 1600 3200" +); + +static struct attribute *adxl345_attrs[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group adxl345_attrs_group = { + .attrs = adxl345_attrs, +}; + static const struct iio_info adxl345_info = { + .attrs = &adxl345_attrs_group, .read_raw = adxl345_read_raw, .write_raw = adxl345_write_raw, + .write_raw_get_fmt = adxl345_write_raw_get_fmt, }; int adxl345_core_probe(struct device *dev, struct regmap *regmap, From 7f310e5d07112e0bae57ce63de954e6773a26f81 Mon Sep 17 00:00:00 2001 From: Andreas Klinger Date: Tue, 10 Jul 2018 20:18:31 +0200 Subject: [PATCH 18/39] iio: hx711: add clock-frequency property in DT Add clock-frequency property for hx711 ADC. This is the frequency of PD_SCK. After PD_SCK goes high DOUT is read just before PD_SCK goes down again. This is necessary because of parasitic capacitance on the wiring. Signed-off-by: Andreas Klinger Reviewed-by: Rob Herring Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/avia-hx711.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/avia-hx711.txt b/Documentation/devicetree/bindings/iio/adc/avia-hx711.txt index b3629405f568..2c1b67d33c49 100644 --- a/Documentation/devicetree/bindings/iio/adc/avia-hx711.txt +++ b/Documentation/devicetree/bindings/iio/adc/avia-hx711.txt @@ -8,11 +8,17 @@ Required properties: See Documentation/devicetree/bindings/gpio/gpio.txt - avdd-supply: Definition of the regulator used as analog supply +Optional properties: + - clock-frequency: Frequency of PD_SCK in Hz + Minimum value allowed is 10 kHz because of maximum + high time of 50 microseconds. + Example: weight@0 { compatible = "avia,hx711"; sck-gpios = <&gpio3 10 GPIO_ACTIVE_HIGH>; dout-gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>; avdd-suppy = <&avdd>; + clock-frequency = <100000>; }; From 461631face58054c72b1f1453f2d66d71b1974e7 Mon Sep 17 00:00:00 2001 From: Andreas Klinger Date: Tue, 10 Jul 2018 20:19:03 +0200 Subject: [PATCH 19/39] iio: hx711: add delay until DOUT is ready On a system with parasitic capacitance it turned out that DOUT is not ready after 100 ns after PD_SCK has raised. A measurement showed almost 1000 ns until DOUT has reached its correct value. With this patch its now possible to wait until data is ready. The wait time should not be higher than the maximum PD_SCK high time which is corresponding to the datasheet 50000 ns. Signed-off-by: Andreas Klinger Signed-off-by: Jonathan Cameron --- drivers/iio/adc/hx711.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/iio/adc/hx711.c b/drivers/iio/adc/hx711.c index 9430b54121e0..36b59d8957fb 100644 --- a/drivers/iio/adc/hx711.c +++ b/drivers/iio/adc/hx711.c @@ -97,6 +97,14 @@ struct hx711_data { * 2x32-bit channel + 64-bit timestamp */ u32 buffer[4]; + /* + * delay after a rising edge on SCK until the data is ready DOUT + * this is dependent on the hx711 where the datasheet tells a + * maximum value of 100 ns + * but also on potential parasitic capacities on the wiring + */ + u32 data_ready_delay_ns; + u32 clock_frequency; }; static int hx711_cycle(struct hx711_data *hx711_data) @@ -110,6 +118,14 @@ static int hx711_cycle(struct hx711_data *hx711_data) */ preempt_disable(); gpiod_set_value(hx711_data->gpiod_pd_sck, 1); + + /* + * wait until DOUT is ready + * it turned out that parasitic capacities are extending the time + * until DOUT has reached it's value + */ + ndelay(hx711_data->data_ready_delay_ns); + val = gpiod_get_value(hx711_data->gpiod_dout); /* * here we are not waiting for 0.2 us as suggested by the datasheet, @@ -120,6 +136,12 @@ static int hx711_cycle(struct hx711_data *hx711_data) gpiod_set_value(hx711_data->gpiod_pd_sck, 0); preempt_enable(); + /* + * make it a square wave for addressing cases with capacitance on + * PC_SCK + */ + ndelay(hx711_data->data_ready_delay_ns); + return val; } @@ -458,6 +480,7 @@ static const struct iio_chan_spec hx711_chan_spec[] = { static int hx711_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; struct hx711_data *hx711_data; struct iio_dev *indio_dev; int ret; @@ -530,6 +553,22 @@ static int hx711_probe(struct platform_device *pdev) hx711_data->gain_set = 128; hx711_data->gain_chan_a = 128; + hx711_data->clock_frequency = 400000; + ret = of_property_read_u32(np, "clock-frequency", + &hx711_data->clock_frequency); + + /* + * datasheet says the high level of PD_SCK has a maximum duration + * of 50 microseconds + */ + if (hx711_data->clock_frequency < 20000) { + dev_warn(dev, "clock-frequency too low - assuming 400 kHz\n"); + hx711_data->clock_frequency = 400000; + } + + hx711_data->data_ready_delay_ns = + 1000000000 / hx711_data->clock_frequency; + platform_set_drvdata(pdev, indio_dev); indio_dev->name = "hx711"; From 843429708e394a31d3298f9b6dfa8fca180f8bfa Mon Sep 17 00:00:00 2001 From: Andreas Klinger Date: Tue, 10 Jul 2018 20:19:37 +0200 Subject: [PATCH 20/39] iio: hx711: fix spurious unit-address in example Device tree compiler (dtc) gives a warning if a device node has "@" with a following number as it's name but no reg property. Fix the example in the documentation of avia,hx711 to conform to dtc behavior. Signed-off-by: Andreas Klinger Reviewed-by: Rob Herring Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/avia-hx711.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/adc/avia-hx711.txt b/Documentation/devicetree/bindings/iio/adc/avia-hx711.txt index 2c1b67d33c49..7222328a3d0d 100644 --- a/Documentation/devicetree/bindings/iio/adc/avia-hx711.txt +++ b/Documentation/devicetree/bindings/iio/adc/avia-hx711.txt @@ -14,7 +14,7 @@ Optional properties: high time of 50 microseconds. Example: -weight@0 { +weight { compatible = "avia,hx711"; sck-gpios = <&gpio3 10 GPIO_ACTIVE_HIGH>; dout-gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>; From de8df0b9c38d8f232f0df03220ff540a54eaf73d Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 10 Jul 2018 21:09:30 -0400 Subject: [PATCH 21/39] iio: imu: mpu6050: add support for 6515 variant This patch adds support for the MPU 6515 variant. Confirmed that the driver functions correctly on a LG Nexus 5 (hammerhead) phone. Signed-off-by: Brian Masney Signed-off-by: Jonathan Marek Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/imu/inv_mpu6050.txt | 1 + drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 6 ++++++ drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c | 5 +++++ drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h | 2 ++ 4 files changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/imu/inv_mpu6050.txt b/Documentation/devicetree/bindings/iio/imu/inv_mpu6050.txt index 5f4777e8cc9e..b7def51c8ad9 100644 --- a/Documentation/devicetree/bindings/iio/imu/inv_mpu6050.txt +++ b/Documentation/devicetree/bindings/iio/imu/inv_mpu6050.txt @@ -6,6 +6,7 @@ Required properties: - compatible : should be one of "invensense,mpu6050" "invensense,mpu6500" + "invensense,mpu6515" "invensense,mpu9150" "invensense,mpu9250" "invensense,mpu9255" diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index de68e83fc52d..12c1b9507007 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -103,6 +103,12 @@ static const struct inv_mpu6050_hw hw_info[] = { .reg = ®_set_6500, .config = &chip_config_6050, }, + { + .whoami = INV_MPU6515_WHOAMI_VALUE, + .name = "MPU6515", + .reg = ®_set_6500, + .config = &chip_config_6050, + }, { .whoami = INV_MPU6000_WHOAMI_VALUE, .name = "MPU6000", diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c index 495409d56207..dd758e3d403d 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c @@ -174,6 +174,7 @@ static int inv_mpu_remove(struct i2c_client *client) static const struct i2c_device_id inv_mpu_id[] = { {"mpu6050", INV_MPU6050}, {"mpu6500", INV_MPU6500}, + {"mpu6515", INV_MPU6515}, {"mpu9150", INV_MPU9150}, {"mpu9250", INV_MPU9250}, {"mpu9255", INV_MPU9255}, @@ -192,6 +193,10 @@ static const struct of_device_id inv_of_match[] = { .compatible = "invensense,mpu6500", .data = (void *)INV_MPU6500 }, + { + .compatible = "invensense,mpu6515", + .data = (void *)INV_MPU6515 + }, { .compatible = "invensense,mpu9150", .data = (void *)INV_MPU9150 diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h index de8391693e17..e69a59659dbc 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -71,6 +71,7 @@ struct inv_mpu6050_reg_map { enum inv_devices { INV_MPU6050, INV_MPU6500, + INV_MPU6515, INV_MPU6000, INV_MPU9150, INV_MPU9250, @@ -256,6 +257,7 @@ struct inv_mpu6050_state { #define INV_MPU9150_WHOAMI_VALUE 0x68 #define INV_MPU9250_WHOAMI_VALUE 0x71 #define INV_MPU9255_WHOAMI_VALUE 0x73 +#define INV_MPU6515_WHOAMI_VALUE 0x74 #define INV_ICM20608_WHOAMI_VALUE 0xAF /* scan element definition */ From d2b863baf1c7d92969c2a9dcada3c6b14e5dbbc4 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 10 Jul 2018 21:33:45 -0400 Subject: [PATCH 22/39] iio: pressure: bmp280: remove unused options from device tree documentation There are several options in the device tree documentation that are no longer relevant for the current in-kernel bmp280 driver so this patch removes them. Signed-off-by: Brian Masney Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/pressure/bmp085.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/pressure/bmp085.txt b/Documentation/devicetree/bindings/iio/pressure/bmp085.txt index c7198a03c906..abcab02504fb 100644 --- a/Documentation/devicetree/bindings/iio/pressure/bmp085.txt +++ b/Documentation/devicetree/bindings/iio/pressure/bmp085.txt @@ -8,10 +8,6 @@ Required properties: "bosch,bme280" Optional properties: -- chip-id: configurable chip id for non-default chip revisions -- temp-measurement-period: temperature measurement period (milliseconds) -- default-oversampling: default oversampling value to be used at startup, - value range is 0-3 with rising sensitivity. - interrupt-parent: should be the phandle for the interrupt controller - interrupts: interrupt mapping for IRQ - reset-gpios: a GPIO line handling reset of the sensor: as the line is @@ -24,9 +20,6 @@ Example: pressure@77 { compatible = "bosch,bmp085"; reg = <0x77>; - chip-id = <10>; - temp-measurement-period = <100>; - default-oversampling = <2>; interrupt-parent = <&gpio0>; interrupts = <25 IRQ_TYPE_EDGE_RISING>; reset-gpios = <&gpio0 26 GPIO_ACTIVE_LOW>; From a42174983c7545055bd8375965e31b3c15b287dc Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 12 Jul 2018 00:15:12 +0200 Subject: [PATCH 23/39] iio: imu: st_lsm6dsx: add error logs to st_lsm6dsx_read_fifo() Add debug info to error conditions in st_lsm6dsx_read_fifo routine Suggested-by: Jorge Ramirez-Ortiz Signed-off-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c index 4994f920a836..7589f2ad1dae 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c @@ -298,8 +298,11 @@ static int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw) err = regmap_bulk_read(hw->regmap, hw->settings->fifo_ops.fifo_diff.addr, &fifo_status, sizeof(fifo_status)); - if (err < 0) + if (err < 0) { + dev_err(hw->dev, "failed to read fifo status (err=%d)\n", + err); return err; + } if (fifo_status & cpu_to_le16(ST_LSM6DSX_FIFO_EMPTY_MASK)) return 0; @@ -313,8 +316,12 @@ static int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw) for (read_len = 0; read_len < fifo_len; read_len += pattern_len) { err = st_lsm6dsx_read_block(hw, hw->buff, pattern_len); - if (err < 0) + if (err < 0) { + dev_err(hw->dev, + "failed to read pattern from fifo (err=%d)\n", + err); return err; + } /* * Data are written to the FIFO with a specific pattern @@ -385,8 +392,11 @@ static int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw) if (unlikely(reset_ts)) { err = st_lsm6dsx_reset_hw_ts(hw); - if (err < 0) + if (err < 0) { + dev_err(hw->dev, "failed to reset hw ts (err=%d)\n", + err); return err; + } } return read_len; } From ef89f4b96a2ab8dc1a0a3815d9365240e4c3c06b Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 13 Jul 2018 14:50:44 +0300 Subject: [PATCH 24/39] iio: adxl345: Add support for the ADXL375 The ADXL375 is fully register map compatible to the ADXL345 (including the device ID register returning the same value ...). The only difference is the resolution of the acceleration sensor. The ADXL375 can measure up to +-200g of acceleration. Datasheet: http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL375.PDF Signed-off-by: Lars-Peter Clausen Signed-off-by: Mircea Caprioru Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/accel/adxl345.txt | 7 +++++-- drivers/iio/accel/Kconfig | 4 ++-- drivers/iio/accel/adxl345.h | 7 ++++++- drivers/iio/accel/adxl345_core.c | 19 +++++++++++++++++-- drivers/iio/accel/adxl345_i2c.c | 7 +++++-- drivers/iio/accel/adxl345_spi.c | 6 ++++-- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/accel/adxl345.txt b/Documentation/devicetree/bindings/iio/accel/adxl345.txt index e7111b02c02c..bb7fc0bd5776 100644 --- a/Documentation/devicetree/bindings/iio/accel/adxl345.txt +++ b/Documentation/devicetree/bindings/iio/accel/adxl345.txt @@ -1,9 +1,12 @@ -Analog Devices ADXL345 3-Axis, +/-(2g/4g/8g/16g) Digital Accelerometer +Analog Devices ADXL345/ADXL375 3-Axis Digital Accelerometers http://www.analog.com/en/products/mems/accelerometers/adxl345.html +http://www.analog.com/en/products/sensors-mems/accelerometers/adxl375.html Required properties: - - compatible : should be "adi,adxl345" + - compatible : should be one of + "adi,adxl345" + "adi,adxl375" - reg : the I2C address or SPI chip select number of the sensor Required properties for SPI bus usage: diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 62ae7e5abcfa..829dc96c9dd6 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -40,7 +40,7 @@ config ADXL345_I2C select REGMAP_I2C help Say Y here if you want to build support for the Analog Devices - ADXL345 3-axis digital accelerometer. + ADXL345 or ADXL375 3-axis digital accelerometer. To compile this driver as a module, choose M here: the module will be called adxl345_i2c and you will also get adxl345_core @@ -54,7 +54,7 @@ config ADXL345_SPI select REGMAP_SPI help Say Y here if you want to build support for the Analog Devices - ADXL345 3-axis digital accelerometer. + ADXL345 or ADXL375 3-axis digital accelerometer. To compile this driver as a module, choose M here: the module will be called adxl345_spi and you will also get adxl345_core diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h index c1ddf3927c47..ccd63de7a55a 100644 --- a/drivers/iio/accel/adxl345.h +++ b/drivers/iio/accel/adxl345.h @@ -11,8 +11,13 @@ #ifndef _ADXL345_H_ #define _ADXL345_H_ +enum adxl345_device_type { + ADXL345, + ADXL375, +}; + int adxl345_core_probe(struct device *dev, struct regmap *regmap, - const char *name); + enum adxl345_device_type type, const char *name); int adxl345_core_remove(struct device *dev); #endif /* _ADXL345_H_ */ diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index 3359f33db4e3..780f87f72338 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -56,9 +56,16 @@ */ static const int adxl345_uscale = 38300; +/* + * The Datasheet lists a resolution of Resolution is ~49 mg per LSB. That's + * ~480mm/s**2 per LSB. + */ +static const int adxl375_uscale = 480000; + struct adxl345_data { struct regmap *regmap; u8 data_range; + enum adxl345_device_type type; }; #define ADXL345_CHANNEL(index, axis) { \ @@ -105,7 +112,14 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = 0; - *val2 = adxl345_uscale; + switch (data->type) { + case ADXL345: + *val2 = adxl345_uscale; + break; + case ADXL375: + *val2 = adxl375_uscale; + break; + } return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_CALIBBIAS: @@ -198,7 +212,7 @@ static const struct iio_info adxl345_info = { }; int adxl345_core_probe(struct device *dev, struct regmap *regmap, - const char *name) + enum adxl345_device_type type, const char *name) { struct adxl345_data *data; struct iio_dev *indio_dev; @@ -224,6 +238,7 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap, data = iio_priv(indio_dev); dev_set_drvdata(dev, indio_dev); data->regmap = regmap; + data->type = type; /* Enable full-resolution mode */ data->data_range = ADXL345_DATA_FORMAT_FULL_RES; diff --git a/drivers/iio/accel/adxl345_i2c.c b/drivers/iio/accel/adxl345_i2c.c index 05e1ec49700c..785c89de91e7 100644 --- a/drivers/iio/accel/adxl345_i2c.c +++ b/drivers/iio/accel/adxl345_i2c.c @@ -34,7 +34,8 @@ static int adxl345_i2c_probe(struct i2c_client *client, return PTR_ERR(regmap); } - return adxl345_core_probe(&client->dev, regmap, id ? id->name : NULL); + return adxl345_core_probe(&client->dev, regmap, id->driver_data, + id ? id->name : NULL); } static int adxl345_i2c_remove(struct i2c_client *client) @@ -43,7 +44,8 @@ static int adxl345_i2c_remove(struct i2c_client *client) } static const struct i2c_device_id adxl345_i2c_id[] = { - { "adxl345", 0 }, + { "adxl345", ADXL345 }, + { "adxl375", ADXL375 }, { } }; @@ -51,6 +53,7 @@ MODULE_DEVICE_TABLE(i2c, adxl345_i2c_id); static const struct of_device_id adxl345_of_match[] = { { .compatible = "adi,adxl345" }, + { .compatible = "adi,adxl375" }, { }, }; diff --git a/drivers/iio/accel/adxl345_spi.c b/drivers/iio/accel/adxl345_spi.c index 6d658196f81c..67b7c66a8492 100644 --- a/drivers/iio/accel/adxl345_spi.c +++ b/drivers/iio/accel/adxl345_spi.c @@ -42,7 +42,7 @@ static int adxl345_spi_probe(struct spi_device *spi) return PTR_ERR(regmap); } - return adxl345_core_probe(&spi->dev, regmap, id->name); + return adxl345_core_probe(&spi->dev, regmap, id->driver_data, id->name); } static int adxl345_spi_remove(struct spi_device *spi) @@ -51,7 +51,8 @@ static int adxl345_spi_remove(struct spi_device *spi) } static const struct spi_device_id adxl345_spi_id[] = { - { "adxl345", 0 }, + { "adxl345", ADXL345 }, + { "adxl375", ADXL375 }, { } }; @@ -59,6 +60,7 @@ MODULE_DEVICE_TABLE(spi, adxl345_spi_id); static const struct of_device_id adxl345_of_match[] = { { .compatible = "adi,adxl345" }, + { .compatible = "adi,adxl375" }, { }, }; From cd570e6fa43a5ed061b91e64dc507a6ac54fac95 Mon Sep 17 00:00:00 2001 From: Dominique Martinet Date: Fri, 13 Jul 2018 03:25:34 +0200 Subject: [PATCH 25/39] iio: change strncpy+truncation to strlcpy Generated by scripts/coccinelle/misc/strncpy_truncation.cocci Signed-off-by: Dominique Martinet Signed-off-by: Jonathan Cameron --- drivers/iio/common/st_sensors/st_sensors_core.c | 3 +-- drivers/iio/pressure/st_pressure_i2c.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c index 57db19182e95..26fbd1bd9413 100644 --- a/drivers/iio/common/st_sensors/st_sensors_core.c +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -380,8 +380,7 @@ void st_sensors_of_name_probe(struct device *dev, return; /* The name from the OF match takes precedence if present */ - strncpy(name, of_id->data, len); - name[len - 1] = '\0'; + strlcpy(name, of_id->data, len); } EXPORT_SYMBOL(st_sensors_of_name_probe); #else diff --git a/drivers/iio/pressure/st_pressure_i2c.c b/drivers/iio/pressure/st_pressure_i2c.c index fbb59059e942..2026a1012012 100644 --- a/drivers/iio/pressure/st_pressure_i2c.c +++ b/drivers/iio/pressure/st_pressure_i2c.c @@ -94,9 +94,8 @@ static int st_press_i2c_probe(struct i2c_client *client, if ((ret < 0) || (ret >= ST_PRESS_MAX)) return -ENODEV; - strncpy(client->name, st_press_id_table[ret].name, + strlcpy(client->name, st_press_id_table[ret].name, sizeof(client->name)); - client->name[sizeof(client->name) - 1] = '\0'; } else if (!id) return -ENODEV; From c73314e6ebb2651a70ca8a3ff08d4bd6b9f9ade1 Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Fri, 20 Jul 2018 19:34:25 +0200 Subject: [PATCH 26/39] iio: Add channel for Phase Add new channel type support for phase. This channel may be used by Time-of-flight sensors to express the phase difference between emitted and received signals. Those sensor will then use the phase shift of return signals to approximate the distance to objects. Signed-off-by: Mathieu Othacehe Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 7 +++++++ drivers/iio/industrialio-core.c | 1 + include/uapi/linux/iio/types.h | 1 + tools/iio/iio_event_monitor.c | 2 ++ 4 files changed, 11 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index c7353030670a..c9cfa833cf47 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -1675,3 +1675,10 @@ KernelVersion: 4.12 Contact: linux-iio@vger.kernel.org Description: Raw counter device counters direction for channel Y. + +What: /sys/bus/iio/devices/iio:deviceX/in_phaseY_raw +KernelVersion: 4.18 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled) phase difference reading from channel Y + that can be processed to radians. \ No newline at end of file diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index ed1b3ebade94..a16ad5a4ab0c 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -86,6 +86,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_INDEX] = "index", [IIO_GRAVITY] = "gravity", [IIO_POSITIONRELATIVE] = "positionrelative", + [IIO_PHASE] = "phase", }; static const char * const iio_modifier_names[] = { diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index 033c7d28924e..e4df3cc268db 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -45,6 +45,7 @@ enum iio_chan_type { IIO_INDEX, IIO_GRAVITY, IIO_POSITIONRELATIVE, + IIO_PHASE, }; enum iio_modifier { diff --git a/tools/iio/iio_event_monitor.c b/tools/iio/iio_event_monitor.c index 148f69dfae75..f478f5558720 100644 --- a/tools/iio/iio_event_monitor.c +++ b/tools/iio/iio_event_monitor.c @@ -59,6 +59,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_UVINDEX] = "uvindex", [IIO_GRAVITY] = "gravity", [IIO_POSITIONRELATIVE] = "positionrelative", + [IIO_PHASE] = "phase", }; static const char * const iio_ev_type_text[] = { @@ -153,6 +154,7 @@ static bool event_is_known(struct iio_event_data *event) case IIO_UVINDEX: case IIO_GRAVITY: case IIO_POSITIONRELATIVE: + case IIO_PHASE: break; default: return false; From 1c28799257bca28cd5ba715e33157500d6239333 Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Fri, 20 Jul 2018 19:34:26 +0200 Subject: [PATCH 27/39] iio: light: isl29501: Add support for the ISL29501 ToF sensor. This patch adds support for the ISL29501 Time of Flight sensor. Signed-off-by: Mathieu Othacehe Signed-off-by: Jonathan Cameron --- .../ABI/testing/sysfs-bus-iio-isl29501 | 47 + .../bindings/iio/light/isl29501.txt | 13 + drivers/iio/proximity/Kconfig | 13 + drivers/iio/proximity/Makefile | 1 + drivers/iio/proximity/isl29501.c | 1027 +++++++++++++++++ 5 files changed, 1101 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-isl29501 create mode 100644 Documentation/devicetree/bindings/iio/light/isl29501.txt create mode 100644 drivers/iio/proximity/isl29501.c diff --git a/Documentation/ABI/testing/sysfs-bus-iio-isl29501 b/Documentation/ABI/testing/sysfs-bus-iio-isl29501 new file mode 100644 index 000000000000..d009cfbbd72b --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-isl29501 @@ -0,0 +1,47 @@ +What: /sys/bus/iio/devices/iio:deviceX/in_proximity0_agc_gain +What: /sys/bus/iio/devices/iio:deviceX/in_proximity0_agc_gain_bias +KernelVersion: 4.18 +Contact: linux-iio@vger.kernel.org +Description: + This sensor has an automatic gain control (agc) loop + which sets the analog signal levels at an optimum + level by controlling programmable gain amplifiers. The + criteria for optimal gain is determined by the sensor. + + Return the actual gain value as an integer in [0; 65536] + range when read from. + + The agc gain read when measuring crosstalk shall be + written into in_proximity0_agc_gain_bias. + +What: /sys/bus/iio/devices/iio:deviceX/in_proximity0_calib_phase_temp_a +What: /sys/bus/iio/devices/iio:deviceX/in_proximity0_calib_phase_temp_b +What: /sys/bus/iio/devices/iio:deviceX/in_proximity0_calib_phase_light_a +What: /sys/bus/iio/devices/iio:deviceX/in_proximity0_calib_phase_light_b +KernelVersion: 4.18 +Contact: linux-iio@vger.kernel.org +Description: + The sensor is able to perform correction of distance + measurements due to changing temperature and ambient + light conditions. It can be programmed to correct for + a second order error polynomial. + + Phase data has to be collected when temperature and + ambient light are modulated independently. + + Then a least squares curve fit to a second order + polynomial has to be generated from the data. The + resultant curves have the form ax^2 + bx + c. + + From those two curves, a and b coefficients shall be + stored in in_proximity0_calib_phase_temp_a and + in_proximity0_calib_phase_temp_b for temperature and + in in_proximity0_calib_phase_light_a and + in_proximity0_calib_phase_light_b for ambient light. + + Those values must be integer in [0; 8355840] range. + + Finally, the c constant is set by the sensor + internally. + + The value stored in sensor is displayed when read from. diff --git a/Documentation/devicetree/bindings/iio/light/isl29501.txt b/Documentation/devicetree/bindings/iio/light/isl29501.txt new file mode 100644 index 000000000000..46957997fee3 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/light/isl29501.txt @@ -0,0 +1,13 @@ +* ISL29501 Time-of-flight sensor. + +Required properties: + + - compatible : should be "renesas,isl29501" + - reg : the I2C address of the sensor + +Example: + +isl29501@57 { + compatible = "renesas,isl29501"; + reg = <0x57>; +}; diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig index f726f9427602..388ef70c11d2 100644 --- a/drivers/iio/proximity/Kconfig +++ b/drivers/iio/proximity/Kconfig @@ -20,6 +20,19 @@ endmenu menu "Proximity and distance sensors" +config ISL29501 + tristate "Intersil ISL29501 Time Of Flight sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_KFIFO_BUF + help + Say Y here if you want to build a driver for the Intersil ISL29501 + Time of Flight sensor. + + To compile this driver as a module, choose M here: the module will be + called isl29501. + config LIDAR_LITE_V2 tristate "PulsedLight LIDAR sensor" select IIO_BUFFER diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile index 4f4ed45e87ef..cac3d7d3325e 100644 --- a/drivers/iio/proximity/Makefile +++ b/drivers/iio/proximity/Makefile @@ -5,6 +5,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AS3935) += as3935.o +obj-$(CONFIG_ISL29501) += isl29501.o obj-$(CONFIG_LIDAR_LITE_V2) += pulsedlight-lidar-lite-v2.o obj-$(CONFIG_RFD77402) += rfd77402.o obj-$(CONFIG_SRF04) += srf04.o diff --git a/drivers/iio/proximity/isl29501.c b/drivers/iio/proximity/isl29501.c new file mode 100644 index 000000000000..e5e94540f404 --- /dev/null +++ b/drivers/iio/proximity/isl29501.c @@ -0,0 +1,1027 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * isl29501.c: ISL29501 Time of Flight sensor driver. + * + * Copyright (C) 2018 + * Author: Mathieu Othacehe + * + * 7-bit I2C slave address: 0x57 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Control, setting and status registers */ +#define ISL29501_DEVICE_ID 0x00 +#define ISL29501_ID 0x0A + +/* Sampling control registers */ +#define ISL29501_INTEGRATION_PERIOD 0x10 +#define ISL29501_SAMPLE_PERIOD 0x11 + +/* Closed loop calibration registers */ +#define ISL29501_CROSSTALK_I_MSB 0x24 +#define ISL29501_CROSSTALK_I_LSB 0x25 +#define ISL29501_CROSSTALK_I_EXPONENT 0x26 +#define ISL29501_CROSSTALK_Q_MSB 0x27 +#define ISL29501_CROSSTALK_Q_LSB 0x28 +#define ISL29501_CROSSTALK_Q_EXPONENT 0x29 +#define ISL29501_CROSSTALK_GAIN_MSB 0x2A +#define ISL29501_CROSSTALK_GAIN_LSB 0x2B +#define ISL29501_MAGNITUDE_REF_EXP 0x2C +#define ISL29501_MAGNITUDE_REF_MSB 0x2D +#define ISL29501_MAGNITUDE_REF_LSB 0x2E +#define ISL29501_PHASE_OFFSET_MSB 0x2F +#define ISL29501_PHASE_OFFSET_LSB 0x30 + +/* Analog control registers */ +#define ISL29501_DRIVER_RANGE 0x90 +#define ISL29501_EMITTER_DAC 0x91 + +#define ISL29501_COMMAND_REGISTER 0xB0 + +/* Commands */ +#define ISL29501_EMUL_SAMPLE_START_PIN 0x49 +#define ISL29501_RESET_ALL_REGISTERS 0xD7 +#define ISL29501_RESET_INT_SM 0xD1 + +/* Ambiant light and temperature corrections */ +#define ISL29501_TEMP_REFERENCE 0x31 +#define ISL29501_PHASE_EXPONENT 0x33 +#define ISL29501_TEMP_COEFF_A 0x34 +#define ISL29501_TEMP_COEFF_B 0x39 +#define ISL29501_AMBIANT_COEFF_A 0x36 +#define ISL29501_AMBIANT_COEFF_B 0x3B + +/* Data output registers */ +#define ISL29501_DISTANCE_MSB_DATA 0xD1 +#define ISL29501_DISTANCE_LSB_DATA 0xD2 +#define ISL29501_PRECISION_MSB 0xD3 +#define ISL29501_PRECISION_LSB 0xD4 +#define ISL29501_MAGNITUDE_EXPONENT 0xD5 +#define ISL29501_MAGNITUDE_MSB 0xD6 +#define ISL29501_MAGNITUDE_LSB 0xD7 +#define ISL29501_PHASE_MSB 0xD8 +#define ISL29501_PHASE_LSB 0xD9 +#define ISL29501_I_RAW_EXPONENT 0xDA +#define ISL29501_I_RAW_MSB 0xDB +#define ISL29501_I_RAW_LSB 0xDC +#define ISL29501_Q_RAW_EXPONENT 0xDD +#define ISL29501_Q_RAW_MSB 0xDE +#define ISL29501_Q_RAW_LSB 0xDF +#define ISL29501_DIE_TEMPERATURE 0xE2 +#define ISL29501_AMBIENT_LIGHT 0xE3 +#define ISL29501_GAIN_MSB 0xE6 +#define ISL29501_GAIN_LSB 0xE7 + +#define ISL29501_MAX_EXP_VAL 15 + +#define ISL29501_INT_TIME_AVAILABLE \ + "0.00007 0.00014 0.00028 0.00057 0.00114 " \ + "0.00228 0.00455 0.00910 0.01820 0.03640 " \ + "0.07281 0.14561" + +#define ISL29501_CURRENT_SCALE_AVAILABLE \ + "0.0039 0.0078 0.0118 0.0157 0.0196 " \ + "0.0235 0.0275 0.0314 0.0352 0.0392 " \ + "0.0431 0.0471 0.0510 0.0549 0.0588" + +enum isl29501_correction_coeff { + COEFF_TEMP_A, + COEFF_TEMP_B, + COEFF_LIGHT_A, + COEFF_LIGHT_B, + COEFF_MAX, +}; + +struct isl29501_private { + struct i2c_client *client; + struct mutex lock; + /* Exact representation of correction coefficients. */ + unsigned int shadow_coeffs[COEFF_MAX]; +}; + +enum isl29501_register_name { + REG_DISTANCE, + REG_PHASE, + REG_TEMPERATURE, + REG_AMBIENT_LIGHT, + REG_GAIN, + REG_GAIN_BIAS, + REG_PHASE_EXP, + REG_CALIB_PHASE_TEMP_A, + REG_CALIB_PHASE_TEMP_B, + REG_CALIB_PHASE_LIGHT_A, + REG_CALIB_PHASE_LIGHT_B, + REG_DISTANCE_BIAS, + REG_TEMPERATURE_BIAS, + REG_INT_TIME, + REG_SAMPLE_TIME, + REG_DRIVER_RANGE, + REG_EMITTER_DAC, +}; + +struct isl29501_register_desc { + u8 msb; + u8 lsb; +}; + +static const struct isl29501_register_desc isl29501_registers[] = { + [REG_DISTANCE] = { + .msb = ISL29501_DISTANCE_MSB_DATA, + .lsb = ISL29501_DISTANCE_LSB_DATA, + }, + [REG_PHASE] = { + .msb = ISL29501_PHASE_MSB, + .lsb = ISL29501_PHASE_LSB, + }, + [REG_TEMPERATURE] = { + .lsb = ISL29501_DIE_TEMPERATURE, + }, + [REG_AMBIENT_LIGHT] = { + .lsb = ISL29501_AMBIENT_LIGHT, + }, + [REG_GAIN] = { + .msb = ISL29501_GAIN_MSB, + .lsb = ISL29501_GAIN_LSB, + }, + [REG_GAIN_BIAS] = { + .msb = ISL29501_CROSSTALK_GAIN_MSB, + .lsb = ISL29501_CROSSTALK_GAIN_LSB, + }, + [REG_PHASE_EXP] = { + .lsb = ISL29501_PHASE_EXPONENT, + }, + [REG_CALIB_PHASE_TEMP_A] = { + .lsb = ISL29501_TEMP_COEFF_A, + }, + [REG_CALIB_PHASE_TEMP_B] = { + .lsb = ISL29501_TEMP_COEFF_B, + }, + [REG_CALIB_PHASE_LIGHT_A] = { + .lsb = ISL29501_AMBIANT_COEFF_A, + }, + [REG_CALIB_PHASE_LIGHT_B] = { + .lsb = ISL29501_AMBIANT_COEFF_B, + }, + [REG_DISTANCE_BIAS] = { + .msb = ISL29501_PHASE_OFFSET_MSB, + .lsb = ISL29501_PHASE_OFFSET_LSB, + }, + [REG_TEMPERATURE_BIAS] = { + .lsb = ISL29501_TEMP_REFERENCE, + }, + [REG_INT_TIME] = { + .lsb = ISL29501_INTEGRATION_PERIOD, + }, + [REG_SAMPLE_TIME] = { + .lsb = ISL29501_SAMPLE_PERIOD, + }, + [REG_DRIVER_RANGE] = { + .lsb = ISL29501_DRIVER_RANGE, + }, + [REG_EMITTER_DAC] = { + .lsb = ISL29501_EMITTER_DAC, + }, +}; + +static int isl29501_register_read(struct isl29501_private *isl29501, + enum isl29501_register_name name, + u32 *val) +{ + const struct isl29501_register_desc *reg = &isl29501_registers[name]; + u8 msb = 0, lsb = 0; + s32 ret; + + mutex_lock(&isl29501->lock); + if (reg->msb) { + ret = i2c_smbus_read_byte_data(isl29501->client, reg->msb); + if (ret < 0) + goto err; + msb = ret; + } + + if (reg->lsb) { + ret = i2c_smbus_read_byte_data(isl29501->client, reg->lsb); + if (ret < 0) + goto err; + lsb = ret; + } + mutex_unlock(&isl29501->lock); + + *val = (msb << 8) + lsb; + + return 0; +err: + mutex_unlock(&isl29501->lock); + + return ret; +} + +static u32 isl29501_register_write(struct isl29501_private *isl29501, + enum isl29501_register_name name, + u32 value) +{ + const struct isl29501_register_desc *reg = &isl29501_registers[name]; + u8 msb, lsb; + int ret; + + if (!reg->msb && value > U8_MAX) + return -ERANGE; + + if (value > U16_MAX) + return -ERANGE; + + if (!reg->msb) { + lsb = value & 0xFF; + } else { + msb = (value >> 8) & 0xFF; + lsb = value & 0xFF; + } + + mutex_lock(&isl29501->lock); + if (reg->msb) { + ret = i2c_smbus_write_byte_data(isl29501->client, + reg->msb, msb); + if (ret < 0) + goto err; + } + + ret = i2c_smbus_write_byte_data(isl29501->client, reg->lsb, lsb); + +err: + mutex_unlock(&isl29501->lock); + return ret; +} + +static ssize_t isl29501_read_ext(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct isl29501_private *isl29501 = iio_priv(indio_dev); + enum isl29501_register_name reg = private; + int ret; + u32 value, gain, coeff, exp; + + switch (reg) { + case REG_GAIN: + case REG_GAIN_BIAS: + ret = isl29501_register_read(isl29501, reg, &gain); + if (ret < 0) + return ret; + + value = gain; + break; + case REG_CALIB_PHASE_TEMP_A: + case REG_CALIB_PHASE_TEMP_B: + case REG_CALIB_PHASE_LIGHT_A: + case REG_CALIB_PHASE_LIGHT_B: + ret = isl29501_register_read(isl29501, REG_PHASE_EXP, &exp); + if (ret < 0) + return ret; + + ret = isl29501_register_read(isl29501, reg, &coeff); + if (ret < 0) + return ret; + + value = coeff << exp; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%u\n", value); +} + +static int isl29501_set_shadow_coeff(struct isl29501_private *isl29501, + enum isl29501_register_name reg, + unsigned int val) +{ + enum isl29501_correction_coeff coeff; + + switch (reg) { + case REG_CALIB_PHASE_TEMP_A: + coeff = COEFF_TEMP_A; + break; + case REG_CALIB_PHASE_TEMP_B: + coeff = COEFF_TEMP_B; + break; + case REG_CALIB_PHASE_LIGHT_A: + coeff = COEFF_LIGHT_A; + break; + case REG_CALIB_PHASE_LIGHT_B: + coeff = COEFF_LIGHT_B; + break; + default: + return -EINVAL; + } + isl29501->shadow_coeffs[coeff] = val; + + return 0; +} + +static int isl29501_write_coeff(struct isl29501_private *isl29501, + enum isl29501_correction_coeff coeff, + int val) +{ + enum isl29501_register_name reg; + + switch (coeff) { + case COEFF_TEMP_A: + reg = REG_CALIB_PHASE_TEMP_A; + break; + case COEFF_TEMP_B: + reg = REG_CALIB_PHASE_TEMP_B; + break; + case COEFF_LIGHT_A: + reg = REG_CALIB_PHASE_LIGHT_A; + break; + case COEFF_LIGHT_B: + reg = REG_CALIB_PHASE_LIGHT_B; + break; + default: + return -EINVAL; + } + + return isl29501_register_write(isl29501, reg, val); +} + +static unsigned int isl29501_find_corr_exp(unsigned int val, + unsigned int max_exp, + unsigned int max_mantissa) +{ + unsigned int exp = 1; + + /* + * Correction coefficients are represented under + * mantissa * 2^exponent form, where mantissa and exponent + * are stored in two separate registers of the sensor. + * + * Compute and return the lowest exponent such as: + * mantissa = value / 2^exponent + * + * where mantissa < max_mantissa. + */ + if (val <= max_mantissa) + return 0; + + while ((val >> exp) > max_mantissa) { + exp++; + + if (exp > max_exp) + return max_exp; + } + + return exp; +} + +static ssize_t isl29501_write_ext(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct isl29501_private *isl29501 = iio_priv(indio_dev); + enum isl29501_register_name reg = private; + unsigned int val; + int max_exp = 0; + int ret; + int i; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + switch (reg) { + case REG_GAIN_BIAS: + if (val > U16_MAX) + return -ERANGE; + + ret = isl29501_register_write(isl29501, reg, val); + if (ret < 0) + return ret; + + break; + case REG_CALIB_PHASE_TEMP_A: + case REG_CALIB_PHASE_TEMP_B: + case REG_CALIB_PHASE_LIGHT_A: + case REG_CALIB_PHASE_LIGHT_B: + + if (val > (U8_MAX << ISL29501_MAX_EXP_VAL)) + return -ERANGE; + + /* Store the correction coefficient under its exact form. */ + ret = isl29501_set_shadow_coeff(isl29501, reg, val); + if (ret < 0) + return ret; + + /* + * Find the highest exponent needed to represent + * correction coefficients. + */ + for (i = 0; i < COEFF_MAX; i++) { + int corr; + int corr_exp; + + corr = isl29501->shadow_coeffs[i]; + corr_exp = isl29501_find_corr_exp(corr, + ISL29501_MAX_EXP_VAL, + U8_MAX / 2); + dev_dbg(&isl29501->client->dev, + "found exp of corr(%d) = %d\n", corr, corr_exp); + + max_exp = max(max_exp, corr_exp); + } + + /* + * Represent every correction coefficient under + * mantissa * 2^max_exponent form and force the + * writing of those coefficients on the sensor. + */ + for (i = 0; i < COEFF_MAX; i++) { + int corr; + int mantissa; + + corr = isl29501->shadow_coeffs[i]; + if (!corr) + continue; + + mantissa = corr >> max_exp; + + ret = isl29501_write_coeff(isl29501, i, mantissa); + if (ret < 0) + return ret; + } + + ret = isl29501_register_write(isl29501, REG_PHASE_EXP, max_exp); + if (ret < 0) + return ret; + + break; + default: + return -EINVAL; + } + + return len; +} + +#define _ISL29501_EXT_INFO(_name, _ident) { \ + .name = _name, \ + .read = isl29501_read_ext, \ + .write = isl29501_write_ext, \ + .private = _ident, \ + .shared = IIO_SEPARATE, \ +} + +static const struct iio_chan_spec_ext_info isl29501_ext_info[] = { + _ISL29501_EXT_INFO("agc_gain", REG_GAIN), + _ISL29501_EXT_INFO("agc_gain_bias", REG_GAIN_BIAS), + _ISL29501_EXT_INFO("calib_phase_temp_a", REG_CALIB_PHASE_TEMP_A), + _ISL29501_EXT_INFO("calib_phase_temp_b", REG_CALIB_PHASE_TEMP_B), + _ISL29501_EXT_INFO("calib_phase_light_a", REG_CALIB_PHASE_LIGHT_A), + _ISL29501_EXT_INFO("calib_phase_light_b", REG_CALIB_PHASE_LIGHT_B), + { }, +}; + +#define ISL29501_DISTANCE_SCAN_INDEX 0 +#define ISL29501_TIMESTAMP_SCAN_INDEX 1 + +static const struct iio_chan_spec isl29501_channels[] = { + { + .type = IIO_PROXIMITY, + .scan_index = ISL29501_DISTANCE_SCAN_INDEX, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = isl29501_ext_info, + }, + { + .type = IIO_PHASE, + .scan_index = -1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_CURRENT, + .scan_index = -1, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .scan_index = -1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + }, + { + .type = IIO_INTENSITY, + .scan_index = -1, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_CLEAR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + IIO_CHAN_SOFT_TIMESTAMP(ISL29501_TIMESTAMP_SCAN_INDEX), +}; + +static int isl29501_reset_registers(struct isl29501_private *isl29501) +{ + int ret; + + ret = i2c_smbus_write_byte_data(isl29501->client, + ISL29501_COMMAND_REGISTER, + ISL29501_RESET_ALL_REGISTERS); + if (ret < 0) { + dev_err(&isl29501->client->dev, + "cannot reset registers %d\n", ret); + return ret; + } + + ret = i2c_smbus_write_byte_data(isl29501->client, + ISL29501_COMMAND_REGISTER, + ISL29501_RESET_INT_SM); + if (ret < 0) + dev_err(&isl29501->client->dev, + "cannot reset state machine %d\n", ret); + + return ret; +} + +static int isl29501_begin_acquisition(struct isl29501_private *isl29501) +{ + int ret; + + ret = i2c_smbus_write_byte_data(isl29501->client, + ISL29501_COMMAND_REGISTER, + ISL29501_EMUL_SAMPLE_START_PIN); + if (ret < 0) + dev_err(&isl29501->client->dev, + "cannot begin acquisition %d\n", ret); + + return ret; +} + +static IIO_CONST_ATTR_INT_TIME_AVAIL(ISL29501_INT_TIME_AVAILABLE); +static IIO_CONST_ATTR(out_current_scale_available, + ISL29501_CURRENT_SCALE_AVAILABLE); + +static struct attribute *isl29501_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_out_current_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group isl29501_attribute_group = { + .attrs = isl29501_attributes, +}; + +static const int isl29501_current_scale_table[][2] = { + {0, 3900}, {0, 7800}, {0, 11800}, {0, 15700}, + {0, 19600}, {0, 23500}, {0, 27500}, {0, 31400}, + {0, 35200}, {0, 39200}, {0, 43100}, {0, 47100}, + {0, 51000}, {0, 54900}, {0, 58800}, +}; + +static const int isl29501_int_time[][2] = { + {0, 70}, /* 0.07 ms */ + {0, 140}, /* 0.14 ms */ + {0, 280}, /* 0.28 ms */ + {0, 570}, /* 0.57 ms */ + {0, 1140}, /* 1.14 ms */ + {0, 2280}, /* 2.28 ms */ + {0, 4550}, /* 4.55 ms */ + {0, 9100}, /* 9.11 ms */ + {0, 18200}, /* 18.2 ms */ + {0, 36400}, /* 36.4 ms */ + {0, 72810}, /* 72.81 ms */ + {0, 145610} /* 145.28 ms */ +}; + +static int isl29501_get_raw(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int *raw) +{ + int ret; + + switch (chan->type) { + case IIO_PROXIMITY: + ret = isl29501_register_read(isl29501, REG_DISTANCE, raw); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_INTENSITY: + ret = isl29501_register_read(isl29501, + REG_AMBIENT_LIGHT, + raw); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_PHASE: + ret = isl29501_register_read(isl29501, REG_PHASE, raw); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_CURRENT: + ret = isl29501_register_read(isl29501, REG_EMITTER_DAC, raw); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_TEMP: + ret = isl29501_register_read(isl29501, REG_TEMPERATURE, raw); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int isl29501_get_scale(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int *val, int *val2) +{ + int ret; + u32 current_scale; + + switch (chan->type) { + case IIO_PROXIMITY: + /* distance = raw_distance * 33.31 / 65536 (m) */ + *val = 3331; + *val2 = 6553600; + + return IIO_VAL_FRACTIONAL; + case IIO_PHASE: + /* phase = raw_phase * 2pi / 65536 (rad) */ + *val = 0; + *val2 = 95874; + + return IIO_VAL_INT_PLUS_NANO; + case IIO_INTENSITY: + /* light = raw_light * 35 / 10000 (mA) */ + *val = 35; + *val2 = 10000; + + return IIO_VAL_FRACTIONAL; + case IIO_CURRENT: + ret = isl29501_register_read(isl29501, + REG_DRIVER_RANGE, + ¤t_scale); + if (ret < 0) + return ret; + + if (current_scale > ARRAY_SIZE(isl29501_current_scale_table)) + return -EINVAL; + + if (!current_scale) { + *val = 0; + *val2 = 0; + return IIO_VAL_INT; + } + + *val = isl29501_current_scale_table[current_scale - 1][0]; + *val2 = isl29501_current_scale_table[current_scale - 1][1]; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + /* temperature = raw_temperature * 125 / 100000 (milli °C) */ + *val = 125; + *val2 = 100000; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int isl29501_get_calibbias(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int *bias) +{ + switch (chan->type) { + case IIO_PROXIMITY: + return isl29501_register_read(isl29501, + REG_DISTANCE_BIAS, + bias); + case IIO_TEMP: + return isl29501_register_read(isl29501, + REG_TEMPERATURE_BIAS, + bias); + default: + return -EINVAL; + } +} + +static int isl29501_get_inttime(struct isl29501_private *isl29501, + int *val, int *val2) +{ + int ret; + u32 inttime; + + ret = isl29501_register_read(isl29501, REG_INT_TIME, &inttime); + if (ret < 0) + return ret; + + if (inttime >= ARRAY_SIZE(isl29501_int_time)) + return -EINVAL; + + *val = isl29501_int_time[inttime][0]; + *val2 = isl29501_int_time[inttime][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int isl29501_get_freq(struct isl29501_private *isl29501, + int *val, int *val2) +{ + int ret; + int sample_time; + unsigned long long freq; + u32 temp; + + ret = isl29501_register_read(isl29501, REG_SAMPLE_TIME, &sample_time); + if (ret < 0) + return ret; + + /* freq = 1 / (0.000450 * (sample_time + 1) * 10^-6) */ + freq = 1000000ULL * 1000000ULL; + + do_div(freq, 450 * (sample_time + 1)); + + temp = do_div(freq, 1000000); + *val = freq; + *val2 = temp; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int isl29501_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct isl29501_private *isl29501 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return isl29501_get_raw(isl29501, chan, val); + case IIO_CHAN_INFO_SCALE: + return isl29501_get_scale(isl29501, chan, val, val2); + case IIO_CHAN_INFO_INT_TIME: + return isl29501_get_inttime(isl29501, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return isl29501_get_freq(isl29501, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return isl29501_get_calibbias(isl29501, chan, val); + default: + return -EINVAL; + } +} + +static int isl29501_set_raw(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int raw) +{ + switch (chan->type) { + case IIO_CURRENT: + return isl29501_register_write(isl29501, REG_EMITTER_DAC, raw); + default: + return -EINVAL; + } +} + +static int isl29501_set_inttime(struct isl29501_private *isl29501, + int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(isl29501_int_time); i++) { + if (isl29501_int_time[i][0] == val && + isl29501_int_time[i][1] == val2) { + return isl29501_register_write(isl29501, + REG_INT_TIME, + i); + } + } + + return -EINVAL; +} + +static int isl29501_set_scale(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int val, int val2) +{ + int i; + + if (chan->type != IIO_CURRENT) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(isl29501_current_scale_table); i++) { + if (isl29501_current_scale_table[i][0] == val && + isl29501_current_scale_table[i][1] == val2) { + return isl29501_register_write(isl29501, + REG_DRIVER_RANGE, + i + 1); + } + } + + return -EINVAL; +} + +static int isl29501_set_calibbias(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int bias) +{ + switch (chan->type) { + case IIO_PROXIMITY: + return isl29501_register_write(isl29501, + REG_DISTANCE_BIAS, + bias); + case IIO_TEMP: + return isl29501_register_write(isl29501, + REG_TEMPERATURE_BIAS, + bias); + default: + return -EINVAL; + } +} + +static int isl29501_set_freq(struct isl29501_private *isl29501, + int val, int val2) +{ + int freq; + unsigned long long sample_time; + + /* sample_freq = 1 / (0.000450 * (sample_time + 1) * 10^-6) */ + freq = val * 1000000 + val2 % 1000000; + sample_time = 2222ULL * 1000000ULL; + do_div(sample_time, freq); + + sample_time -= 1; + + if (sample_time > 255) + return -ERANGE; + + return isl29501_register_write(isl29501, REG_SAMPLE_TIME, sample_time); +} + +static int isl29501_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct isl29501_private *isl29501 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return isl29501_set_raw(isl29501, chan, val); + case IIO_CHAN_INFO_INT_TIME: + return isl29501_set_inttime(isl29501, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return isl29501_set_freq(isl29501, val, val2); + case IIO_CHAN_INFO_SCALE: + return isl29501_set_scale(isl29501, chan, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return isl29501_set_calibbias(isl29501, chan, val); + default: + return -EINVAL; + } +} + +static const struct iio_info isl29501_info = { + .read_raw = &isl29501_read_raw, + .write_raw = &isl29501_write_raw, + .attrs = &isl29501_attribute_group, +}; + +static int isl29501_init_chip(struct isl29501_private *isl29501) +{ + int ret; + + ret = i2c_smbus_read_byte_data(isl29501->client, ISL29501_DEVICE_ID); + if (ret < 0) { + dev_err(&isl29501->client->dev, "Error reading device id\n"); + return ret; + } + + if (ret != ISL29501_ID) { + dev_err(&isl29501->client->dev, + "Wrong chip id, got %x expected %x\n", + ret, ISL29501_DEVICE_ID); + return -ENODEV; + } + + ret = isl29501_reset_registers(isl29501); + if (ret < 0) + return ret; + + return isl29501_begin_acquisition(isl29501); +} + +static irqreturn_t isl29501_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct isl29501_private *isl29501 = iio_priv(indio_dev); + const unsigned long *active_mask = indio_dev->active_scan_mask; + u32 buffer[4] = {}; /* 1x16-bit + ts */ + + if (test_bit(ISL29501_DISTANCE_SCAN_INDEX, active_mask)) + isl29501_register_read(isl29501, REG_DISTANCE, buffer); + + iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int isl29501_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct isl29501_private *isl29501; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*isl29501)); + if (!indio_dev) + return -ENOMEM; + + isl29501 = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + isl29501->client = client; + + mutex_init(&isl29501->lock); + + ret = isl29501_init_chip(isl29501); + if (ret < 0) + return ret; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &client->dev; + indio_dev->channels = isl29501_channels; + indio_dev->num_channels = ARRAY_SIZE(isl29501_channels); + indio_dev->name = client->name; + indio_dev->info = &isl29501_info; + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, + iio_pollfunc_store_time, + isl29501_trigger_handler, + NULL); + if (ret < 0) { + dev_err(&client->dev, "unable to setup iio triggered buffer\n"); + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id isl29501_id[] = { + {"isl29501", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, isl29501_id); + +#if defined(CONFIG_OF) +static const struct of_device_id isl29501_i2c_matches[] = { + { .compatible = "renesas,isl29501" }, + { } +}; +MODULE_DEVICE_TABLE(of, isl29501_i2c_matches); +#endif + +static struct i2c_driver isl29501_driver = { + .driver = { + .name = "isl29501", + }, + .id_table = isl29501_id, + .probe = isl29501_probe, +}; +module_i2c_driver(isl29501_driver); + +MODULE_AUTHOR("Mathieu Othacehe "); +MODULE_DESCRIPTION("ISL29501 Time of Flight sensor driver"); +MODULE_LICENSE("GPL v2"); From c0e4e0fd952b73bf6aae67e92b9a496a52837eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Roussin-B=C3=A9langer?= Date: Thu, 19 Jul 2018 16:26:24 -0400 Subject: [PATCH 28/39] iio: Add modifier for DUV light MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime Roussin-Bélanger Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 7 +++++-- drivers/iio/industrialio-core.c | 1 + include/uapi/linux/iio/types.h | 1 + tools/iio/iio_event_monitor.c | 2 ++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index c9cfa833cf47..a5b4f223641d 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -1307,13 +1307,16 @@ What: /sys/.../iio:deviceX/in_intensityY_raw What: /sys/.../iio:deviceX/in_intensityY_ir_raw What: /sys/.../iio:deviceX/in_intensityY_both_raw What: /sys/.../iio:deviceX/in_intensityY_uv_raw +What: /sys/.../iio:deviceX/in_intensityY_duv_raw KernelVersion: 3.4 Contact: linux-iio@vger.kernel.org Description: Unit-less light intensity. Modifiers both and ir indicate that measurements contain visible and infrared light - components or just infrared light, respectively. Modifier uv indicates - that measurements contain ultraviolet light components. + components or just infrared light, respectively. Modifier + uv indicates that measurements contain ultraviolet light + components. Modifier duv indicates that measurements + contain deep ultraviolet light components. What: /sys/.../iio:deviceX/in_uvindex_input KernelVersion: 4.6 diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index a16ad5a4ab0c..a062cfddc5af 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -110,6 +110,7 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_LIGHT_GREEN] = "green", [IIO_MOD_LIGHT_BLUE] = "blue", [IIO_MOD_LIGHT_UV] = "uv", + [IIO_MOD_LIGHT_DUV] = "duv", [IIO_MOD_QUATERNION] = "quaternion", [IIO_MOD_TEMP_AMBIENT] = "ambient", [IIO_MOD_TEMP_OBJECT] = "object", diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index e4df3cc268db..92baabc103ac 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -86,6 +86,7 @@ enum iio_modifier { IIO_MOD_CO2, IIO_MOD_VOC, IIO_MOD_LIGHT_UV, + IIO_MOD_LIGHT_DUV, }; enum iio_event_type { diff --git a/tools/iio/iio_event_monitor.c b/tools/iio/iio_event_monitor.c index f478f5558720..ac2de6b7e89f 100644 --- a/tools/iio/iio_event_monitor.c +++ b/tools/iio/iio_event_monitor.c @@ -98,6 +98,7 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_LIGHT_GREEN] = "green", [IIO_MOD_LIGHT_BLUE] = "blue", [IIO_MOD_LIGHT_UV] = "uv", + [IIO_MOD_LIGHT_DUV] = "duv", [IIO_MOD_QUATERNION] = "quaternion", [IIO_MOD_TEMP_AMBIENT] = "ambient", [IIO_MOD_TEMP_OBJECT] = "object", @@ -182,6 +183,7 @@ static bool event_is_known(struct iio_event_data *event) case IIO_MOD_LIGHT_GREEN: case IIO_MOD_LIGHT_BLUE: case IIO_MOD_LIGHT_UV: + case IIO_MOD_LIGHT_DUV: case IIO_MOD_QUATERNION: case IIO_MOD_TEMP_AMBIENT: case IIO_MOD_TEMP_OBJECT: From e01e7eaf37d865c72e2501a7b09e7a61317ce2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Roussin-B=C3=A9langer?= Date: Thu, 19 Jul 2018 16:26:25 -0400 Subject: [PATCH 29/39] iio: light: introduce si1133 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit e-mail received from Silicon Lab to confirm that the licensing isn't a problem. " Dear Maxime Roussin-Belanger, The LUX calculation code only works with Si1133. As long as the software is used with Silicon Lab's sensor product, I don't see any problem. Regards, Tony " Signed-off-by: Maxime Roussin-Bélanger Reviewed-by: Jean-Francois Dagenais Signed-off-by: Jonathan Cameron --- .../ABI/testing/sysfs-bus-iio-light-si1133 | 22 + drivers/iio/light/Kconfig | 12 + drivers/iio/light/Makefile | 1 + drivers/iio/light/si1133.c | 1068 +++++++++++++++++ 4 files changed, 1103 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-si1133 create mode 100644 drivers/iio/light/si1133.c diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-si1133 b/Documentation/ABI/testing/sysfs-bus-iio-light-si1133 new file mode 100644 index 000000000000..6f130cdb26a6 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-si1133 @@ -0,0 +1,22 @@ +What: /sys/bus/iio/devices/iio:deviceX/in_intensity_ir_small_raw +KernelVersion: 4.18 +Contact: linux-iio@vger.kernel.org +Description: + Unit-less infrared intensity. The intensity is measured from 1 + dark photodiode. "small" indicate the surface area capturing + infrared. + +What: /sys/bus/iio/devices/iio:deviceX/in_intensity_ir_large_raw +KernelVersion: 4.18 +Contact: linux-iio@vger.kernel.org +Description: + Unit-less infrared intensity. The intensity is measured from 4 + dark photodiodes. "large" indicate the surface area capturing + infrared. + +What: /sys/bus/iio/devices/iio:deviceX/in_intensity_large_raw +KernelVersion: 4.18 +Contact: linux-iio@vger.kernel.org +Description: + Unit-less light intensity with more diodes. + diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index c7ef8d1862d6..f17f701a9b61 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -1,3 +1,4 @@ + # # Light sensors # @@ -319,6 +320,17 @@ config PA12203001 This driver can also be built as a module. If so, the module will be called pa12203001. +config SI1133 + tristate "SI1133 UV Index Sensor and Ambient Light Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build a driver for the Silicon Labs SI1133 + UV Index Sensor and Ambient Light Sensor chip. + + To compile this driver as a module, choose M here: the module will be + called si1133. + config SI1145 tristate "SI1132 and SI1141/2/3/5/6/7 combined ALS, UV index and proximity sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 80943af5d627..86337b114bc4 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_OPT3001) += opt3001.o obj-$(CONFIG_PA12203001) += pa12203001.o obj-$(CONFIG_RPR0521) += rpr0521.o obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o +obj-$(CONFIG_SI1133) += si1133.o obj-$(CONFIG_SI1145) += si1145.o obj-$(CONFIG_STK3310) += stk3310.o obj-$(CONFIG_ST_UVIS25) += st_uvis25_core.o diff --git a/drivers/iio/light/si1133.c b/drivers/iio/light/si1133.c new file mode 100644 index 000000000000..d3fbeb3bc463 --- /dev/null +++ b/drivers/iio/light/si1133.c @@ -0,0 +1,1068 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * si1133.c - Support for Silabs SI1133 combined ambient + * light and UV index sensors + * + * Copyright 2018 Maxime Roussin-Belanger + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define SI1133_REG_PART_ID 0x00 +#define SI1133_REG_REV_ID 0x01 +#define SI1133_REG_MFR_ID 0x02 +#define SI1133_REG_INFO0 0x03 +#define SI1133_REG_INFO1 0x04 + +#define SI1133_PART_ID 0x33 + +#define SI1133_REG_HOSTIN0 0x0A +#define SI1133_REG_COMMAND 0x0B +#define SI1133_REG_IRQ_ENABLE 0x0F +#define SI1133_REG_RESPONSE1 0x10 +#define SI1133_REG_RESPONSE0 0x11 +#define SI1133_REG_IRQ_STATUS 0x12 +#define SI1133_REG_MEAS_RATE 0x1A + +#define SI1133_IRQ_CHANNEL_ENABLE 0xF + +#define SI1133_CMD_RESET_CTR 0x00 +#define SI1133_CMD_RESET_SW 0x01 +#define SI1133_CMD_FORCE 0x11 +#define SI1133_CMD_START_AUTONOMOUS 0x13 +#define SI1133_CMD_PARAM_SET 0x80 +#define SI1133_CMD_PARAM_QUERY 0x40 +#define SI1133_CMD_PARAM_MASK 0x3F + +#define SI1133_CMD_ERR_MASK BIT(4) +#define SI1133_CMD_SEQ_MASK 0xF +#define SI1133_MAX_CMD_CTR 0xF + +#define SI1133_PARAM_REG_CHAN_LIST 0x01 +#define SI1133_PARAM_REG_ADCCONFIG(x) ((x) * 4) + 2 +#define SI1133_PARAM_REG_ADCSENS(x) ((x) * 4) + 3 +#define SI1133_PARAM_REG_ADCPOST(x) ((x) * 4) + 4 + +#define SI1133_ADCMUX_MASK 0x1F + +#define SI1133_ADCCONFIG_DECIM_RATE(x) (x) << 5 + +#define SI1133_ADCSENS_SCALE_MASK 0x70 +#define SI1133_ADCSENS_SCALE_SHIFT 4 +#define SI1133_ADCSENS_HSIG_MASK BIT(7) +#define SI1133_ADCSENS_HSIG_SHIFT 7 +#define SI1133_ADCSENS_HW_GAIN_MASK 0xF +#define SI1133_ADCSENS_NB_MEAS(x) fls(x) << SI1133_ADCSENS_SCALE_SHIFT + +#define SI1133_ADCPOST_24BIT_EN BIT(6) +#define SI1133_ADCPOST_POSTSHIFT_BITQTY(x) (x & GENMASK(2, 0)) << 3 + +#define SI1133_PARAM_ADCMUX_SMALL_IR 0x0 +#define SI1133_PARAM_ADCMUX_MED_IR 0x1 +#define SI1133_PARAM_ADCMUX_LARGE_IR 0x2 +#define SI1133_PARAM_ADCMUX_WHITE 0xB +#define SI1133_PARAM_ADCMUX_LARGE_WHITE 0xD +#define SI1133_PARAM_ADCMUX_UV 0x18 +#define SI1133_PARAM_ADCMUX_UV_DEEP 0x19 + +#define SI1133_ERR_INVALID_CMD 0x0 +#define SI1133_ERR_INVALID_LOCATION_CMD 0x1 +#define SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION 0x2 +#define SI1133_ERR_OUTPUT_BUFFER_OVERFLOW 0x3 + +#define SI1133_COMPLETION_TIMEOUT_MS 500 + +#define SI1133_CMD_MINSLEEP_US_LOW 5000 +#define SI1133_CMD_MINSLEEP_US_HIGH 7500 +#define SI1133_CMD_TIMEOUT_MS 25 +#define SI1133_CMD_LUX_TIMEOUT_MS 5000 +#define SI1133_CMD_TIMEOUT_US SI1133_CMD_TIMEOUT_MS * 1000 + +#define SI1133_REG_HOSTOUT(x) (x) + 0x13 + +#define SI1133_MEASUREMENT_FREQUENCY 1250 + +#define SI1133_X_ORDER_MASK 0x0070 +#define SI1133_Y_ORDER_MASK 0x0007 +#define si1133_get_x_order(m) ((m) & SI1133_X_ORDER_MASK) >> 4 +#define si1133_get_y_order(m) ((m) & SI1133_Y_ORDER_MASK) + +#define SI1133_LUX_ADC_MASK 0xE +#define SI1133_ADC_THRESHOLD 16000 +#define SI1133_INPUT_FRACTION_HIGH 7 +#define SI1133_INPUT_FRACTION_LOW 15 +#define SI1133_LUX_OUTPUT_FRACTION 12 +#define SI1133_LUX_BUFFER_SIZE 9 + +static const int si1133_scale_available[] = { + 1, 2, 4, 8, 16, 32, 64, 128}; + +static IIO_CONST_ATTR(scale_available, "1 2 4 8 16 32 64 128"); + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.0244 0.0488 0.0975 0.195 0.390 0.780 " + "1.560 3.120 6.24 12.48 25.0 50.0"); + +/* A.K.A. HW_GAIN in datasheet */ +enum si1133_int_time { + _24_4_us = 0, + _48_8_us = 1, + _97_5_us = 2, + _195_0_us = 3, + _390_0_us = 4, + _780_0_us = 5, + _1_560_0_us = 6, + _3_120_0_us = 7, + _6_240_0_us = 8, + _12_480_0_us = 9, + _25_ms = 10, + _50_ms = 11, +}; + +/* Integration time in milliseconds, nanoseconds */ +static const int si1133_int_time_table[][2] = { + [_24_4_us] = {0, 24400}, + [_48_8_us] = {0, 48800}, + [_97_5_us] = {0, 97500}, + [_195_0_us] = {0, 195000}, + [_390_0_us] = {0, 390000}, + [_780_0_us] = {0, 780000}, + [_1_560_0_us] = {1, 560000}, + [_3_120_0_us] = {3, 120000}, + [_6_240_0_us] = {6, 240000}, + [_12_480_0_us] = {12, 480000}, + [_25_ms] = {25, 000000}, + [_50_ms] = {50, 000000}, +}; + +static const struct regmap_range si1133_reg_ranges[] = { + regmap_reg_range(0x00, 0x02), + regmap_reg_range(0x0A, 0x0B), + regmap_reg_range(0x0F, 0x0F), + regmap_reg_range(0x10, 0x12), + regmap_reg_range(0x13, 0x2C), +}; + +static const struct regmap_range si1133_reg_ro_ranges[] = { + regmap_reg_range(0x00, 0x02), + regmap_reg_range(0x10, 0x2C), +}; + +static const struct regmap_range si1133_precious_ranges[] = { + regmap_reg_range(0x12, 0x12), +}; + +static const struct regmap_access_table si1133_write_ranges_table = { + .yes_ranges = si1133_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si1133_reg_ranges), + .no_ranges = si1133_reg_ro_ranges, + .n_no_ranges = ARRAY_SIZE(si1133_reg_ro_ranges), +}; + +static const struct regmap_access_table si1133_read_ranges_table = { + .yes_ranges = si1133_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si1133_reg_ranges), +}; + +static const struct regmap_access_table si1133_precious_table = { + .yes_ranges = si1133_precious_ranges, + .n_yes_ranges = ARRAY_SIZE(si1133_precious_ranges), +}; + +static const struct regmap_config si1133_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0x2C, + + .wr_table = &si1133_write_ranges_table, + .rd_table = &si1133_read_ranges_table, + + .precious_table = &si1133_precious_table, +}; + +struct si1133_data { + struct regmap *regmap; + struct i2c_client *client; + + /* Lock protecting one command at a time can be processed */ + struct mutex mutex; + + int rsp_seq; + u8 scan_mask; + u8 adc_sens[6]; + u8 adc_config[6]; + + struct completion completion; +}; + +struct si1133_coeff { + s16 info; + u16 mag; +}; + +struct si1133_lux_coeff { + struct si1133_coeff coeff_high[4]; + struct si1133_coeff coeff_low[9]; +}; + +static const struct si1133_lux_coeff lux_coeff = { + { + { 0, 209}, + { 1665, 93}, + { 2064, 65}, + {-2671, 234} + }, + { + { 0, 0}, + { 1921, 29053}, + {-1022, 36363}, + { 2320, 20789}, + { -367, 57909}, + {-1774, 38240}, + { -608, 46775}, + {-1503, 51831}, + {-1886, 58928} + } +}; + +static int si1133_calculate_polynomial_inner(u32 input, u8 fraction, u16 mag, + s8 shift) +{ + return ((input << fraction) / mag) << shift; +} + +static int si1133_calculate_output(u32 x, u32 y, u8 x_order, u8 y_order, + u8 input_fraction, s8 sign, + const struct si1133_coeff *coeffs) +{ + s8 shift; + int x1 = 1; + int x2 = 1; + int y1 = 1; + int y2 = 1; + + shift = ((u16)coeffs->info & 0xFF00) >> 8; + shift ^= 0xFF; + shift += 1; + shift = -shift; + + if (x_order > 0) { + x1 = si1133_calculate_polynomial_inner(x, input_fraction, + coeffs->mag, shift); + if (x_order > 1) + x2 = x1; + } + + if (y_order > 0) { + y1 = si1133_calculate_polynomial_inner(y, input_fraction, + coeffs->mag, shift); + if (y_order > 1) + y2 = y1; + } + + return sign * x1 * x2 * y1 * y2; +} + +/* + * The algorithm is from: + * https://siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00716 + */ +static int si1133_calc_polynomial(u32 x, u32 y, u8 input_fraction, u8 num_coeff, + const struct si1133_coeff *coeffs) +{ + u8 x_order, y_order; + u8 counter; + s8 sign; + int output = 0; + + for (counter = 0; counter < num_coeff; counter++) { + if (coeffs->info < 0) + sign = -1; + else + sign = 1; + + x_order = si1133_get_x_order(coeffs->info); + y_order = si1133_get_y_order(coeffs->info); + + if ((x_order == 0) && (y_order == 0)) + output += + sign * coeffs->mag << SI1133_LUX_OUTPUT_FRACTION; + else + output += si1133_calculate_output(x, y, x_order, + y_order, + input_fraction, sign, + coeffs); + coeffs++; + } + + return abs(output); +} + +static int si1133_cmd_reset_sw(struct si1133_data *data) +{ + struct device *dev = &data->client->dev; + unsigned int resp; + unsigned long timeout; + int err; + + err = regmap_write(data->regmap, SI1133_REG_COMMAND, + SI1133_CMD_RESET_SW); + if (err) + return err; + + timeout = jiffies + msecs_to_jiffies(SI1133_CMD_TIMEOUT_MS); + while (true) { + err = regmap_read(data->regmap, SI1133_REG_RESPONSE0, &resp); + if (err == -ENXIO) { + usleep_range(SI1133_CMD_MINSLEEP_US_LOW, + SI1133_CMD_MINSLEEP_US_HIGH); + continue; + } + + if ((resp & SI1133_MAX_CMD_CTR) == SI1133_MAX_CMD_CTR) + break; + + if (time_after(jiffies, timeout)) { + dev_warn(dev, "Timeout on reset ctr resp: %d\n", resp); + return -ETIMEDOUT; + } + } + + if (!err) + data->rsp_seq = SI1133_MAX_CMD_CTR; + + return err; +} + +static int si1133_parse_response_err(struct device *dev, u32 resp, u8 cmd) +{ + resp &= 0xF; + + switch (resp) { + case SI1133_ERR_OUTPUT_BUFFER_OVERFLOW: + dev_warn(dev, "Output buffer overflow: %#02hhx\n", cmd); + return -EOVERFLOW; + case SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION: + dev_warn(dev, "Saturation of the ADC or overflow of accumulation: %#02hhx\n", + cmd); + return -EOVERFLOW; + case SI1133_ERR_INVALID_LOCATION_CMD: + dev_warn(dev, + "Parameter access to an invalid location: %#02hhx\n", + cmd); + return -EINVAL; + case SI1133_ERR_INVALID_CMD: + dev_warn(dev, "Invalid command %#02hhx\n", cmd); + return -EINVAL; + default: + dev_warn(dev, "Unknown error %#02hhx\n", cmd); + return -EINVAL; + } +} + +static int si1133_cmd_reset_counter(struct si1133_data *data) +{ + int err = regmap_write(data->regmap, SI1133_REG_COMMAND, + SI1133_CMD_RESET_CTR); + if (err) + return err; + + data->rsp_seq = 0; + + return 0; +} + +static int si1133_command(struct si1133_data *data, u8 cmd) +{ + struct device *dev = &data->client->dev; + u32 resp; + int err; + int expected_seq; + + mutex_lock(&data->mutex); + + expected_seq = (data->rsp_seq + 1) & SI1133_MAX_CMD_CTR; + + if (cmd == SI1133_CMD_FORCE) + reinit_completion(&data->completion); + + err = regmap_write(data->regmap, SI1133_REG_COMMAND, cmd); + if (err) { + dev_warn(dev, "Failed to write command %#02hhx, ret=%d\n", cmd, + err); + goto out; + } + + if (cmd == SI1133_CMD_FORCE) { + /* wait for irq */ + if (!wait_for_completion_timeout(&data->completion, + msecs_to_jiffies(SI1133_COMPLETION_TIMEOUT_MS))) { + err = -ETIMEDOUT; + goto out; + } + } else { + err = regmap_read_poll_timeout(data->regmap, + SI1133_REG_RESPONSE0, resp, + (resp & SI1133_CMD_SEQ_MASK) == + expected_seq || + (resp & SI1133_CMD_ERR_MASK), + SI1133_CMD_MINSLEEP_US_LOW, + SI1133_CMD_TIMEOUT_MS * 1000); + if (err) { + dev_warn(dev, + "Failed to read command %#02hhx, ret=%d\n", + cmd, err); + goto out; + } + } + + if (resp & SI1133_CMD_ERR_MASK) { + err = si1133_parse_response_err(dev, resp, cmd); + si1133_cmd_reset_counter(data); + } else { + data->rsp_seq = expected_seq; + } + +out: + mutex_unlock(&data->mutex); + + return err; +} + +static int si1133_param_set(struct si1133_data *data, u8 param, u32 value) +{ + int err = regmap_write(data->regmap, SI1133_REG_HOSTIN0, value); + + if (err) + return err; + + return si1133_command(data, SI1133_CMD_PARAM_SET | + (param & SI1133_CMD_PARAM_MASK)); +} + +static int si1133_param_query(struct si1133_data *data, u8 param, u32 *result) +{ + int err = si1133_command(data, SI1133_CMD_PARAM_QUERY | + (param & SI1133_CMD_PARAM_MASK)); + if (err) + return err; + + return regmap_read(data->regmap, SI1133_REG_RESPONSE1, result); +} + +#define SI1133_CHANNEL(_ch, _type) \ + .type = _type, \ + .channel = _ch, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + +static const struct iio_chan_spec si1133_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .channel = 0, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_WHITE, IIO_INTENSITY) + .channel2 = IIO_MOD_LIGHT_BOTH, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_LARGE_WHITE, IIO_INTENSITY) + .channel2 = IIO_MOD_LIGHT_BOTH, + .extend_name = "large", + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_SMALL_IR, IIO_INTENSITY) + .extend_name = "small", + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_MED_IR, IIO_INTENSITY) + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_LARGE_IR, IIO_INTENSITY) + .extend_name = "large", + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_UV, IIO_UVINDEX) + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_UV_DEEP, IIO_UVINDEX) + .modified = 1, + .channel2 = IIO_MOD_LIGHT_DUV, + } +}; + +static int si1133_get_int_time_index(int milliseconds, int nanoseconds) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(si1133_int_time_table); i++) { + if (milliseconds == si1133_int_time_table[i][0] && + nanoseconds == si1133_int_time_table[i][1]) + return i; + } + return -EINVAL; +} + +static int si1133_set_integration_time(struct si1133_data *data, u8 adc, + int milliseconds, int nanoseconds) +{ + int index; + + index = si1133_get_int_time_index(milliseconds, nanoseconds); + if (index < 0) + return index; + + data->adc_sens[adc] &= 0xF0; + data->adc_sens[adc] |= index; + + return si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(0), + data->adc_sens[adc]); +} + +static int si1133_set_chlist(struct si1133_data *data, u8 scan_mask) +{ + /* channel list already set, no need to reprogram */ + if (data->scan_mask == scan_mask) + return 0; + + data->scan_mask = scan_mask; + + return si1133_param_set(data, SI1133_PARAM_REG_CHAN_LIST, scan_mask); +} + +static int si1133_chan_set_adcconfig(struct si1133_data *data, u8 adc, + u8 adc_config) +{ + int err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCCONFIG(adc), + adc_config); + if (err) + return err; + + data->adc_config[adc] = adc_config; + + return 0; +} + +static int si1133_update_adcconfig(struct si1133_data *data, uint8_t adc, + u8 mask, u8 shift, u8 value) +{ + u32 adc_config; + int err; + + err = si1133_param_query(data, SI1133_PARAM_REG_ADCCONFIG(adc), + &adc_config); + if (err) + return err; + + adc_config &= ~mask; + adc_config |= (value << shift); + + return si1133_chan_set_adcconfig(data, adc, adc_config); +} + +static int si1133_set_adcmux(struct si1133_data *data, u8 adc, u8 mux) +{ + if ((mux & data->adc_config[adc]) == mux) + return 0; /* mux already set to correct value */ + + return si1133_update_adcconfig(data, adc, SI1133_ADCMUX_MASK, 0, mux); +} + +static int si1133_force_measurement(struct si1133_data *data) +{ + return si1133_command(data, SI1133_CMD_FORCE); +} + +static int si1133_bulk_read(struct si1133_data *data, u8 start_reg, u8 length, + u8 *buffer) +{ + int err; + + err = si1133_force_measurement(data); + if (err) + return err; + + return regmap_bulk_read(data->regmap, start_reg, buffer, length); +} + +static int si1133_measure(struct si1133_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + int err; + + __be16 resp; + + err = si1133_set_adcmux(data, 0, chan->channel); + if (err) + return err; + + /* Deactivate lux measurements if they were active */ + err = si1133_set_chlist(data, BIT(0)); + if (err) + return err; + + err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0), sizeof(resp), + (u8 *)&resp); + if (err) + return err; + + *val = be16_to_cpu(resp); + + return err; +} + +static irqreturn_t si1133_threaded_irq_handler(int irq, void *private) +{ + struct iio_dev *iio_dev = private; + struct si1133_data *data = iio_priv(iio_dev); + u32 irq_status; + int err; + + err = regmap_read(data->regmap, SI1133_REG_IRQ_STATUS, &irq_status); + if (err) { + dev_err_ratelimited(&iio_dev->dev, "Error reading IRQ\n"); + goto out; + } + + if (irq_status != data->scan_mask) + return IRQ_NONE; + +out: + complete(&data->completion); + + return IRQ_HANDLED; +} + +static int si1133_scale_to_swgain(int scale_integer, int scale_fractional) +{ + scale_integer = find_closest(scale_integer, si1133_scale_available, + ARRAY_SIZE(si1133_scale_available)); + if (scale_integer < 0 || + scale_integer > ARRAY_SIZE(si1133_scale_available) || + scale_fractional != 0) + return -EINVAL; + + return scale_integer; +} + +static int si1133_chan_set_adcsens(struct si1133_data *data, u8 adc, + u8 adc_sens) +{ + int err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(adc), adc_sens); + if (err) + return err; + + data->adc_sens[adc] = adc_sens; + + return 0; +} + +static int si1133_update_adcsens(struct si1133_data *data, u8 mask, + u8 shift, u8 value) +{ + int err; + u32 adc_sens; + + err = si1133_param_query(data, SI1133_PARAM_REG_ADCSENS(0), + &adc_sens); + if (err) + return err; + + adc_sens &= ~mask; + adc_sens |= (value << shift); + + return si1133_chan_set_adcsens(data, 0, adc_sens); +} + +static int si1133_get_lux(struct si1133_data *data, int *val) +{ + int err; + int lux; + u32 high_vis; + u32 low_vis; + u32 ir; + u8 buffer[SI1133_LUX_BUFFER_SIZE]; + + /* Activate lux channels */ + err = si1133_set_chlist(data, SI1133_LUX_ADC_MASK); + if (err) + return err; + + err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0), + SI1133_LUX_BUFFER_SIZE, buffer); + if (err) + return err; + + high_vis = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2]; + low_vis = (buffer[3] << 16) | (buffer[4] << 8) | buffer[5]; + ir = (buffer[6] << 16) | (buffer[7] << 8) | buffer[8]; + + if (high_vis > SI1133_ADC_THRESHOLD || ir > SI1133_ADC_THRESHOLD) + lux = si1133_calc_polynomial(high_vis, ir, + SI1133_INPUT_FRACTION_HIGH, + ARRAY_SIZE(lux_coeff.coeff_high), + &lux_coeff.coeff_high[0]); + else + lux = si1133_calc_polynomial(low_vis, ir, + SI1133_INPUT_FRACTION_LOW, + ARRAY_SIZE(lux_coeff.coeff_low), + &lux_coeff.coeff_low[0]); + + *val = lux >> SI1133_LUX_OUTPUT_FRACTION; + + return err; +} + +static int si1133_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct si1133_data *data = iio_priv(iio_dev); + u8 adc_sens = data->adc_sens[0]; + int err; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + err = si1133_get_lux(data, val); + if (err) + return err; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + err = si1133_measure(data, chan, val); + if (err) + return err; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + adc_sens &= SI1133_ADCSENS_HW_GAIN_MASK; + + *val = si1133_int_time_table[adc_sens][0]; + *val2 = si1133_int_time_table[adc_sens][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + adc_sens &= SI1133_ADCSENS_SCALE_MASK; + adc_sens >>= SI1133_ADCSENS_SCALE_SHIFT; + + *val = BIT(adc_sens); + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + adc_sens >>= SI1133_ADCSENS_HSIG_SHIFT; + + *val = adc_sens; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int si1133_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct si1133_data *data = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + val = si1133_scale_to_swgain(val, val2); + if (val < 0) + return val; + + return si1133_update_adcsens(data, + SI1133_ADCSENS_SCALE_MASK, + SI1133_ADCSENS_SCALE_SHIFT, + val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + return si1133_set_integration_time(data, 0, val, val2); + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + if (val != 0 || val != 1) + return -EINVAL; + + return si1133_update_adcsens(data, + SI1133_ADCSENS_HSIG_MASK, + SI1133_ADCSENS_HSIG_SHIFT, + val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static struct attribute *si1133_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group si1133_attribute_group = { + .attrs = si1133_attributes, +}; + +static const struct iio_info si1133_info = { + .read_raw = si1133_read_raw, + .write_raw = si1133_write_raw, + .attrs = &si1133_attribute_group, +}; + +/* + * si1133_init_lux_channels - Configure 3 different channels(adc) (1,2 and 3) + * The channel configuration for the lux measurement was taken from : + * https://siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00578 + * + * Reserved the channel 0 for the other raw measurements + */ +static int si1133_init_lux_channels(struct si1133_data *data) +{ + int err; + + err = si1133_chan_set_adcconfig(data, 1, + SI1133_ADCCONFIG_DECIM_RATE(1) | + SI1133_PARAM_ADCMUX_LARGE_WHITE); + if (err) + return err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(1), + SI1133_ADCPOST_24BIT_EN | + SI1133_ADCPOST_POSTSHIFT_BITQTY(0)); + if (err) + return err; + err = si1133_chan_set_adcsens(data, 1, SI1133_ADCSENS_HSIG_MASK | + SI1133_ADCSENS_NB_MEAS(64) | _48_8_us); + if (err) + return err; + + err = si1133_chan_set_adcconfig(data, 2, + SI1133_ADCCONFIG_DECIM_RATE(1) | + SI1133_PARAM_ADCMUX_LARGE_WHITE); + if (err) + return err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(2), + SI1133_ADCPOST_24BIT_EN | + SI1133_ADCPOST_POSTSHIFT_BITQTY(2)); + if (err) + return err; + + err = si1133_chan_set_adcsens(data, 2, SI1133_ADCSENS_HSIG_MASK | + SI1133_ADCSENS_NB_MEAS(1) | _3_120_0_us); + if (err) + return err; + + err = si1133_chan_set_adcconfig(data, 3, + SI1133_ADCCONFIG_DECIM_RATE(1) | + SI1133_PARAM_ADCMUX_MED_IR); + if (err) + return err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(3), + SI1133_ADCPOST_24BIT_EN | + SI1133_ADCPOST_POSTSHIFT_BITQTY(2)); + if (err) + return err; + + return si1133_chan_set_adcsens(data, 3, SI1133_ADCSENS_HSIG_MASK | + SI1133_ADCSENS_NB_MEAS(64) | _48_8_us); +} + +static int si1133_initialize(struct si1133_data *data) +{ + int err; + + err = si1133_cmd_reset_sw(data); + if (err) + return err; + + /* Turn off autonomous mode */ + err = si1133_param_set(data, SI1133_REG_MEAS_RATE, 0); + if (err) + return err; + + err = si1133_init_lux_channels(data); + if (err) + return err; + + return regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE, + SI1133_IRQ_CHANNEL_ENABLE); +} + +static int si1133_validate_ids(struct iio_dev *iio_dev) +{ + struct si1133_data *data = iio_priv(iio_dev); + + unsigned int part_id, rev_id, mfr_id; + int err; + + err = regmap_read(data->regmap, SI1133_REG_PART_ID, &part_id); + if (err) + return err; + + err = regmap_read(data->regmap, SI1133_REG_REV_ID, &rev_id); + if (err) + return err; + + err = regmap_read(data->regmap, SI1133_REG_MFR_ID, &mfr_id); + if (err) + return err; + + dev_info(&iio_dev->dev, + "Device ID part %#02hhx rev %#02hhx mfr %#02hhx\n", + part_id, rev_id, mfr_id); + if (part_id != SI1133_PART_ID) { + dev_err(&iio_dev->dev, + "Part ID mismatch got %#02hhx, expected %#02x\n", + part_id, SI1133_PART_ID); + return -ENODEV; + } + + return 0; +} + +static int si1133_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct si1133_data *data; + struct iio_dev *iio_dev; + int err; + + iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!iio_dev) + return -ENOMEM; + + data = iio_priv(iio_dev); + + init_completion(&data->completion); + + data->regmap = devm_regmap_init_i2c(client, &si1133_regmap_config); + if (IS_ERR(data->regmap)) { + err = PTR_ERR(data->regmap); + dev_err(&client->dev, "Failed to initialise regmap: %d\n", err); + return err; + } + + i2c_set_clientdata(client, iio_dev); + data->client = client; + + iio_dev->dev.parent = &client->dev; + iio_dev->name = id->name; + iio_dev->channels = si1133_channels; + iio_dev->num_channels = ARRAY_SIZE(si1133_channels); + iio_dev->info = &si1133_info; + iio_dev->modes = INDIO_DIRECT_MODE; + + mutex_init(&data->mutex); + + err = si1133_validate_ids(iio_dev); + if (err) + return err; + + err = si1133_initialize(data); + if (err) { + dev_err(&client->dev, + "Error when initializing chip: %d\n", err); + return err; + } + + if (!client->irq) { + dev_err(&client->dev, + "Required interrupt not provided, cannot proceed\n"); + return -EINVAL; + } + + err = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + si1133_threaded_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + client->name, iio_dev); + if (err) { + dev_warn(&client->dev, "Request irq %d failed: %i\n", + client->irq, err); + return err; + } + + return devm_iio_device_register(&client->dev, iio_dev); +} + +static const struct i2c_device_id si1133_ids[] = { + { "si1133", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si1133_ids); + +static struct i2c_driver si1133_driver = { + .driver = { + .name = "si1133", + }, + .probe = si1133_probe, + .id_table = si1133_ids, +}; + +module_i2c_driver(si1133_driver); + +MODULE_AUTHOR("Maxime Roussin-Belanger "); +MODULE_DESCRIPTION("Silabs SI1133, UV index sensor and ambient light sensor driver"); +MODULE_LICENSE("GPL"); From 39e27533cdb4affdec79a89308ab9133a6e0e5c7 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 19 Jul 2018 16:51:50 +0200 Subject: [PATCH 30/39] dt-bindings: iio: sigma-delta-modulator: fix unit-address in example Device tree compiler gives a warning if a device node has "@" but no reg property. Fix the example in iio: adc: sigma-delta-modulator. Signed-off-by: Fabrice Gasnier Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/sigma-delta-modulator.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt b/Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt index ba24ca7ba95e..59b92cd32552 100644 --- a/Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt +++ b/Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt @@ -7,7 +7,7 @@ Required properties: Example node: - ads1202: adc@0 { + ads1202: adc { compatible = "sd-modulator"; #io-channel-cells = <0>; }; From c344a327baefc372b7f4b7d0c17ac8b417ceaf94 Mon Sep 17 00:00:00 2001 From: Manish Narani Date: Wed, 18 Jul 2018 16:42:09 +0530 Subject: [PATCH 31/39] iio: adc: xilinx: Remove dead code from xadc_zynq_setup This patch removes dead code from xadc_zynq_setup. The condition "if (tck_rate > XADC_ZYNQ_TCK_RATE_MAX)" cannot be true at any point of time. There is also an incompatible parameter used in the code. This patch fixes the same reported by coverity. Signed-off-by: Manish Narani Signed-off-by: Jonathan Cameron --- drivers/iio/adc/xilinx-xadc-core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/iio/adc/xilinx-xadc-core.c b/drivers/iio/adc/xilinx-xadc-core.c index d4f21d1be6c8..0127e8513166 100644 --- a/drivers/iio/adc/xilinx-xadc-core.c +++ b/drivers/iio/adc/xilinx-xadc-core.c @@ -341,8 +341,6 @@ static int xadc_zynq_setup(struct platform_device *pdev, pcap_rate = clk_get_rate(xadc->clk); - if (tck_rate > XADC_ZYNQ_TCK_RATE_MAX) - tck_rate = XADC_ZYNQ_TCK_RATE_MAX; if (tck_rate > pcap_rate / 2) { div = 2; } else { @@ -1045,7 +1043,7 @@ static int xadc_parse_dt(struct iio_dev *indio_dev, struct device_node *np, unsigned int num_channels; const char *external_mux; u32 ext_mux_chan; - int reg; + u32 reg; int ret; *conf = 0; From 69f894c3f3185d3826ea6573afb282386b797acf Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 5 Jul 2018 15:34:22 +0300 Subject: [PATCH 32/39] iio: ad9523: replace core mlock with local lock This is also part of a long term effort to make the use of mlock opaque and single purpose. This lock is required for accessing device registers. The device may be accessed by multiple processes at the same time, and this can result in inconsistent data, where one device reads data before the other one has finished writing. Signed-off-by: Lars-Peter Clausen Signed-off-by: Alexandru Ardelean Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/ad9523.c | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/drivers/iio/frequency/ad9523.c b/drivers/iio/frequency/ad9523.c index ddb6a334ae68..48ea46a1bc38 100644 --- a/drivers/iio/frequency/ad9523.c +++ b/drivers/iio/frequency/ad9523.c @@ -274,6 +274,15 @@ struct ad9523_state { unsigned long vco_out_freq[AD9523_NUM_CLK_SRC]; unsigned char vco_out_map[AD9523_NUM_CHAN_ALT_CLK_SRC]; + /* + * Lock for accessing device registers. Some operations require + * multiple consecutive R/W operations, during which the device + * shouldn't be interrupted. The buffers are also shared across + * all operations so need to be protected on stand alone reads and + * writes. + */ + struct mutex lock; + /* * DMA (thus cache coherency maintenance) requires the * transfer buffers to live in their own cache lines. @@ -500,6 +509,7 @@ static ssize_t ad9523_store(struct device *dev, { struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct ad9523_state *st = iio_priv(indio_dev); bool state; int ret; @@ -510,7 +520,7 @@ static ssize_t ad9523_store(struct device *dev, if (!state) return 0; - mutex_lock(&indio_dev->mlock); + mutex_lock(&st->lock); switch ((u32)this_attr->address) { case AD9523_SYNC: ret = ad9523_sync(indio_dev); @@ -521,7 +531,7 @@ static ssize_t ad9523_store(struct device *dev, default: ret = -ENODEV; } - mutex_unlock(&indio_dev->mlock); + mutex_unlock(&st->lock); return ret ? ret : len; } @@ -532,15 +542,16 @@ static ssize_t ad9523_show(struct device *dev, { struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct ad9523_state *st = iio_priv(indio_dev); int ret; - mutex_lock(&indio_dev->mlock); + mutex_lock(&st->lock); ret = ad9523_read(indio_dev, AD9523_READBACK_0); if (ret >= 0) { ret = sprintf(buf, "%d\n", !!(ret & (1 << (u32)this_attr->address))); } - mutex_unlock(&indio_dev->mlock); + mutex_unlock(&st->lock); return ret; } @@ -623,9 +634,9 @@ static int ad9523_read_raw(struct iio_dev *indio_dev, unsigned int code; int ret; - mutex_lock(&indio_dev->mlock); + mutex_lock(&st->lock); ret = ad9523_read(indio_dev, AD9523_CHANNEL_CLOCK_DIST(chan->channel)); - mutex_unlock(&indio_dev->mlock); + mutex_unlock(&st->lock); if (ret < 0) return ret; @@ -659,7 +670,7 @@ static int ad9523_write_raw(struct iio_dev *indio_dev, unsigned int reg; int ret, tmp, code; - mutex_lock(&indio_dev->mlock); + mutex_lock(&st->lock); ret = ad9523_read(indio_dev, AD9523_CHANNEL_CLOCK_DIST(chan->channel)); if (ret < 0) goto out; @@ -705,7 +716,7 @@ static int ad9523_write_raw(struct iio_dev *indio_dev, ad9523_io_update(indio_dev); out: - mutex_unlock(&indio_dev->mlock); + mutex_unlock(&st->lock); return ret; } @@ -713,9 +724,10 @@ static int ad9523_reg_access(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { + struct ad9523_state *st = iio_priv(indio_dev); int ret; - mutex_lock(&indio_dev->mlock); + mutex_lock(&st->lock); if (readval == NULL) { ret = ad9523_write(indio_dev, reg | AD9523_R1B, writeval); ad9523_io_update(indio_dev); @@ -728,7 +740,7 @@ static int ad9523_reg_access(struct iio_dev *indio_dev, } out_unlock: - mutex_unlock(&indio_dev->mlock); + mutex_unlock(&st->lock); return ret; } @@ -967,6 +979,8 @@ static int ad9523_probe(struct spi_device *spi) st = iio_priv(indio_dev); + mutex_init(&st->lock); + st->reg = devm_regulator_get(&spi->dev, "vcc"); if (!IS_ERR(st->reg)) { ret = regulator_enable(st->reg); From a176ba37e6282207be3f012fb58b77c6f51d55d9 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 9 Jul 2018 14:06:59 +0300 Subject: [PATCH 33/39] iio: adc: at91-sama5d2_adc: fix up casting in at91_adc_read_info_raw() This code is problematic because we're supposed to be writing an int but we instead write to only the high 16 bits. This doesn't work on big endian systems, and there is a potential that the bottom 16 bits are used without being initialized. Fixes: 23ec2774f1cc ("iio: adc: at91-sama5d2_adc: add support for position and pressure channels") Signed-off-by: Dan Carpenter Tested-by: Eugen Hristev Acked-by: Ludovic Desroches Signed-off-by: Jonathan Cameron --- drivers/iio/adc/at91-sama5d2_adc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c index e02f7d1c86bc..d5ea84cf6460 100644 --- a/drivers/iio/adc/at91-sama5d2_adc.c +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -1296,6 +1296,7 @@ static int at91_adc_read_info_raw(struct iio_dev *indio_dev, { struct at91_adc_state *st = iio_priv(indio_dev); u32 cor = 0; + u16 tmp_val; int ret; /* @@ -1309,7 +1310,8 @@ static int at91_adc_read_info_raw(struct iio_dev *indio_dev, mutex_lock(&st->lock); ret = at91_adc_read_position(st, chan->channel, - (u16 *)val); + &tmp_val); + *val = tmp_val; mutex_unlock(&st->lock); iio_device_release_direct_mode(indio_dev); @@ -1322,7 +1324,8 @@ static int at91_adc_read_info_raw(struct iio_dev *indio_dev, mutex_lock(&st->lock); ret = at91_adc_read_pressure(st, chan->channel, - (u16 *)val); + &tmp_val); + *val = tmp_val; mutex_unlock(&st->lock); iio_device_release_direct_mode(indio_dev); From 7d7209f0c467ba8b86229dbd4e1aa2f3d9b0c5bb Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 21 Jul 2018 14:37:33 -0500 Subject: [PATCH 34/39] iio: adc: ti-ads7950: use SPDX-License-Identifier This updates the ti-ads7950.c file to use SPDX-License-Identifier instead of more verbose license text. Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads7950.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/iio/adc/ti-ads7950.c b/drivers/iio/adc/ti-ads7950.c index 0225c1b333ab..f08c4de2bd10 100644 --- a/drivers/iio/adc/ti-ads7950.c +++ b/drivers/iio/adc/ti-ads7950.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Texas Instruments ADS7950 SPI ADC driver * @@ -10,15 +11,6 @@ * And also on hwmon/ads79xx.c * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/ * Nishanth Menon - * - * 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 version 2. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include From 8134b613b05dd27fc718fbe3785ccc9c5da10e02 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 21 Jul 2018 14:37:34 -0500 Subject: [PATCH 35/39] iio: adc: ti-ads7950: allow simultaneous use of buffer and direct mode This modifies the TI ADS7950 A/DC driver to allow the simultaneous use of both the triggered buffer and reading channels directly (via in- kernel API or sysfs). The use case for this is on LEGO MINDSTORMS EV3. Two of the voltage channels are used in-kernel by a power supply driver, which reads the values using iio_read_channel_processed(). These channels are only read at a slow rate (<= 1Hz). However, we want to be able to read 12 other channels at the same time using the triggered buffer at a high rate (>= 100Hz). Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads7950.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/iio/adc/ti-ads7950.c b/drivers/iio/adc/ti-ads7950.c index f08c4de2bd10..a5bd5944bc66 100644 --- a/drivers/iio/adc/ti-ads7950.c +++ b/drivers/iio/adc/ti-ads7950.c @@ -68,6 +68,9 @@ struct ti_ads7950_state { __be16 rx_buf[TI_ADS7950_MAX_CHAN + TI_ADS7950_TIMESTAMP_SIZE] ____cacheline_aligned; __be16 tx_buf[TI_ADS7950_MAX_CHAN]; + __be16 single_tx; + __be16 single_rx; + }; struct ti_ads7950_chip_info { @@ -287,18 +290,26 @@ static irqreturn_t ti_ads7950_trigger_handler(int irq, void *p) return IRQ_HANDLED; } -static int ti_ads7950_scan_direct(struct ti_ads7950_state *st, unsigned int ch) +static int ti_ads7950_scan_direct(struct iio_dev *indio_dev, unsigned int ch) { + struct ti_ads7950_state *st = iio_priv(indio_dev); int ret, cmd; + mutex_lock(&indio_dev->mlock); + cmd = TI_ADS7950_CR_WRITE | TI_ADS7950_CR_CHAN(ch) | st->settings; - st->tx_buf[0] = cpu_to_be16(cmd); + st->single_tx = cpu_to_be16(cmd); ret = spi_sync(st->spi, &st->scan_single_msg); if (ret) - return ret; + goto out; - return be16_to_cpu(st->rx_buf[0]); + ret = be16_to_cpu(st->single_rx); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret; } static int ti_ads7950_get_range(struct ti_ads7950_state *st) @@ -330,13 +341,7 @@ static int ti_ads7950_read_raw(struct iio_dev *indio_dev, switch (m) { case IIO_CHAN_INFO_RAW: - - ret = iio_device_claim_direct_mode(indio_dev); - if (ret < 0) - return ret; - - ret = ti_ads7950_scan_direct(st, chan->address); - iio_device_release_direct_mode(indio_dev); + ret = ti_ads7950_scan_direct(indio_dev, chan->address); if (ret < 0) return ret; @@ -402,13 +407,13 @@ static int ti_ads7950_probe(struct spi_device *spi) * was read at the end of the first transfer. */ - st->scan_single_xfer[0].tx_buf = &st->tx_buf[0]; + st->scan_single_xfer[0].tx_buf = &st->single_tx; st->scan_single_xfer[0].len = 2; st->scan_single_xfer[0].cs_change = 1; - st->scan_single_xfer[1].tx_buf = &st->tx_buf[0]; + st->scan_single_xfer[1].tx_buf = &st->single_tx; st->scan_single_xfer[1].len = 2; st->scan_single_xfer[1].cs_change = 1; - st->scan_single_xfer[2].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[2].rx_buf = &st->single_rx; st->scan_single_xfer[2].len = 2; spi_message_init_with_transfers(&st->scan_single_msg, From 439ba65b8b4f3788d55a90356fe25dc214721106 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 21 Jul 2018 21:40:48 +0200 Subject: [PATCH 36/39] dt-bindings: iio: adc: add Meson8m2 support The Amlogic Meson SAR ADC implementation on the Meson8m2 SoC is identical to the Meson8b variant. Add a compatible string to indicate that we support the SAR ADC on the Meson8m2 SoC. Signed-off-by: Martin Blumenstingl Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/amlogic,meson-saradc.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt b/Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt index d1acd5ea2737..54b823f3a453 100644 --- a/Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt +++ b/Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt @@ -4,6 +4,7 @@ Required properties: - compatible: depending on the SoC this should be one of: - "amlogic,meson8-saradc" for Meson8 - "amlogic,meson8b-saradc" for Meson8b + - "amlogic,meson8m2-saradc" for Meson8m2 - "amlogic,meson-gxbb-saradc" for GXBB - "amlogic,meson-gxl-saradc" for GXL - "amlogic,meson-gxm-saradc" for GXM From ffc0d638c838ff670fee2eb43d3884d05cb86faa Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 21 Jul 2018 21:40:49 +0200 Subject: [PATCH 37/39] iio: adc: meson-saradc: add support for the Meson8m2 SoCs The SAR ADC on Meson8m2 behaves identical to the one found in the Meson8b SoCs. Add a separate compatible string because the temperature sensor logic (not supported yet) differs between Meson8 and Meson8m2 (however, it's the same for Meson8b and Meson8m2). Signed-off-by: Martin Blumenstingl Signed-off-by: Jonathan Cameron --- drivers/iio/adc/meson_saradc.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c index 2948909f3ee3..da2d16dfa63e 100644 --- a/drivers/iio/adc/meson_saradc.c +++ b/drivers/iio/adc/meson_saradc.c @@ -922,6 +922,11 @@ static const struct meson_sar_adc_data meson_sar_adc_meson8b_data = { .name = "meson-meson8b-saradc", }; +static const struct meson_sar_adc_data meson_sar_adc_meson8m2_data = { + .param = &meson_sar_adc_meson8_param, + .name = "meson-meson8m2-saradc", +}; + static const struct meson_sar_adc_data meson_sar_adc_gxbb_data = { .param = &meson_sar_adc_gxbb_param, .name = "meson-gxbb-saradc", @@ -951,6 +956,10 @@ static const struct of_device_id meson_sar_adc_of_match[] = { .compatible = "amlogic,meson8b-saradc", .data = &meson_sar_adc_meson8b_data, }, + { + .compatible = "amlogic,meson8m2-saradc", + .data = &meson_sar_adc_meson8m2_data, + }, { .compatible = "amlogic,meson-gxbb-saradc", .data = &meson_sar_adc_gxbb_data, From 5a4e33c1c53ae7d4425f7d94e60e4458a37b349e Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 25 Jun 2018 11:03:07 +0300 Subject: [PATCH 38/39] iio: ad9523: Fix displayed phase Fix the displayed phase for the ad9523 driver. Currently the most significant decimal place is dropped and all other digits are shifted one to the left. This is due to a multiplication by 10, which is not necessary, so remove it. Signed-off-by: Lars-Peter Clausen Signed-off-by: Alexandru Ardelean Fixes: cd1678f9632 ("iio: frequency: New driver for AD9523 SPI Low Jitter Clock Generator") Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/ad9523.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/frequency/ad9523.c b/drivers/iio/frequency/ad9523.c index 48ea46a1bc38..37504739c277 100644 --- a/drivers/iio/frequency/ad9523.c +++ b/drivers/iio/frequency/ad9523.c @@ -653,7 +653,7 @@ static int ad9523_read_raw(struct iio_dev *indio_dev, code = (AD9523_CLK_DIST_DIV_PHASE_REV(ret) * 3141592) / AD9523_CLK_DIST_DIV_REV(ret); *val = code / 1000000; - *val2 = (code % 1000000) * 10; + *val2 = code % 1000000; return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; From c5b974bee9d2ceae4c441ae5a01e498c2674e100 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Sat, 7 Jul 2018 12:44:01 -0500 Subject: [PATCH 39/39] iio: sca3000: Fix missing return in switch The IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY case is missing a return and will fall through to the default case and errorenously return -EINVAL. Fix this by adding in missing *return ret*. Fixes: 626f971b5b07 ("staging:iio:accel:sca3000 Add write support to the low pass filter control") Reported-by: Jonathan Cameron Signed-off-by: Gustavo A. R. Silva Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/accel/sca3000.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c index 4dceb75e3586..4964561595f5 100644 --- a/drivers/iio/accel/sca3000.c +++ b/drivers/iio/accel/sca3000.c @@ -797,6 +797,7 @@ static int sca3000_write_raw(struct iio_dev *indio_dev, mutex_lock(&st->lock); ret = sca3000_write_3db_freq(st, val); mutex_unlock(&st->lock); + return ret; default: return -EINVAL; }