From d93de07a12e8b00e99ff9d7522c326ba6a5747c0 Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Thu, 21 Jun 2018 11:14:04 +0800 Subject: 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 (limited to 'Documentation') 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>; + }; + }; -- cgit v1.2.3 From 777baca07ef179301e363b10a14c428e5ebbd6a6 Mon Sep 17 00:00:00 2001 From: Stefan Popa Date: Wed, 4 Jul 2018 17:32:53 +0300 Subject: 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 (limited to 'Documentation') 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 -- cgit v1.2.3 From 7f310e5d07112e0bae57ce63de954e6773a26f81 Mon Sep 17 00:00:00 2001 From: Andreas Klinger Date: Tue, 10 Jul 2018 20:18:31 +0200 Subject: 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(+) (limited to 'Documentation') 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>; }; -- cgit v1.2.3 From 843429708e394a31d3298f9b6dfa8fca180f8bfa Mon Sep 17 00:00:00 2001 From: Andreas Klinger Date: Tue, 10 Jul 2018 20:19:37 +0200 Subject: 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(-) (limited to 'Documentation') 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>; -- cgit v1.2.3 From de8df0b9c38d8f232f0df03220ff540a54eaf73d Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 10 Jul 2018 21:09:30 -0400 Subject: 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(+) (limited to 'Documentation') 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 */ -- cgit v1.2.3 From d2b863baf1c7d92969c2a9dcada3c6b14e5dbbc4 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Tue, 10 Jul 2018 21:33:45 -0400 Subject: 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(-) (limited to 'Documentation') 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>; -- cgit v1.2.3 From ef89f4b96a2ab8dc1a0a3815d9365240e4c3c06b Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 13 Jul 2018 14:50:44 +0300 Subject: 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(-) (limited to 'Documentation') 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" }, { }, }; -- cgit v1.2.3 From c73314e6ebb2651a70ca8a3ff08d4bd6b9f9ade1 Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Fri, 20 Jul 2018 19:34:25 +0200 Subject: 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(+) (limited to 'Documentation') 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; -- cgit v1.2.3 From 1c28799257bca28cd5ba715e33157500d6239333 Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Fri, 20 Jul 2018 19:34:26 +0200 Subject: 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 --- Documentation/ABI/testing/sysfs-bus-iio-isl29501 | 47 + .../devicetree/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 (limited to 'Documentation') 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"); -- cgit v1.2.3 From c0e4e0fd952b73bf6aae67e92b9a496a52837eb9 Mon Sep 17 00:00:00 2001 From: Maxime Roussin-Bélanger Date: Thu, 19 Jul 2018 16:26:24 -0400 Subject: 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(-) (limited to 'Documentation') 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: -- cgit v1.2.3 From e01e7eaf37d865c72e2501a7b09e7a61317ce2d4 Mon Sep 17 00:00:00 2001 From: Maxime Roussin-Bélanger Date: Thu, 19 Jul 2018 16:26:25 -0400 Subject: 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 (limited to 'Documentation') 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"); -- cgit v1.2.3 From 39e27533cdb4affdec79a89308ab9133a6e0e5c7 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 19 Jul 2018 16:51:50 +0200 Subject: 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 --- Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation') 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>; }; -- cgit v1.2.3 From 439ba65b8b4f3788d55a90356fe25dc214721106 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 21 Jul 2018 21:40:48 +0200 Subject: 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 --- Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation') 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 -- cgit v1.2.3