diff options
Diffstat (limited to 'drivers/iio')
52 files changed, 6355 insertions, 361 deletions
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 7251d0e63d74..780f87f72338 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -6,21 +6,35 @@ * 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 <linux/module.h> #include <linux/regmap.h> #include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> #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_BW_RATE 0x2C #define ADXL345_REG_POWER_CTL 0x2D #define ADXL345_REG_DATA_FORMAT 0x31 #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_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 @@ -42,24 +56,33 @@ */ 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(reg, axis) { \ +#define ADXL345_CHANNEL(index, axis) { \ .type = IIO_ACCEL, \ .modified = 1, \ .channel2 = IIO_MOD_##axis, \ - .address = reg, \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .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) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ } 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, @@ -67,7 +90,9 @@ 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; + long long samp_freq_nhz; + unsigned int regval; int ret; switch (mask) { @@ -77,29 +102,117 @@ 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; - *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: + 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; + 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; } +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); + s64 n; + + 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); + 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, - const char *name) + enum adxl345_device_type type, const char *name) { struct adxl345_data *data; struct iio_dev *indio_dev; @@ -125,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" }, { }, }; diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index c149c9c360fc..421a0a8a1379 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; } 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; } diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c index 056dddb27236..2ca5d1f6ade0 100644 --- a/drivers/iio/accel/st_accel_i2c.c +++ b/drivers/iio/accel/st_accel_i2c.c @@ -14,8 +14,8 @@ #include <linux/acpi.h> #include <linux/i2c.h> #include <linux/iio/iio.h> +#include <linux/property.h> -#include <linux/iio/common/st_sensors.h> #include <linux/iio/common/st_sensors_i2c.h> #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,33 +117,33 @@ 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); -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; + const char *match; int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adata)); @@ -152,19 +152,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); @@ -188,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, }; diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 9da79070357c..4a754921fb6f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -157,7 +157,6 @@ config AT91_SAMA5D2_ADC tristate "Atmel AT91 SAMA5D2 ADC" depends on ARCH_AT91 || COMPILE_TEST depends on HAS_IOMEM - depends on HAS_DMA select IIO_BUFFER select IIO_TRIGGERED_BUFFER help @@ -621,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 @@ -647,7 +656,6 @@ config SD_ADC_MODULATOR config STM32_ADC_CORE tristate "STMicroelectronics STM32 adc core" depends on ARCH_STM32 || COMPILE_TEST - depends on HAS_DMA depends on OF depends on REGULATOR select IIO_BUFFER @@ -717,6 +725,7 @@ config SUN4I_GPADC depends on IIO depends on MFD_SUN4I_GPADC || MACH_SUN8I depends on THERMAL || !THERMAL_OF + select REGMAP_IRQ help Say yes here to build support for Allwinner (A10, A13 and A31) SoCs GPADC. This ADC provides 4 channels which can be used as an ADC or as 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/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c index cf1b048b0665..fc9510716ac7 100644 --- a/drivers/iio/adc/ad_sigma_delta.c +++ b/drivers/iio/adc/ad_sigma_delta.c @@ -209,6 +209,7 @@ static int ad_sd_calibrate(struct ad_sigma_delta *sigma_delta, unsigned int mode, unsigned int channel) { int ret; + unsigned long timeout; ret = ad_sigma_delta_set_channel(sigma_delta, channel); if (ret) @@ -224,8 +225,8 @@ static int ad_sd_calibrate(struct ad_sigma_delta *sigma_delta, sigma_delta->irq_dis = false; enable_irq(sigma_delta->spi->irq); - ret = wait_for_completion_timeout(&sigma_delta->completion, 2*HZ); - if (ret == 0) { + timeout = wait_for_completion_timeout(&sigma_delta->completion, 2 * HZ); + if (timeout == 0) { sigma_delta->irq_dis = true; disable_irq_nosync(sigma_delta->spi->irq); ret = -EIO; diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c index 8729d6524b4d..d5ea84cf6460 100644 --- a/drivers/iio/adc/at91-sama5d2_adc.c +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -102,14 +102,26 @@ #define AT91_SAMA5D2_LCDR 0x20 /* Interrupt Enable Register */ #define AT91_SAMA5D2_IER 0x24 +/* Interrupt Enable Register - TS X measurement ready */ +#define AT91_SAMA5D2_IER_XRDY BIT(20) +/* Interrupt Enable Register - TS Y measurement ready */ +#define AT91_SAMA5D2_IER_YRDY BIT(21) +/* Interrupt Enable Register - TS pressure measurement ready */ +#define AT91_SAMA5D2_IER_PRDY BIT(22) /* Interrupt Enable Register - general overrun error */ #define AT91_SAMA5D2_IER_GOVRE BIT(25) +/* Interrupt Enable Register - Pen detect */ +#define AT91_SAMA5D2_IER_PEN BIT(29) +/* Interrupt Enable Register - No pen detect */ +#define AT91_SAMA5D2_IER_NOPEN BIT(30) /* Interrupt Disable Register */ #define AT91_SAMA5D2_IDR 0x28 /* Interrupt Mask Register */ #define AT91_SAMA5D2_IMR 0x2c /* Interrupt Status Register */ #define AT91_SAMA5D2_ISR 0x30 +/* Interrupt Status Register - Pen touching sense status */ +#define AT91_SAMA5D2_ISR_PENS BIT(31) /* Last Channel Trigger Mode Register */ #define AT91_SAMA5D2_LCTMR 0x34 /* Last Channel Compare Window Register */ @@ -118,6 +130,15 @@ #define AT91_SAMA5D2_OVER 0x3c /* Extended Mode Register */ #define AT91_SAMA5D2_EMR 0x40 +/* Extended Mode Register - Oversampling rate */ +#define AT91_SAMA5D2_EMR_OSR(V) ((V) << 16) +#define AT91_SAMA5D2_EMR_OSR_MASK GENMASK(17, 16) +#define AT91_SAMA5D2_EMR_OSR_1SAMPLES 0 +#define AT91_SAMA5D2_EMR_OSR_4SAMPLES 1 +#define AT91_SAMA5D2_EMR_OSR_16SAMPLES 2 + +/* Extended Mode Register - Averaging on single trigger event */ +#define AT91_SAMA5D2_EMR_ASTE(V) ((V) << 20) /* Compare Window Register */ #define AT91_SAMA5D2_CWR 0x44 /* Channel Gain Register */ @@ -131,8 +152,38 @@ #define AT91_SAMA5D2_CDR0 0x50 /* Analog Control Register */ #define AT91_SAMA5D2_ACR 0x94 +/* Analog Control Register - Pen detect sensitivity mask */ +#define AT91_SAMA5D2_ACR_PENDETSENS_MASK GENMASK(1, 0) + /* Touchscreen Mode Register */ #define AT91_SAMA5D2_TSMR 0xb0 +/* Touchscreen Mode Register - No touch mode */ +#define AT91_SAMA5D2_TSMR_TSMODE_NONE 0 +/* Touchscreen Mode Register - 4 wire screen, no pressure measurement */ +#define AT91_SAMA5D2_TSMR_TSMODE_4WIRE_NO_PRESS 1 +/* Touchscreen Mode Register - 4 wire screen, pressure measurement */ +#define AT91_SAMA5D2_TSMR_TSMODE_4WIRE_PRESS 2 +/* Touchscreen Mode Register - 5 wire screen */ +#define AT91_SAMA5D2_TSMR_TSMODE_5WIRE 3 +/* Touchscreen Mode Register - Average samples mask */ +#define AT91_SAMA5D2_TSMR_TSAV_MASK GENMASK(5, 4) +/* Touchscreen Mode Register - Average samples */ +#define AT91_SAMA5D2_TSMR_TSAV(x) ((x) << 4) +/* Touchscreen Mode Register - Touch/trigger frequency ratio mask */ +#define AT91_SAMA5D2_TSMR_TSFREQ_MASK GENMASK(11, 8) +/* Touchscreen Mode Register - Touch/trigger frequency ratio */ +#define AT91_SAMA5D2_TSMR_TSFREQ(x) ((x) << 8) +/* Touchscreen Mode Register - Pen Debounce Time mask */ +#define AT91_SAMA5D2_TSMR_PENDBC_MASK GENMASK(31, 28) +/* Touchscreen Mode Register - Pen Debounce Time */ +#define AT91_SAMA5D2_TSMR_PENDBC(x) ((x) << 28) +/* Touchscreen Mode Register - No DMA for touch measurements */ +#define AT91_SAMA5D2_TSMR_NOTSDMA BIT(22) +/* Touchscreen Mode Register - Disable pen detection */ +#define AT91_SAMA5D2_TSMR_PENDET_DIS (0 << 24) +/* Touchscreen Mode Register - Enable pen detection */ +#define AT91_SAMA5D2_TSMR_PENDET_ENA BIT(24) + /* Touchscreen X Position Register */ #define AT91_SAMA5D2_XPOSR 0xb4 /* Touchscreen Y Position Register */ @@ -151,6 +202,12 @@ #define AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_FALL 2 /* Trigger Mode external trigger any edge */ #define AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_ANY 3 +/* Trigger Mode internal periodic */ +#define AT91_SAMA5D2_TRGR_TRGMOD_PERIODIC 5 +/* Trigger Mode - trigger period mask */ +#define AT91_SAMA5D2_TRGR_TRGPER_MASK GENMASK(31, 16) +/* Trigger Mode - trigger period */ +#define AT91_SAMA5D2_TRGR_TRGPER(x) ((x) << 16) /* Correction Select Register */ #define AT91_SAMA5D2_COSR 0xd0 @@ -169,6 +226,22 @@ #define AT91_SAMA5D2_SINGLE_CHAN_CNT 12 #define AT91_SAMA5D2_DIFF_CHAN_CNT 6 +#define AT91_SAMA5D2_TIMESTAMP_CHAN_IDX (AT91_SAMA5D2_SINGLE_CHAN_CNT + \ + AT91_SAMA5D2_DIFF_CHAN_CNT + 1) + +#define AT91_SAMA5D2_TOUCH_X_CHAN_IDX (AT91_SAMA5D2_SINGLE_CHAN_CNT + \ + AT91_SAMA5D2_DIFF_CHAN_CNT * 2) +#define AT91_SAMA5D2_TOUCH_Y_CHAN_IDX (AT91_SAMA5D2_TOUCH_X_CHAN_IDX + 1) +#define AT91_SAMA5D2_TOUCH_P_CHAN_IDX (AT91_SAMA5D2_TOUCH_Y_CHAN_IDX + 1) +#define AT91_SAMA5D2_MAX_CHAN_IDX AT91_SAMA5D2_TOUCH_P_CHAN_IDX + +#define AT91_SAMA5D2_TOUCH_SAMPLE_PERIOD_US 2000 /* 2ms */ +#define AT91_SAMA5D2_TOUCH_PEN_DETECT_DEBOUNCE_US 200 + +#define AT91_SAMA5D2_XYZ_MASK GENMASK(11, 0) + +#define AT91_SAMA5D2_MAX_POS_BITS 12 + /* * Maximum number of bytes to hold conversion from all channels * without the timestamp. @@ -184,6 +257,11 @@ #define AT91_HWFIFO_MAX_SIZE_STR "128" #define AT91_HWFIFO_MAX_SIZE 128 +/* Possible values for oversampling ratio */ +#define AT91_OSR_1SAMPLES 1 +#define AT91_OSR_4SAMPLES 4 +#define AT91_OSR_16SAMPLES 16 + #define AT91_SAMA5D2_CHAN_SINGLE(num, addr) \ { \ .type = IIO_VOLTAGE, \ @@ -192,12 +270,13 @@ .scan_index = num, \ .scan_type = { \ .sign = 'u', \ - .realbits = 12, \ + .realbits = 14, \ .storagebits = 16, \ }, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ - .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ)|\ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ .datasheet_name = "CH"#num, \ .indexed = 1, \ } @@ -212,16 +291,50 @@ .scan_index = num + AT91_SAMA5D2_SINGLE_CHAN_CNT, \ .scan_type = { \ .sign = 's', \ - .realbits = 12, \ + .realbits = 14, \ .storagebits = 16, \ }, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ - .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ)|\ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ .datasheet_name = "CH"#num"-CH"#num2, \ .indexed = 1, \ } +#define AT91_SAMA5D2_CHAN_TOUCH(num, name, mod) \ + { \ + .type = IIO_POSITIONRELATIVE, \ + .modified = 1, \ + .channel = num, \ + .channel2 = mod, \ + .scan_index = num, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ)|\ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .datasheet_name = name, \ + } +#define AT91_SAMA5D2_CHAN_PRESSURE(num, name) \ + { \ + .type = IIO_PRESSURE, \ + .channel = num, \ + .scan_index = num, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ)|\ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .datasheet_name = name, \ + } + #define at91_adc_readl(st, reg) readl_relaxed(st->base + reg) #define at91_adc_writel(st, reg, val) writel_relaxed(val, st->base + reg) @@ -260,6 +373,22 @@ struct at91_adc_dma { s64 dma_ts; }; +/** + * at91_adc_touch - at91-sama5d2 touchscreen information struct + * @sample_period_val: the value for periodic trigger interval + * @touching: is the pen touching the screen or not + * @x_pos: temporary placeholder for pressure computation + * @channels_bitmask: bitmask with the touchscreen channels enabled + * @workq: workqueue for buffer data pushing + */ +struct at91_adc_touch { + u16 sample_period_val; + bool touching; + u16 x_pos; + unsigned long channels_bitmask; + struct work_struct workq; +}; + struct at91_adc_state { void __iomem *base; int irq; @@ -267,14 +396,17 @@ struct at91_adc_state { struct regulator *reg; struct regulator *vref; int vref_uv; + unsigned int current_sample_rate; struct iio_trigger *trig; const struct at91_adc_trigger *selected_trig; const struct iio_chan_spec *chan; bool conversion_done; u32 conversion_value; + unsigned int oversampling_ratio; struct at91_adc_soc_info soc_info; wait_queue_head_t wq_data_available; struct at91_adc_dma dma_st; + struct at91_adc_touch touch_st; u16 buffer[AT91_BUFFER_MAX_HWORDS]; /* * lock to prevent concurrent 'single conversion' requests through @@ -329,8 +461,10 @@ static const struct iio_chan_spec at91_adc_channels[] = { AT91_SAMA5D2_CHAN_DIFF(6, 7, 0x68), AT91_SAMA5D2_CHAN_DIFF(8, 9, 0x70), AT91_SAMA5D2_CHAN_DIFF(10, 11, 0x78), - IIO_CHAN_SOFT_TIMESTAMP(AT91_SAMA5D2_SINGLE_CHAN_CNT - + AT91_SAMA5D2_DIFF_CHAN_CNT + 1), + IIO_CHAN_SOFT_TIMESTAMP(AT91_SAMA5D2_TIMESTAMP_CHAN_IDX), + AT91_SAMA5D2_CHAN_TOUCH(AT91_SAMA5D2_TOUCH_X_CHAN_IDX, "x", IIO_MOD_X), + AT91_SAMA5D2_CHAN_TOUCH(AT91_SAMA5D2_TOUCH_Y_CHAN_IDX, "y", IIO_MOD_Y), + AT91_SAMA5D2_CHAN_PRESSURE(AT91_SAMA5D2_TOUCH_P_CHAN_IDX, "pressure"), }; static int at91_adc_chan_xlate(struct iio_dev *indio_dev, int chan) @@ -354,6 +488,231 @@ at91_adc_chan_get(struct iio_dev *indio_dev, int chan) return indio_dev->channels + index; } +static inline int at91_adc_of_xlate(struct iio_dev *indio_dev, + const struct of_phandle_args *iiospec) +{ + return at91_adc_chan_xlate(indio_dev, iiospec->args[0]); +} + +static void at91_adc_config_emr(struct at91_adc_state *st) +{ + /* configure the extended mode register */ + unsigned int emr = at91_adc_readl(st, AT91_SAMA5D2_EMR); + + /* select oversampling per single trigger event */ + emr |= AT91_SAMA5D2_EMR_ASTE(1); + + /* delete leftover content if it's the case */ + emr &= ~AT91_SAMA5D2_EMR_OSR_MASK; + + /* select oversampling ratio from configuration */ + switch (st->oversampling_ratio) { + case AT91_OSR_1SAMPLES: + emr |= AT91_SAMA5D2_EMR_OSR(AT91_SAMA5D2_EMR_OSR_1SAMPLES) & + AT91_SAMA5D2_EMR_OSR_MASK; + break; + case AT91_OSR_4SAMPLES: + emr |= AT91_SAMA5D2_EMR_OSR(AT91_SAMA5D2_EMR_OSR_4SAMPLES) & + AT91_SAMA5D2_EMR_OSR_MASK; + break; + case AT91_OSR_16SAMPLES: + emr |= AT91_SAMA5D2_EMR_OSR(AT91_SAMA5D2_EMR_OSR_16SAMPLES) & + AT91_SAMA5D2_EMR_OSR_MASK; + break; + } + + at91_adc_writel(st, AT91_SAMA5D2_EMR, emr); +} + +static int at91_adc_adjust_val_osr(struct at91_adc_state *st, int *val) +{ + if (st->oversampling_ratio == AT91_OSR_1SAMPLES) { + /* + * in this case we only have 12 bits of real data, but channel + * is registered as 14 bits, so shift left two bits + */ + *val <<= 2; + } else if (st->oversampling_ratio == AT91_OSR_4SAMPLES) { + /* + * in this case we have 13 bits of real data, but channel + * is registered as 14 bits, so left shift one bit + */ + *val <<= 1; + } + + return IIO_VAL_INT; +} + +static void at91_adc_adjust_val_osr_array(struct at91_adc_state *st, void *buf, + int len) +{ + int i = 0, val; + u16 *buf_u16 = (u16 *) buf; + + /* + * We are converting each two bytes (each sample). + * First convert the byte based array to u16, and convert each sample + * separately. + * Each value is two bytes in an array of chars, so to not shift + * more than we need, save the value separately. + * len is in bytes, so divide by two to get number of samples. + */ + while (i < len / 2) { + val = buf_u16[i]; + at91_adc_adjust_val_osr(st, &val); + buf_u16[i] = val; + i++; + } +} + +static int at91_adc_configure_touch(struct at91_adc_state *st, bool state) +{ + u32 clk_khz = st->current_sample_rate / 1000; + int i = 0; + u16 pendbc; + u32 tsmr, acr; + + if (!state) { + /* disabling touch IRQs and setting mode to no touch enabled */ + at91_adc_writel(st, AT91_SAMA5D2_IDR, + AT91_SAMA5D2_IER_PEN | AT91_SAMA5D2_IER_NOPEN); + at91_adc_writel(st, AT91_SAMA5D2_TSMR, 0); + return 0; + } + /* + * debounce time is in microseconds, we need it in milliseconds to + * multiply with kilohertz, so, divide by 1000, but after the multiply. + * round up to make sure pendbc is at least 1 + */ + pendbc = round_up(AT91_SAMA5D2_TOUCH_PEN_DETECT_DEBOUNCE_US * + clk_khz / 1000, 1); + + /* get the required exponent */ + while (pendbc >> i++) + ; + + pendbc = i; + + tsmr = AT91_SAMA5D2_TSMR_TSMODE_4WIRE_PRESS; + + tsmr |= AT91_SAMA5D2_TSMR_TSAV(2) & AT91_SAMA5D2_TSMR_TSAV_MASK; + tsmr |= AT91_SAMA5D2_TSMR_PENDBC(pendbc) & + AT91_SAMA5D2_TSMR_PENDBC_MASK; + tsmr |= AT91_SAMA5D2_TSMR_NOTSDMA; + tsmr |= AT91_SAMA5D2_TSMR_PENDET_ENA; + tsmr |= AT91_SAMA5D2_TSMR_TSFREQ(2) & AT91_SAMA5D2_TSMR_TSFREQ_MASK; + + at91_adc_writel(st, AT91_SAMA5D2_TSMR, tsmr); + + acr = at91_adc_readl(st, AT91_SAMA5D2_ACR); + acr &= ~AT91_SAMA5D2_ACR_PENDETSENS_MASK; + acr |= 0x02 & AT91_SAMA5D2_ACR_PENDETSENS_MASK; + at91_adc_writel(st, AT91_SAMA5D2_ACR, acr); + + /* Sample Period Time = (TRGPER + 1) / ADCClock */ + st->touch_st.sample_period_val = + round_up((AT91_SAMA5D2_TOUCH_SAMPLE_PERIOD_US * + clk_khz / 1000) - 1, 1); + /* enable pen detect IRQ */ + at91_adc_writel(st, AT91_SAMA5D2_IER, AT91_SAMA5D2_IER_PEN); + + return 0; +} + +static u16 at91_adc_touch_pos(struct at91_adc_state *st, int reg) +{ + u32 val; + u32 scale, result, pos; + + /* + * to obtain the actual position we must divide by scale + * and multiply with max, where + * max = 2^AT91_SAMA5D2_MAX_POS_BITS - 1 + */ + /* first half of register is the x or y, second half is the scale */ + val = at91_adc_readl(st, reg); + if (!val) + dev_dbg(&iio_priv_to_dev(st)->dev, "pos is 0\n"); + + pos = val & AT91_SAMA5D2_XYZ_MASK; + result = (pos << AT91_SAMA5D2_MAX_POS_BITS) - pos; + scale = (val >> 16) & AT91_SAMA5D2_XYZ_MASK; + if (scale == 0) { + dev_err(&iio_priv_to_dev(st)->dev, "scale is 0\n"); + return 0; + } + result /= scale; + + return result; +} + +static u16 at91_adc_touch_x_pos(struct at91_adc_state *st) +{ + st->touch_st.x_pos = at91_adc_touch_pos(st, AT91_SAMA5D2_XPOSR); + return st->touch_st.x_pos; +} + +static u16 at91_adc_touch_y_pos(struct at91_adc_state *st) +{ + return at91_adc_touch_pos(st, AT91_SAMA5D2_YPOSR); +} + +static u16 at91_adc_touch_pressure(struct at91_adc_state *st) +{ + u32 val; + u32 z1, z2; + u32 pres; + u32 rxp = 1; + u32 factor = 1000; + + /* calculate the pressure */ + val = at91_adc_readl(st, AT91_SAMA5D2_PRESSR); + z1 = val & AT91_SAMA5D2_XYZ_MASK; + z2 = (val >> 16) & AT91_SAMA5D2_XYZ_MASK; + + if (z1 != 0) + pres = rxp * (st->touch_st.x_pos * factor / 1024) * + (z2 * factor / z1 - factor) / + factor; + else + pres = 0xFFFF; /* no pen contact */ + + /* + * The pressure from device grows down, minimum is 0xFFFF, maximum 0x0. + * We compute it this way, but let's return it in the expected way, + * growing from 0 to 0xFFFF. + */ + return 0xFFFF - pres; +} + +static int at91_adc_read_position(struct at91_adc_state *st, int chan, u16 *val) +{ + *val = 0; + if (!st->touch_st.touching) + return -ENODATA; + if (chan == AT91_SAMA5D2_TOUCH_X_CHAN_IDX) + *val = at91_adc_touch_x_pos(st); + else if (chan == AT91_SAMA5D2_TOUCH_Y_CHAN_IDX) + *val = at91_adc_touch_y_pos(st); + else + return -ENODATA; + + return IIO_VAL_INT; +} + +static int at91_adc_read_pressure(struct at91_adc_state *st, int chan, u16 *val) +{ + *val = 0; + if (!st->touch_st.touching) + return -ENODATA; + if (chan == AT91_SAMA5D2_TOUCH_P_CHAN_IDX) + *val = at91_adc_touch_pressure(st); + else + return -ENODATA; + + return IIO_VAL_INT; +} + static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) { struct iio_dev *indio = iio_trigger_get_drvdata(trig); @@ -375,6 +734,11 @@ static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) if (!chan) continue; + /* these channel types cannot be handled by this trigger */ + if (chan->type == IIO_POSITIONRELATIVE || + chan->type == IIO_PRESSURE) + continue; + if (state) { at91_adc_writel(st, AT91_SAMA5D2_CHER, BIT(chan->channel)); @@ -520,7 +884,20 @@ static int at91_adc_dma_start(struct iio_dev *indio_dev) static int at91_adc_buffer_postenable(struct iio_dev *indio_dev) { int ret; + struct at91_adc_state *st = iio_priv(indio_dev); + /* check if we are enabling triggered buffer or the touchscreen */ + if (bitmap_subset(indio_dev->active_scan_mask, + &st->touch_st.channels_bitmask, + AT91_SAMA5D2_MAX_CHAN_IDX + 1)) { + /* touchscreen enabling */ + return at91_adc_configure_touch(st, true); + } + /* if we are not in triggered mode, we cannot enable the buffer. */ + if (!(indio_dev->currentmode & INDIO_ALL_TRIGGERED_MODES)) + return -EINVAL; + + /* we continue with the triggered buffer */ ret = at91_adc_dma_start(indio_dev); if (ret) { dev_err(&indio_dev->dev, "buffer postenable failed\n"); @@ -536,6 +913,18 @@ static int at91_adc_buffer_predisable(struct iio_dev *indio_dev) int ret; u8 bit; + /* check if we are disabling triggered buffer or the touchscreen */ + if (bitmap_subset(indio_dev->active_scan_mask, + &st->touch_st.channels_bitmask, + AT91_SAMA5D2_MAX_CHAN_IDX + 1)) { + /* touchscreen disable */ + return at91_adc_configure_touch(st, false); + } + /* if we are not in triggered mode, nothing to do here */ + if (!(indio_dev->currentmode & INDIO_ALL_TRIGGERED_MODES)) + return -EINVAL; + + /* continue with the triggered buffer */ ret = iio_triggered_buffer_predisable(indio_dev); if (ret < 0) dev_err(&indio_dev->dev, "buffer predisable failed\n"); @@ -558,6 +947,10 @@ static int at91_adc_buffer_predisable(struct iio_dev *indio_dev) if (!chan) continue; + /* these channel types are virtual, no need to do anything */ + if (chan->type == IIO_POSITIONRELATIVE || + chan->type == IIO_PRESSURE) + continue; if (st->dma_st.dma_chan) at91_adc_readl(st, chan->address); } @@ -613,6 +1006,7 @@ static void at91_adc_trigger_handler_nodma(struct iio_dev *indio_dev, { struct at91_adc_state *st = iio_priv(indio_dev); int i = 0; + int val; u8 bit; for_each_set_bit(bit, indio_dev->active_scan_mask, @@ -622,7 +1016,24 @@ static void at91_adc_trigger_handler_nodma(struct iio_dev *indio_dev, if (!chan) continue; - st->buffer[i] = at91_adc_readl(st, chan->address); + /* + * Our external trigger only supports the voltage channels. + * In case someone requested a different type of channel + * just put zeroes to buffer. + * This should not happen because we check the scan mode + * and scan mask when we enable the buffer, and we don't allow + * the buffer to start with a mixed mask (voltage and something + * else). + * Thus, emit a warning. + */ + if (chan->type == IIO_VOLTAGE) { + val = at91_adc_readl(st, chan->address); + at91_adc_adjust_val_osr(st, &val); + st->buffer[i] = val; + } else { + st->buffer[i] = 0; + WARN(true, "This trigger cannot handle this type of channel"); + } i++; } iio_push_to_buffers_with_timestamp(indio_dev, st->buffer, @@ -654,6 +1065,14 @@ static void at91_adc_trigger_handler_dma(struct iio_dev *indio_dev) interval = div_s64((ns - st->dma_st.dma_ts), sample_count); while (transferred_len >= sample_size) { + /* + * for all the values in the current sample, + * adjust the values inside the buffer for oversampling + */ + at91_adc_adjust_val_osr_array(st, + &st->dma_st.rx_buf[st->dma_st.buf_idx], + sample_size); + iio_push_to_buffers_with_timestamp(indio_dev, (st->dma_st.rx_buf + st->dma_st.buf_idx), (st->dma_st.dma_ts + interval * sample_index)); @@ -688,9 +1107,20 @@ static irqreturn_t at91_adc_trigger_handler(int irq, void *p) static int at91_adc_buffer_init(struct iio_dev *indio) { - return devm_iio_triggered_buffer_setup(&indio->dev, indio, + struct at91_adc_state *st = iio_priv(indio); + + if (st->selected_trig->hw_trig) { + return devm_iio_triggered_buffer_setup(&indio->dev, indio, &iio_pollfunc_store_time, &at91_adc_trigger_handler, &at91_buffer_setup_ops); + } + /* + * we need to prepare the buffer ops in case we will get + * another buffer attached (like a callback buffer for the touchscreen) + */ + indio->setup_ops = &at91_buffer_setup_ops; + + return 0; } static unsigned at91_adc_startup_time(unsigned startup_time_min, @@ -736,19 +1166,83 @@ static void at91_adc_setup_samp_freq(struct at91_adc_state *st, unsigned freq) dev_dbg(&indio_dev->dev, "freq: %u, startup: %u, prescal: %u\n", freq, startup, prescal); + st->current_sample_rate = freq; } -static unsigned at91_adc_get_sample_freq(struct at91_adc_state *st) +static inline unsigned at91_adc_get_sample_freq(struct at91_adc_state *st) { - unsigned f_adc, f_per = clk_get_rate(st->per_clk); - unsigned mr, prescal; + return st->current_sample_rate; +} - mr = at91_adc_readl(st, AT91_SAMA5D2_MR); - prescal = (mr >> AT91_SAMA5D2_MR_PRESCAL_OFFSET) - & AT91_SAMA5D2_MR_PRESCAL_MAX; - f_adc = f_per / (2 * (prescal + 1)); +static void at91_adc_touch_data_handler(struct iio_dev *indio_dev) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + u8 bit; + u16 val; + int i = 0; + + for_each_set_bit(bit, indio_dev->active_scan_mask, + AT91_SAMA5D2_MAX_CHAN_IDX + 1) { + struct iio_chan_spec const *chan = + at91_adc_chan_get(indio_dev, bit); + + if (chan->type == IIO_POSITIONRELATIVE) + at91_adc_read_position(st, chan->channel, &val); + else if (chan->type == IIO_PRESSURE) + at91_adc_read_pressure(st, chan->channel, &val); + else + continue; + st->buffer[i] = val; + i++; + } + /* + * Schedule work to push to buffers. + * This is intended to push to the callback buffer that another driver + * registered. We are still in a handler from our IRQ. If we push + * directly, it means the other driver has it's callback called + * from our IRQ context. Which is something we better avoid. + * Let's schedule it after our IRQ is completed. + */ + schedule_work(&st->touch_st.workq); +} + +static void at91_adc_pen_detect_interrupt(struct at91_adc_state *st) +{ + at91_adc_writel(st, AT91_SAMA5D2_IDR, AT91_SAMA5D2_IER_PEN); + at91_adc_writel(st, AT91_SAMA5D2_IER, AT91_SAMA5D2_IER_NOPEN | + AT91_SAMA5D2_IER_XRDY | AT91_SAMA5D2_IER_YRDY | + AT91_SAMA5D2_IER_PRDY); + at91_adc_writel(st, AT91_SAMA5D2_TRGR, + AT91_SAMA5D2_TRGR_TRGMOD_PERIODIC | + AT91_SAMA5D2_TRGR_TRGPER(st->touch_st.sample_period_val)); + st->touch_st.touching = true; +} + +static void at91_adc_no_pen_detect_interrupt(struct at91_adc_state *st) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + + at91_adc_writel(st, AT91_SAMA5D2_TRGR, + AT91_SAMA5D2_TRGR_TRGMOD_NO_TRIGGER); + at91_adc_writel(st, AT91_SAMA5D2_IDR, AT91_SAMA5D2_IER_NOPEN | + AT91_SAMA5D2_IER_XRDY | AT91_SAMA5D2_IER_YRDY | + AT91_SAMA5D2_IER_PRDY); + st->touch_st.touching = false; + + at91_adc_touch_data_handler(indio_dev); + + at91_adc_writel(st, AT91_SAMA5D2_IER, AT91_SAMA5D2_IER_PEN); +} + +static void at91_adc_workq_handler(struct work_struct *workq) +{ + struct at91_adc_touch *touch_st = container_of(workq, + struct at91_adc_touch, workq); + struct at91_adc_state *st = container_of(touch_st, + struct at91_adc_state, touch_st); + struct iio_dev *indio_dev = iio_priv_to_dev(st); - return f_adc; + iio_push_to_buffers(indio_dev, st->buffer); } static irqreturn_t at91_adc_interrupt(int irq, void *private) @@ -757,17 +1251,39 @@ static irqreturn_t at91_adc_interrupt(int irq, void *private) struct at91_adc_state *st = iio_priv(indio); u32 status = at91_adc_readl(st, AT91_SAMA5D2_ISR); u32 imr = at91_adc_readl(st, AT91_SAMA5D2_IMR); + u32 rdy_mask = AT91_SAMA5D2_IER_XRDY | AT91_SAMA5D2_IER_YRDY | + AT91_SAMA5D2_IER_PRDY; if (!(status & imr)) return IRQ_NONE; - - if (iio_buffer_enabled(indio) && !st->dma_st.dma_chan) { + if (status & AT91_SAMA5D2_IER_PEN) { + /* pen detected IRQ */ + at91_adc_pen_detect_interrupt(st); + } else if ((status & AT91_SAMA5D2_IER_NOPEN)) { + /* nopen detected IRQ */ + at91_adc_no_pen_detect_interrupt(st); + } else if ((status & AT91_SAMA5D2_ISR_PENS) && + ((status & rdy_mask) == rdy_mask)) { + /* periodic trigger IRQ - during pen sense */ + at91_adc_touch_data_handler(indio); + } else if (status & AT91_SAMA5D2_ISR_PENS) { + /* + * touching, but the measurements are not ready yet. + * read and ignore. + */ + status = at91_adc_readl(st, AT91_SAMA5D2_XPOSR); + status = at91_adc_readl(st, AT91_SAMA5D2_YPOSR); + status = at91_adc_readl(st, AT91_SAMA5D2_PRESSR); + } else if (iio_buffer_enabled(indio) && !st->dma_st.dma_chan) { + /* triggered buffer without DMA */ disable_irq_nosync(irq); iio_trigger_poll(indio->trig); } else if (iio_buffer_enabled(indio) && st->dma_st.dma_chan) { + /* triggered buffer with DMA - should not happen */ disable_irq_nosync(irq); WARN(true, "Unexpected irq occurred\n"); } else if (!iio_buffer_enabled(indio)) { + /* software requested conversion */ st->conversion_value = at91_adc_readl(st, st->chan->address); st->conversion_done = true; wake_up_interruptible(&st->wq_data_available); @@ -775,58 +1291,100 @@ static irqreturn_t at91_adc_interrupt(int irq, void *private) return IRQ_HANDLED; } -static int at91_adc_read_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int *val, int *val2, long mask) +static int at91_adc_read_info_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) { struct at91_adc_state *st = iio_priv(indio_dev); u32 cor = 0; + u16 tmp_val; int ret; - switch (mask) { - case IIO_CHAN_INFO_RAW: - /* we cannot use software trigger if hw trigger enabled */ + /* + * Keep in mind that we cannot use software trigger or touchscreen + * if external trigger is enabled + */ + if (chan->type == IIO_POSITIONRELATIVE) { ret = iio_device_claim_direct_mode(indio_dev); if (ret) return ret; mutex_lock(&st->lock); - st->chan = chan; + ret = at91_adc_read_position(st, chan->channel, + &tmp_val); + *val = tmp_val; + mutex_unlock(&st->lock); + iio_device_release_direct_mode(indio_dev); - if (chan->differential) - cor = (BIT(chan->channel) | BIT(chan->channel2)) << - AT91_SAMA5D2_COR_DIFF_OFFSET; - - at91_adc_writel(st, AT91_SAMA5D2_COR, cor); - at91_adc_writel(st, AT91_SAMA5D2_CHER, BIT(chan->channel)); - at91_adc_writel(st, AT91_SAMA5D2_IER, BIT(chan->channel)); - at91_adc_writel(st, AT91_SAMA5D2_CR, AT91_SAMA5D2_CR_START); - - ret = wait_event_interruptible_timeout(st->wq_data_available, - st->conversion_done, - msecs_to_jiffies(1000)); - if (ret == 0) - ret = -ETIMEDOUT; - - if (ret > 0) { - *val = st->conversion_value; - if (chan->scan_type.sign == 's') - *val = sign_extend32(*val, 11); - ret = IIO_VAL_INT; - st->conversion_done = false; - } + return at91_adc_adjust_val_osr(st, val); + } + if (chan->type == IIO_PRESSURE) { + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + mutex_lock(&st->lock); - at91_adc_writel(st, AT91_SAMA5D2_IDR, BIT(chan->channel)); - at91_adc_writel(st, AT91_SAMA5D2_CHDR, BIT(chan->channel)); + ret = at91_adc_read_pressure(st, chan->channel, + &tmp_val); + *val = tmp_val; + mutex_unlock(&st->lock); + iio_device_release_direct_mode(indio_dev); - /* Needed to ACK the DRDY interruption */ - at91_adc_readl(st, AT91_SAMA5D2_LCDR); + return at91_adc_adjust_val_osr(st, val); + } - mutex_unlock(&st->lock); + /* in this case we have a voltage channel */ - iio_device_release_direct_mode(indio_dev); + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) return ret; + mutex_lock(&st->lock); + + st->chan = chan; + + if (chan->differential) + cor = (BIT(chan->channel) | BIT(chan->channel2)) << + AT91_SAMA5D2_COR_DIFF_OFFSET; + + at91_adc_writel(st, AT91_SAMA5D2_COR, cor); + at91_adc_writel(st, AT91_SAMA5D2_CHER, BIT(chan->channel)); + at91_adc_writel(st, AT91_SAMA5D2_IER, BIT(chan->channel)); + at91_adc_writel(st, AT91_SAMA5D2_CR, AT91_SAMA5D2_CR_START); + + ret = wait_event_interruptible_timeout(st->wq_data_available, + st->conversion_done, + msecs_to_jiffies(1000)); + if (ret == 0) + ret = -ETIMEDOUT; + + if (ret > 0) { + *val = st->conversion_value; + ret = at91_adc_adjust_val_osr(st, val); + if (chan->scan_type.sign == 's') + *val = sign_extend32(*val, 11); + st->conversion_done = false; + } + at91_adc_writel(st, AT91_SAMA5D2_IDR, BIT(chan->channel)); + at91_adc_writel(st, AT91_SAMA5D2_CHDR, BIT(chan->channel)); + + /* Needed to ACK the DRDY interruption */ + at91_adc_readl(st, AT91_SAMA5D2_LCDR); + + mutex_unlock(&st->lock); + + iio_device_release_direct_mode(indio_dev); + return ret; +} + +static int at91_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return at91_adc_read_info_raw(indio_dev, chan, val); case IIO_CHAN_INFO_SCALE: *val = st->vref_uv / 1000; if (chan->differential) @@ -838,6 +1396,10 @@ static int at91_adc_read_raw(struct iio_dev *indio_dev, *val = at91_adc_get_sample_freq(st); return IIO_VAL_INT; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = st->oversampling_ratio; + return IIO_VAL_INT; + default: return -EINVAL; } @@ -849,16 +1411,28 @@ static int at91_adc_write_raw(struct iio_dev *indio_dev, { struct at91_adc_state *st = iio_priv(indio_dev); - if (mask != IIO_CHAN_INFO_SAMP_FREQ) - return -EINVAL; + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if ((val != AT91_OSR_1SAMPLES) && (val != AT91_OSR_4SAMPLES) && + (val != AT91_OSR_16SAMPLES)) + return -EINVAL; + /* if no change, optimize out */ + if (val == st->oversampling_ratio) + return 0; + st->oversampling_ratio = val; + /* update ratio */ + at91_adc_config_emr(st); + return 0; + case IIO_CHAN_INFO_SAMP_FREQ: + if (val < st->soc_info.min_sample_rate || + val > st->soc_info.max_sample_rate) + return -EINVAL; - if (val < st->soc_info.min_sample_rate || - val > st->soc_info.max_sample_rate) + at91_adc_setup_samp_freq(st, val); + return 0; + default: return -EINVAL; - - at91_adc_setup_samp_freq(st, val); - - return 0; + }; } static void at91_adc_dma_init(struct platform_device *pdev) @@ -974,11 +1548,23 @@ static int at91_adc_set_watermark(struct iio_dev *indio_dev, unsigned int val) return 0; } -static const struct iio_info at91_adc_info = { - .read_raw = &at91_adc_read_raw, - .write_raw = &at91_adc_write_raw, - .hwfifo_set_watermark = &at91_adc_set_watermark, -}; +static int at91_adc_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + if (bitmap_subset(scan_mask, &st->touch_st.channels_bitmask, + AT91_SAMA5D2_MAX_CHAN_IDX + 1)) + return 0; + /* + * if the new bitmap is a combination of touchscreen and regular + * channels, then we are not fine + */ + if (bitmap_intersects(&st->touch_st.channels_bitmask, scan_mask, + AT91_SAMA5D2_MAX_CHAN_IDX + 1)) + return -EINVAL; + return 0; +} static void at91_adc_hw_init(struct at91_adc_state *st) { @@ -992,6 +1578,9 @@ static void at91_adc_hw_init(struct at91_adc_state *st) AT91_SAMA5D2_MR_TRANSFER(2) | AT91_SAMA5D2_MR_ANACH); at91_adc_setup_samp_freq(st, st->soc_info.min_sample_rate); + + /* configure extended mode register */ + at91_adc_config_emr(st); } static ssize_t at91_adc_get_fifo_state(struct device *dev, @@ -1022,6 +1611,20 @@ static IIO_DEVICE_ATTR(hwfifo_watermark, 0444, static IIO_CONST_ATTR(hwfifo_watermark_min, "2"); static IIO_CONST_ATTR(hwfifo_watermark_max, AT91_HWFIFO_MAX_SIZE_STR); +static IIO_CONST_ATTR(oversampling_ratio_available, + __stringify(AT91_OSR_1SAMPLES) " " + __stringify(AT91_OSR_4SAMPLES) " " + __stringify(AT91_OSR_16SAMPLES)); + +static struct attribute *at91_adc_attributes[] = { + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group at91_adc_attribute_group = { + .attrs = at91_adc_attributes, +}; + static const struct attribute *at91_adc_fifo_attributes[] = { &iio_const_attr_hwfifo_watermark_min.dev_attr.attr, &iio_const_attr_hwfifo_watermark_max.dev_attr.attr, @@ -1030,6 +1633,15 @@ static const struct attribute *at91_adc_fifo_attributes[] = { NULL, }; +static const struct iio_info at91_adc_info = { + .attrs = &at91_adc_attribute_group, + .read_raw = &at91_adc_read_raw, + .write_raw = &at91_adc_write_raw, + .update_scan_mode = &at91_adc_update_scan_mode, + .of_xlate = &at91_adc_of_xlate, + .hwfifo_set_watermark = &at91_adc_set_watermark, +}; + static int at91_adc_probe(struct platform_device *pdev) { struct iio_dev *indio_dev; @@ -1044,13 +1656,22 @@ static int at91_adc_probe(struct platform_device *pdev) indio_dev->dev.parent = &pdev->dev; indio_dev->name = dev_name(&pdev->dev); - indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; indio_dev->info = &at91_adc_info; indio_dev->channels = at91_adc_channels; indio_dev->num_channels = ARRAY_SIZE(at91_adc_channels); st = iio_priv(indio_dev); + bitmap_set(&st->touch_st.channels_bitmask, + AT91_SAMA5D2_TOUCH_X_CHAN_IDX, 1); + bitmap_set(&st->touch_st.channels_bitmask, + AT91_SAMA5D2_TOUCH_Y_CHAN_IDX, 1); + bitmap_set(&st->touch_st.channels_bitmask, + AT91_SAMA5D2_TOUCH_P_CHAN_IDX, 1); + + st->oversampling_ratio = AT91_OSR_1SAMPLES; + ret = of_property_read_u32(pdev->dev.of_node, "atmel,min-sample-rate-hz", &st->soc_info.min_sample_rate); @@ -1100,6 +1721,7 @@ static int at91_adc_probe(struct platform_device *pdev) init_waitqueue_head(&st->wq_data_available); mutex_init(&st->lock); + INIT_WORK(&st->touch_st.workq, at91_adc_workq_handler); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) @@ -1159,13 +1781,13 @@ static int at91_adc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, indio_dev); - if (st->selected_trig->hw_trig) { - ret = at91_adc_buffer_init(indio_dev); - if (ret < 0) { - dev_err(&pdev->dev, "couldn't initialize the buffer.\n"); - goto per_clk_disable_unprepare; - } + ret = at91_adc_buffer_init(indio_dev); + if (ret < 0) { + dev_err(&pdev->dev, "couldn't initialize the buffer.\n"); + goto per_clk_disable_unprepare; + } + if (st->selected_trig->hw_trig) { ret = at91_adc_trigger_init(indio_dev); if (ret < 0) { dev_err(&pdev->dev, "couldn't setup the triggers.\n"); @@ -1272,9 +1894,20 @@ static __maybe_unused int at91_adc_resume(struct device *dev) at91_adc_hw_init(st); /* reconfiguring trigger hardware state */ - if (iio_buffer_enabled(indio_dev)) - at91_adc_configure_trigger(st->trig, true); + if (!iio_buffer_enabled(indio_dev)) + return 0; + + /* check if we are enabling triggered buffer or the touchscreen */ + if (bitmap_subset(indio_dev->active_scan_mask, + &st->touch_st.channels_bitmask, + AT91_SAMA5D2_MAX_CHAN_IDX + 1)) { + /* touchscreen enabling */ + return at91_adc_configure_touch(st, true); + } else { + return at91_adc_configure_trigger(st->trig, true); + } + /* not needed but more explicit */ return 0; vref_disable_resume: 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"; 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 <linux/module.h> #include <linux/of_device.h> #include <linux/regmap.h> +#include <linux/sched/task.h> #include <linux/util_macros.h> #include <linux/platform_data/ina2xx.h> @@ -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); + + get_task_struct(task); + wake_up_process(task); + chip->task = task; - return PTR_ERR_OR_ZERO(chip->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; } 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; 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", @@ -952,6 +957,10 @@ static const struct of_device_id meson_sar_adc_of_match[] = { .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, }, { 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 <linux/hwspinlock.h> +#include <linux/iio/iio.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +/* 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 <freeman.liu@spreadtrum.com>"); +MODULE_DESCRIPTION("Spreadtrum SC27XX ADC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-ads7950.c b/drivers/iio/adc/ti-ads7950.c index 0225c1b333ab..a5bd5944bc66 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 <linux/acpi.h> @@ -76,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 { @@ -295,18 +290,26 @@ out: 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; + + ret = be16_to_cpu(st->single_rx); - return be16_to_cpu(st->rx_buf[0]); +out: + mutex_unlock(&indio_dev->mlock); + + return ret; } static int ti_ads7950_get_range(struct ti_ads7950_state *st) @@ -338,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; @@ -410,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, diff --git a/drivers/iio/adc/xilinx-xadc-core.c b/drivers/iio/adc/xilinx-xadc-core.c index d4f21d1be6c8..3f6be5ac049a 100644 --- a/drivers/iio/adc/xilinx-xadc-core.c +++ b/drivers/iio/adc/xilinx-xadc-core.c @@ -322,6 +322,7 @@ static irqreturn_t xadc_zynq_interrupt_handler(int irq, void *devid) #define XADC_ZYNQ_TCK_RATE_MAX 50000000 #define XADC_ZYNQ_IGAP_DEFAULT 20 +#define XADC_ZYNQ_PCAP_RATE_MAX 200000000 static int xadc_zynq_setup(struct platform_device *pdev, struct iio_dev *indio_dev, int irq) @@ -332,6 +333,7 @@ static int xadc_zynq_setup(struct platform_device *pdev, unsigned int div; unsigned int igap; unsigned int tck_rate; + int ret; /* TODO: Figure out how to make igap and tck_rate configurable */ igap = XADC_ZYNQ_IGAP_DEFAULT; @@ -340,9 +342,16 @@ static int xadc_zynq_setup(struct platform_device *pdev, xadc->zynq_intmask = ~0; pcap_rate = clk_get_rate(xadc->clk); + if (!pcap_rate) + return -EINVAL; + + if (pcap_rate > XADC_ZYNQ_PCAP_RATE_MAX) { + ret = clk_set_rate(xadc->clk, + (unsigned long)XADC_ZYNQ_PCAP_RATE_MAX); + if (ret) + return ret; + } - if (tck_rate > XADC_ZYNQ_TCK_RATE_MAX) - tck_rate = XADC_ZYNQ_TCK_RATE_MAX; if (tck_rate > pcap_rate / 2) { div = 2; } else { @@ -368,6 +377,12 @@ static int xadc_zynq_setup(struct platform_device *pdev, XADC_ZYNQ_CFG_REDGE | XADC_ZYNQ_CFG_WEDGE | tck_div | XADC_ZYNQ_CFG_IGAP(igap)); + if (pcap_rate > XADC_ZYNQ_PCAP_RATE_MAX) { + ret = clk_set_rate(xadc->clk, pcap_rate); + if (ret) + return ret; + } + return 0; } @@ -889,6 +904,9 @@ static int xadc_write_raw(struct iio_dev *indio_dev, unsigned long clk_rate = xadc_get_dclk_rate(xadc); unsigned int div; + if (!clk_rate) + return -EINVAL; + if (info != IIO_CHAN_INFO_SAMP_FREQ) return -EINVAL; @@ -1045,7 +1063,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; @@ -1157,6 +1175,7 @@ static int xadc_probe(struct platform_device *pdev) xadc = iio_priv(indio_dev); xadc->ops = id->data; + xadc->irq = irq; init_completion(&xadc->completion); mutex_init(&xadc->mutex); spin_lock_init(&xadc->lock); @@ -1207,14 +1226,14 @@ static int xadc_probe(struct platform_device *pdev) if (ret) goto err_free_samplerate_trigger; - ret = xadc->ops->setup(pdev, indio_dev, irq); + ret = request_irq(xadc->irq, xadc->ops->interrupt_handler, 0, + dev_name(&pdev->dev), indio_dev); if (ret) goto err_clk_disable_unprepare; - ret = request_irq(irq, xadc->ops->interrupt_handler, 0, - dev_name(&pdev->dev), indio_dev); + ret = xadc->ops->setup(pdev, indio_dev, xadc->irq); if (ret) - goto err_clk_disable_unprepare; + goto err_free_irq; for (i = 0; i < 16; i++) xadc_read_adc_reg(xadc, XADC_REG_THRESHOLD(i), @@ -1239,8 +1258,10 @@ static int xadc_probe(struct platform_device *pdev) goto err_free_irq; /* Disable all alarms */ - xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF1_ALARM_MASK, - XADC_CONF1_ALARM_MASK); + ret = xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF1_ALARM_MASK, + XADC_CONF1_ALARM_MASK); + if (ret) + goto err_free_irq; /* Set thresholds to min/max */ for (i = 0; i < 16; i++) { @@ -1268,7 +1289,7 @@ static int xadc_probe(struct platform_device *pdev) return 0; err_free_irq: - free_irq(irq, indio_dev); + free_irq(xadc->irq, indio_dev); err_clk_disable_unprepare: clk_disable_unprepare(xadc->clk); err_free_samplerate_trigger: @@ -1290,7 +1311,6 @@ static int xadc_remove(struct platform_device *pdev) { struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct xadc *xadc = iio_priv(indio_dev); - int irq = platform_get_irq(pdev, 0); iio_device_unregister(indio_dev); if (xadc->ops->flags & XADC_FLAGS_BUFFERED) { @@ -1298,7 +1318,7 @@ static int xadc_remove(struct platform_device *pdev) iio_trigger_free(xadc->convst_trigger); iio_triggered_buffer_cleanup(indio_dev); } - free_irq(irq, indio_dev); + free_irq(xadc->irq, indio_dev); clk_disable_unprepare(xadc->clk); cancel_delayed_work(&xadc->zynq_unmask_work); kfree(xadc->data); diff --git a/drivers/iio/adc/xilinx-xadc.h b/drivers/iio/adc/xilinx-xadc.h index 62edbdae1244..8c0009585c16 100644 --- a/drivers/iio/adc/xilinx-xadc.h +++ b/drivers/iio/adc/xilinx-xadc.h @@ -68,6 +68,7 @@ struct xadc { spinlock_t lock; struct completion completion; + int irq; }; struct xadc_ops { diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig index 5cb5be7612b4..b8e005be4f87 100644 --- a/drivers/iio/chemical/Kconfig +++ b/drivers/iio/chemical/Kconfig @@ -21,6 +21,29 @@ config ATLAS_PH_SENSOR To compile this driver as module, choose M here: the module will be called atlas-ph-sensor. +config BME680 + tristate "Bosch Sensortec BME680 sensor driver" + depends on (I2C || SPI) + select REGMAP + select BME680_I2C if I2C + select BME680_SPI if SPI + help + Say yes here to build support for Bosch Sensortec BME680 sensor with + temperature, pressure, humidity and gas sensing capability. + + This driver can also be built as a module. If so, the module for I2C + would be called bme680_i2c and bme680_spi for SPI support. + +config BME680_I2C + tristate + depends on I2C && BME680 + select REGMAP_I2C + +config BME680_SPI + tristate + depends on SPI && BME680 + select REGMAP_SPI + config CCS811 tristate "AMS CCS811 VOC sensor" depends on I2C diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile index a629b29d1e0b..2f4c4ba4d781 100644 --- a/drivers/iio/chemical/Makefile +++ b/drivers/iio/chemical/Makefile @@ -4,6 +4,9 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_ATLAS_PH_SENSOR) += atlas-ph-sensor.o +obj-$(CONFIG_BME680) += bme680_core.o +obj-$(CONFIG_BME680_I2C) += bme680_i2c.o +obj-$(CONFIG_BME680_SPI) += bme680_spi.o obj-$(CONFIG_CCS811) += ccs811.o obj-$(CONFIG_IAQCORE) += ams-iaq-core.o obj-$(CONFIG_VZ89X) += vz89x.o diff --git a/drivers/iio/chemical/bme680.h b/drivers/iio/chemical/bme680.h new file mode 100644 index 000000000000..e049323f209a --- /dev/null +++ b/drivers/iio/chemical/bme680.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef BME680_H_ +#define BME680_H_ + +#define BME680_REG_CHIP_I2C_ID 0xD0 +#define BME680_REG_CHIP_SPI_ID 0x50 +#define BME680_CHIP_ID_VAL 0x61 +#define BME680_REG_SOFT_RESET_I2C 0xE0 +#define BME680_REG_SOFT_RESET_SPI 0x60 +#define BME680_CMD_SOFTRESET 0xB6 +#define BME680_REG_STATUS 0x73 +#define BME680_SPI_MEM_PAGE_BIT BIT(4) +#define BME680_SPI_MEM_PAGE_1_VAL 1 + +#define BME680_REG_TEMP_MSB 0x22 +#define BME680_REG_PRESS_MSB 0x1F +#define BM6880_REG_HUMIDITY_MSB 0x25 +#define BME680_REG_GAS_MSB 0x2A +#define BME680_REG_GAS_R_LSB 0x2B +#define BME680_GAS_STAB_BIT BIT(4) + +#define BME680_REG_CTRL_HUMIDITY 0x72 +#define BME680_OSRS_HUMIDITY_MASK GENMASK(2, 0) + +#define BME680_REG_CTRL_MEAS 0x74 +#define BME680_OSRS_TEMP_MASK GENMASK(7, 5) +#define BME680_OSRS_PRESS_MASK GENMASK(4, 2) +#define BME680_MODE_MASK GENMASK(1, 0) + +#define BME680_MODE_FORCED 1 +#define BME680_MODE_SLEEP 0 + +#define BME680_REG_CONFIG 0x75 +#define BME680_FILTER_MASK GENMASK(4, 2) +#define BME680_FILTER_COEFF_VAL BIT(1) + +/* TEMP/PRESS/HUMID reading skipped */ +#define BME680_MEAS_SKIPPED 0x8000 + +#define BME680_MAX_OVERFLOW_VAL 0x40000000 +#define BME680_HUM_REG_SHIFT_VAL 4 +#define BME680_BIT_H1_DATA_MSK 0x0F + +#define BME680_REG_RES_HEAT_RANGE 0x02 +#define BME680_RHRANGE_MSK 0x30 +#define BME680_REG_RES_HEAT_VAL 0x00 +#define BME680_REG_RANGE_SW_ERR 0x04 +#define BME680_RSERROR_MSK 0xF0 +#define BME680_REG_RES_HEAT_0 0x5A +#define BME680_REG_GAS_WAIT_0 0x64 +#define BME680_GAS_RANGE_MASK 0x0F +#define BME680_ADC_GAS_RES_SHIFT 6 +#define BME680_AMB_TEMP 25 + +#define BME680_REG_CTRL_GAS_1 0x71 +#define BME680_RUN_GAS_MASK BIT(4) +#define BME680_NB_CONV_MASK GENMASK(3, 0) +#define BME680_RUN_GAS_EN_BIT BIT(4) +#define BME680_NB_CONV_0_VAL 0 + +#define BME680_REG_MEAS_STAT_0 0x1D +#define BME680_GAS_MEAS_BIT BIT(6) + +/* Calibration Parameters */ +#define BME680_T2_LSB_REG 0x8A +#define BME680_T3_REG 0x8C +#define BME680_P1_LSB_REG 0x8E +#define BME680_P2_LSB_REG 0x90 +#define BME680_P3_REG 0x92 +#define BME680_P4_LSB_REG 0x94 +#define BME680_P5_LSB_REG 0x96 +#define BME680_P7_REG 0x98 +#define BME680_P6_REG 0x99 +#define BME680_P8_LSB_REG 0x9C +#define BME680_P9_LSB_REG 0x9E +#define BME680_P10_REG 0xA0 +#define BME680_H2_LSB_REG 0xE2 +#define BME680_H2_MSB_REG 0xE1 +#define BME680_H1_MSB_REG 0xE3 +#define BME680_H1_LSB_REG 0xE2 +#define BME680_H3_REG 0xE4 +#define BME680_H4_REG 0xE5 +#define BME680_H5_REG 0xE6 +#define BME680_H6_REG 0xE7 +#define BME680_H7_REG 0xE8 +#define BME680_T1_LSB_REG 0xE9 +#define BME680_GH2_LSB_REG 0xEB +#define BME680_GH1_REG 0xED +#define BME680_GH3_REG 0xEE + +extern const struct regmap_config bme680_regmap_config; + +int bme680_core_probe(struct device *dev, struct regmap *regmap, + const char *name); + +#endif /* BME680_H_ */ diff --git a/drivers/iio/chemical/bme680_core.c b/drivers/iio/chemical/bme680_core.c new file mode 100644 index 000000000000..7d9bb62baa3f --- /dev/null +++ b/drivers/iio/chemical/bme680_core.c @@ -0,0 +1,959 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Bosch BME680 - Temperature, Pressure, Humidity & Gas Sensor + * + * Copyright (C) 2017 - 2018 Bosch Sensortec GmbH + * Copyright (C) 2018 Himanshu Jha <himanshujha199640@gmail.com> + * + * Datasheet: + * https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME680-DS001-00.pdf + */ +#include <linux/acpi.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/log2.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include "bme680.h" + +struct bme680_calib { + u16 par_t1; + s16 par_t2; + s8 par_t3; + u16 par_p1; + s16 par_p2; + s8 par_p3; + s16 par_p4; + s16 par_p5; + s8 par_p6; + s8 par_p7; + s16 par_p8; + s16 par_p9; + u8 par_p10; + u16 par_h1; + u16 par_h2; + s8 par_h3; + s8 par_h4; + s8 par_h5; + s8 par_h6; + s8 par_h7; + s8 par_gh1; + s16 par_gh2; + s8 par_gh3; + u8 res_heat_range; + s8 res_heat_val; + s8 range_sw_err; +}; + +struct bme680_data { + struct regmap *regmap; + struct bme680_calib bme680; + u8 oversampling_temp; + u8 oversampling_press; + u8 oversampling_humid; + u16 heater_dur; + u16 heater_temp; + /* + * Carryover value from temperature conversion, used in pressure + * and humidity compensation calculations. + */ + s32 t_fine; +}; + +const struct regmap_config bme680_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; +EXPORT_SYMBOL(bme680_regmap_config); + +static const struct iio_chan_spec bme680_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, + { + .type = IIO_RESISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static const int bme680_oversampling_avail[] = { 1, 2, 4, 8, 16 }; + +static int bme680_read_calib(struct bme680_data *data, + struct bme680_calib *calib) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int tmp, tmp_msb, tmp_lsb; + int ret; + __le16 buf; + + /* Temperature related coefficients */ + ret = regmap_bulk_read(data->regmap, BME680_T1_LSB_REG, + (u8 *) &buf, 2); + if (ret < 0) { + dev_err(dev, "failed to read BME680_T1_LSB_REG\n"); + return ret; + } + calib->par_t1 = le16_to_cpu(buf); + + ret = regmap_bulk_read(data->regmap, BME680_T2_LSB_REG, + (u8 *) &buf, 2); + if (ret < 0) { + dev_err(dev, "failed to read BME680_T2_LSB_REG\n"); + return ret; + } + calib->par_t2 = le16_to_cpu(buf); + + ret = regmap_read(data->regmap, BME680_T3_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_T3_REG\n"); + return ret; + } + calib->par_t3 = tmp; + + /* Pressure related coefficients */ + ret = regmap_bulk_read(data->regmap, BME680_P1_LSB_REG, + (u8 *) &buf, 2); + if (ret < 0) { + dev_err(dev, "failed to read BME680_P1_LSB_REG\n"); + return ret; + } + calib->par_p1 = le16_to_cpu(buf); + + ret = regmap_bulk_read(data->regmap, BME680_P2_LSB_REG, + (u8 *) &buf, 2); + if (ret < 0) { + dev_err(dev, "failed to read BME680_P2_LSB_REG\n"); + return ret; + } + calib->par_p2 = le16_to_cpu(buf); + + ret = regmap_read(data->regmap, BME680_P3_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_P3_REG\n"); + return ret; + } + calib->par_p3 = tmp; + + ret = regmap_bulk_read(data->regmap, BME680_P4_LSB_REG, + (u8 *) &buf, 2); + if (ret < 0) { + dev_err(dev, "failed to read BME680_P4_LSB_REG\n"); + return ret; + } + calib->par_p4 = le16_to_cpu(buf); + + ret = regmap_bulk_read(data->regmap, BME680_P5_LSB_REG, + (u8 *) &buf, 2); + if (ret < 0) { + dev_err(dev, "failed to read BME680_P5_LSB_REG\n"); + return ret; + } + calib->par_p5 = le16_to_cpu(buf); + + ret = regmap_read(data->regmap, BME680_P6_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_P6_REG\n"); + return ret; + } + calib->par_p6 = tmp; + + ret = regmap_read(data->regmap, BME680_P7_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_P7_REG\n"); + return ret; + } + calib->par_p7 = tmp; + + ret = regmap_bulk_read(data->regmap, BME680_P8_LSB_REG, + (u8 *) &buf, 2); + if (ret < 0) { + dev_err(dev, "failed to read BME680_P8_LSB_REG\n"); + return ret; + } + calib->par_p8 = le16_to_cpu(buf); + + ret = regmap_bulk_read(data->regmap, BME680_P9_LSB_REG, + (u8 *) &buf, 2); + if (ret < 0) { + dev_err(dev, "failed to read BME680_P9_LSB_REG\n"); + return ret; + } + calib->par_p9 = le16_to_cpu(buf); + + ret = regmap_read(data->regmap, BME680_P10_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_P10_REG\n"); + return ret; + } + calib->par_p10 = tmp; + + /* Humidity related coefficients */ + ret = regmap_read(data->regmap, BME680_H1_MSB_REG, &tmp_msb); + if (ret < 0) { + dev_err(dev, "failed to read BME680_H1_MSB_REG\n"); + return ret; + } + + ret = regmap_read(data->regmap, BME680_H1_LSB_REG, &tmp_lsb); + if (ret < 0) { + dev_err(dev, "failed to read BME680_H1_LSB_REG\n"); + return ret; + } + + calib->par_h1 = (tmp_msb << BME680_HUM_REG_SHIFT_VAL) | + (tmp_lsb & BME680_BIT_H1_DATA_MSK); + + ret = regmap_read(data->regmap, BME680_H2_MSB_REG, &tmp_msb); + if (ret < 0) { + dev_err(dev, "failed to read BME680_H2_MSB_REG\n"); + return ret; + } + + ret = regmap_read(data->regmap, BME680_H2_LSB_REG, &tmp_lsb); + if (ret < 0) { + dev_err(dev, "failed to read BME680_H2_LSB_REG\n"); + return ret; + } + + calib->par_h2 = (tmp_msb << BME680_HUM_REG_SHIFT_VAL) | + (tmp_lsb >> BME680_HUM_REG_SHIFT_VAL); + + ret = regmap_read(data->regmap, BME680_H3_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_H3_REG\n"); + return ret; + } + calib->par_h3 = tmp; + + ret = regmap_read(data->regmap, BME680_H4_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_H4_REG\n"); + return ret; + } + calib->par_h4 = tmp; + + ret = regmap_read(data->regmap, BME680_H5_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_H5_REG\n"); + return ret; + } + calib->par_h5 = tmp; + + ret = regmap_read(data->regmap, BME680_H6_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_H6_REG\n"); + return ret; + } + calib->par_h6 = tmp; + + ret = regmap_read(data->regmap, BME680_H7_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_H7_REG\n"); + return ret; + } + calib->par_h7 = tmp; + + /* Gas heater related coefficients */ + ret = regmap_read(data->regmap, BME680_GH1_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_GH1_REG\n"); + return ret; + } + calib->par_gh1 = tmp; + + ret = regmap_bulk_read(data->regmap, BME680_GH2_LSB_REG, + (u8 *) &buf, 2); + if (ret < 0) { + dev_err(dev, "failed to read BME680_GH2_LSB_REG\n"); + return ret; + } + calib->par_gh2 = le16_to_cpu(buf); + + ret = regmap_read(data->regmap, BME680_GH3_REG, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read BME680_GH3_REG\n"); + return ret; + } + calib->par_gh3 = tmp; + + /* Other coefficients */ + ret = regmap_read(data->regmap, BME680_REG_RES_HEAT_RANGE, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read resistance heat range\n"); + return ret; + } + calib->res_heat_range = (tmp & BME680_RHRANGE_MSK) / 16; + + ret = regmap_read(data->regmap, BME680_REG_RES_HEAT_VAL, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read resistance heat value\n"); + return ret; + } + calib->res_heat_val = tmp; + + ret = regmap_read(data->regmap, BME680_REG_RANGE_SW_ERR, &tmp); + if (ret < 0) { + dev_err(dev, "failed to read range software error\n"); + return ret; + } + calib->range_sw_err = (tmp & BME680_RSERROR_MSK) / 16; + + return 0; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L876 + * + * Returns temperature measurement in DegC, resolutions is 0.01 DegC. Therefore, + * output value of "3233" represents 32.33 DegC. + */ +static s16 bme680_compensate_temp(struct bme680_data *data, + s32 adc_temp) +{ + struct bme680_calib *calib = &data->bme680; + s64 var1, var2, var3; + s16 calc_temp; + + var1 = (adc_temp >> 3) - (calib->par_t1 << 1); + var2 = (var1 * calib->par_t2) >> 11; + var3 = ((var1 >> 1) * (var1 >> 1)) >> 12; + var3 = (var3 * (calib->par_t3 << 4)) >> 14; + data->t_fine = var2 + var3; + calc_temp = (data->t_fine * 5 + 128) >> 8; + + return calc_temp; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L896 + * + * Returns pressure measurement in Pa. Output value of "97356" represents + * 97356 Pa = 973.56 hPa. + */ +static u32 bme680_compensate_press(struct bme680_data *data, + u32 adc_press) +{ + struct bme680_calib *calib = &data->bme680; + s32 var1, var2, var3, press_comp; + + var1 = (data->t_fine >> 1) - 64000; + var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * calib->par_p6) >> 2; + var2 = var2 + (var1 * calib->par_p5 << 1); + var2 = (var2 >> 2) + (calib->par_p4 << 16); + var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * + (calib->par_p3 << 5)) >> 3) + + ((calib->par_p2 * var1) >> 1); + var1 = var1 >> 18; + var1 = ((32768 + var1) * calib->par_p1) >> 15; + press_comp = 1048576 - adc_press; + press_comp = ((press_comp - (var2 >> 12)) * 3125); + + if (press_comp >= BME680_MAX_OVERFLOW_VAL) + press_comp = ((press_comp / (u32)var1) << 1); + else + press_comp = ((press_comp << 1) / (u32)var1); + + var1 = (calib->par_p9 * (((press_comp >> 3) * + (press_comp >> 3)) >> 13)) >> 12; + var2 = ((press_comp >> 2) * calib->par_p8) >> 13; + var3 = ((press_comp >> 8) * (press_comp >> 8) * + (press_comp >> 8) * calib->par_p10) >> 17; + + press_comp += (var1 + var2 + var3 + (calib->par_p7 << 7)) >> 4; + + return press_comp; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L937 + * + * Returns humidity measurement in percent, resolution is 0.001 percent. Output + * value of "43215" represents 43.215 %rH. + */ +static u32 bme680_compensate_humid(struct bme680_data *data, + u16 adc_humid) +{ + struct bme680_calib *calib = &data->bme680; + s32 var1, var2, var3, var4, var5, var6, temp_scaled, calc_hum; + + temp_scaled = (data->t_fine * 5 + 128) >> 8; + var1 = (adc_humid - ((s32) ((s32) calib->par_h1 * 16))) - + (((temp_scaled * (s32) calib->par_h3) / 100) >> 1); + var2 = ((s32) calib->par_h2 * + (((temp_scaled * calib->par_h4) / 100) + + (((temp_scaled * ((temp_scaled * calib->par_h5) / 100)) + >> 6) / 100) + (1 << 14))) >> 10; + var3 = var1 * var2; + var4 = calib->par_h6 << 7; + var4 = (var4 + ((temp_scaled * calib->par_h7) / 100)) >> 4; + var5 = ((var3 >> 14) * (var3 >> 14)) >> 10; + var6 = (var4 * var5) >> 1; + calc_hum = (((var3 + var6) >> 10) * 1000) >> 12; + + if (calc_hum > 100000) /* Cap at 100%rH */ + calc_hum = 100000; + else if (calc_hum < 0) + calc_hum = 0; + + return calc_hum; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L973 + * + * Returns gas measurement in Ohm. Output value of "82986" represent 82986 ohms. + */ +static u32 bme680_compensate_gas(struct bme680_data *data, u16 gas_res_adc, + u8 gas_range) +{ + struct bme680_calib *calib = &data->bme680; + s64 var1; + u64 var2; + s64 var3; + u32 calc_gas_res; + + /* Look up table for the possible gas range values */ + const u32 lookupTable[16] = {2147483647u, 2147483647u, + 2147483647u, 2147483647u, 2147483647u, + 2126008810u, 2147483647u, 2130303777u, + 2147483647u, 2147483647u, 2143188679u, + 2136746228u, 2147483647u, 2126008810u, + 2147483647u, 2147483647u}; + + var1 = ((1340 + (5 * (s64) calib->range_sw_err)) * + ((s64) lookupTable[gas_range])) >> 16; + var2 = ((gas_res_adc << 15) - 16777216) + var1; + var3 = ((125000 << (15 - gas_range)) * var1) >> 9; + var3 += (var2 >> 1); + calc_gas_res = div64_s64(var3, (s64) var2); + + return calc_gas_res; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L1002 + */ +static u8 bme680_calc_heater_res(struct bme680_data *data, u16 temp) +{ + struct bme680_calib *calib = &data->bme680; + s32 var1, var2, var3, var4, var5, heatr_res_x100; + u8 heatr_res; + + if (temp > 400) /* Cap temperature */ + temp = 400; + + var1 = (((s32) BME680_AMB_TEMP * calib->par_gh3) / 1000) * 256; + var2 = (calib->par_gh1 + 784) * (((((calib->par_gh2 + 154009) * + temp * 5) / 100) + + 3276800) / 10); + var3 = var1 + (var2 / 2); + var4 = (var3 / (calib->res_heat_range + 4)); + var5 = 131 * calib->res_heat_val + 65536; + heatr_res_x100 = ((var4 / var5) - 250) * 34; + heatr_res = (heatr_res_x100 + 50) / 100; + + return heatr_res; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L1188 + */ +static u8 bme680_calc_heater_dur(u16 dur) +{ + u8 durval, factor = 0; + + if (dur >= 0xfc0) { + durval = 0xff; /* Max duration */ + } else { + while (dur > 0x3F) { + dur = dur / 4; + factor += 1; + } + durval = dur + (factor * 64); + } + + return durval; +} + +static int bme680_set_mode(struct bme680_data *data, bool mode) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + if (mode) { + ret = regmap_write_bits(data->regmap, BME680_REG_CTRL_MEAS, + BME680_MODE_MASK, BME680_MODE_FORCED); + if (ret < 0) + dev_err(dev, "failed to set forced mode\n"); + + } else { + ret = regmap_write_bits(data->regmap, BME680_REG_CTRL_MEAS, + BME680_MODE_MASK, BME680_MODE_SLEEP); + if (ret < 0) + dev_err(dev, "failed to set sleep mode\n"); + + } + + return ret; +} + +static int bme680_chip_config(struct bme680_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + u8 osrs = FIELD_PREP(BME680_OSRS_HUMIDITY_MASK, + data->oversampling_humid + 1); + /* + * Highly recommended to set oversampling of humidity before + * temperature/pressure oversampling. + */ + ret = regmap_update_bits(data->regmap, BME680_REG_CTRL_HUMIDITY, + BME680_OSRS_HUMIDITY_MASK, osrs); + if (ret < 0) { + dev_err(dev, "failed to write ctrl_hum register\n"); + return ret; + } + + /* IIR filter settings */ + ret = regmap_update_bits(data->regmap, BME680_REG_CONFIG, + BME680_FILTER_MASK, + BME680_FILTER_COEFF_VAL); + if (ret < 0) { + dev_err(dev, "failed to write config register\n"); + return ret; + } + + osrs = FIELD_PREP(BME680_OSRS_TEMP_MASK, data->oversampling_temp + 1) | + FIELD_PREP(BME680_OSRS_PRESS_MASK, data->oversampling_press + 1); + + ret = regmap_write_bits(data->regmap, BME680_REG_CTRL_MEAS, + BME680_OSRS_TEMP_MASK | + BME680_OSRS_PRESS_MASK, + osrs); + if (ret < 0) + dev_err(dev, "failed to write ctrl_meas register\n"); + + return ret; +} + +static int bme680_gas_config(struct bme680_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + u8 heatr_res, heatr_dur; + + heatr_res = bme680_calc_heater_res(data, data->heater_temp); + + /* set target heater temperature */ + ret = regmap_write(data->regmap, BME680_REG_RES_HEAT_0, heatr_res); + if (ret < 0) { + dev_err(dev, "failed to write res_heat_0 register\n"); + return ret; + } + + heatr_dur = bme680_calc_heater_dur(data->heater_dur); + + /* set target heating duration */ + ret = regmap_write(data->regmap, BME680_REG_GAS_WAIT_0, heatr_dur); + if (ret < 0) { + dev_err(dev, "failted to write gas_wait_0 register\n"); + return ret; + } + + /* Selecting the runGas and NB conversion settings for the sensor */ + ret = regmap_update_bits(data->regmap, BME680_REG_CTRL_GAS_1, + BME680_RUN_GAS_MASK | BME680_NB_CONV_MASK, + BME680_RUN_GAS_EN_BIT | BME680_NB_CONV_0_VAL); + if (ret < 0) + dev_err(dev, "failed to write ctrl_gas_1 register\n"); + + return ret; +} + +static int bme680_read_temp(struct bme680_data *data, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + __be32 tmp = 0; + s32 adc_temp; + s16 comp_temp; + + /* set forced mode to trigger measurement */ + ret = bme680_set_mode(data, true); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, BME680_REG_TEMP_MSB, + (u8 *) &tmp, 3); + if (ret < 0) { + dev_err(dev, "failed to read temperature\n"); + return ret; + } + + adc_temp = be32_to_cpu(tmp) >> 12; + if (adc_temp == BME680_MEAS_SKIPPED) { + /* reading was skipped */ + dev_err(dev, "reading temperature skipped\n"); + return -EINVAL; + } + comp_temp = bme680_compensate_temp(data, adc_temp); + /* + * val might be NULL if we're called by the read_press/read_humid + * routine which is callled to get t_fine value used in + * compensate_press/compensate_humid to get compensated + * pressure/humidity readings. + */ + if (val && val2) { + *val = comp_temp; + *val2 = 100; + return IIO_VAL_FRACTIONAL; + } + + return ret; +} + +static int bme680_read_press(struct bme680_data *data, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + __be32 tmp = 0; + s32 adc_press; + + /* Read and compensate temperature to get a reading of t_fine */ + ret = bme680_read_temp(data, NULL, NULL); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, BME680_REG_PRESS_MSB, + (u8 *) &tmp, 3); + if (ret < 0) { + dev_err(dev, "failed to read pressure\n"); + return ret; + } + + adc_press = be32_to_cpu(tmp) >> 12; + if (adc_press == BME680_MEAS_SKIPPED) { + /* reading was skipped */ + dev_err(dev, "reading pressure skipped\n"); + return -EINVAL; + } + + *val = bme680_compensate_press(data, adc_press); + *val2 = 100; + return IIO_VAL_FRACTIONAL; +} + +static int bme680_read_humid(struct bme680_data *data, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + __be16 tmp = 0; + s32 adc_humidity; + u32 comp_humidity; + + /* Read and compensate temperature to get a reading of t_fine */ + ret = bme680_read_temp(data, NULL, NULL); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, BM6880_REG_HUMIDITY_MSB, + (u8 *) &tmp, 2); + if (ret < 0) { + dev_err(dev, "failed to read humidity\n"); + return ret; + } + + adc_humidity = be16_to_cpu(tmp); + if (adc_humidity == BME680_MEAS_SKIPPED) { + /* reading was skipped */ + dev_err(dev, "reading humidity skipped\n"); + return -EINVAL; + } + comp_humidity = bme680_compensate_humid(data, adc_humidity); + + *val = comp_humidity; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; +} + +static int bme680_read_gas(struct bme680_data *data, + int *val) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + __be16 tmp = 0; + unsigned int check; + u16 adc_gas_res; + u8 gas_range; + + /* Set heater settings */ + ret = bme680_gas_config(data); + if (ret < 0) { + dev_err(dev, "failed to set gas config\n"); + return ret; + } + + /* set forced mode to trigger measurement */ + ret = bme680_set_mode(data, true); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, BME680_REG_MEAS_STAT_0, &check); + if (check & BME680_GAS_MEAS_BIT) { + dev_err(dev, "gas measurement incomplete\n"); + return -EBUSY; + } + + ret = regmap_read(data->regmap, BME680_REG_GAS_R_LSB, &check); + if (ret < 0) { + dev_err(dev, "failed to read gas_r_lsb register\n"); + return ret; + } + + /* + * occurs if either the gas heating duration was insuffient + * to reach the target heater temperature or the target + * heater temperature was too high for the heater sink to + * reach. + */ + if ((check & BME680_GAS_STAB_BIT) == 0) { + dev_err(dev, "heater failed to reach the target temperature\n"); + return -EINVAL; + } + + ret = regmap_bulk_read(data->regmap, BME680_REG_GAS_MSB, + (u8 *) &tmp, 2); + if (ret < 0) { + dev_err(dev, "failed to read gas resistance\n"); + return ret; + } + + gas_range = check & BME680_GAS_RANGE_MASK; + adc_gas_res = be16_to_cpu(tmp) >> BME680_ADC_GAS_RES_SHIFT; + + *val = bme680_compensate_gas(data, adc_gas_res, gas_range); + return IIO_VAL_INT; +} + +static int bme680_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bme680_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_TEMP: + return bme680_read_temp(data, val, val2); + case IIO_PRESSURE: + return bme680_read_press(data, val, val2); + case IIO_HUMIDITYRELATIVE: + return bme680_read_humid(data, val, val2); + case IIO_RESISTANCE: + return bme680_read_gas(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_TEMP: + *val = 1 << data->oversampling_temp; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = 1 << data->oversampling_press; + return IIO_VAL_INT; + case IIO_HUMIDITYRELATIVE: + *val = 1 << data->oversampling_humid; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bme680_write_oversampling_ratio_temp(struct bme680_data *data, + int val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bme680_oversampling_avail); i++) { + if (bme680_oversampling_avail[i] == val) { + data->oversampling_temp = ilog2(val); + + return bme680_chip_config(data); + } + } + + return -EINVAL; +} + +static int bme680_write_oversampling_ratio_press(struct bme680_data *data, + int val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bme680_oversampling_avail); i++) { + if (bme680_oversampling_avail[i] == val) { + data->oversampling_press = ilog2(val); + + return bme680_chip_config(data); + } + } + + return -EINVAL; +} + +static int bme680_write_oversampling_ratio_humid(struct bme680_data *data, + int val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bme680_oversampling_avail); i++) { + if (bme680_oversampling_avail[i] == val) { + data->oversampling_humid = ilog2(val); + + return bme680_chip_config(data); + } + } + + return -EINVAL; +} + +static int bme680_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bme680_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_TEMP: + return bme680_write_oversampling_ratio_temp(data, val); + case IIO_PRESSURE: + return bme680_write_oversampling_ratio_press(data, val); + case IIO_HUMIDITYRELATIVE: + return bme680_write_oversampling_ratio_humid(data, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const char bme680_oversampling_ratio_show[] = "1 2 4 8 16"; + +static IIO_CONST_ATTR(oversampling_ratio_available, + bme680_oversampling_ratio_show); + +static struct attribute *bme680_attributes[] = { + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bme680_attribute_group = { + .attrs = bme680_attributes, +}; + +static const struct iio_info bme680_info = { + .read_raw = &bme680_read_raw, + .write_raw = &bme680_write_raw, + .attrs = &bme680_attribute_group, +}; + +static const char *bme680_match_acpi_device(struct device *dev) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + + return dev_name(dev); +} + +int bme680_core_probe(struct device *dev, struct regmap *regmap, + const char *name) +{ + struct iio_dev *indio_dev; + struct bme680_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + if (!name && ACPI_HANDLE(dev)) + name = bme680_match_acpi_device(dev); + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + indio_dev->dev.parent = dev; + indio_dev->name = name; + indio_dev->channels = bme680_channels; + indio_dev->num_channels = ARRAY_SIZE(bme680_channels); + indio_dev->info = &bme680_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* default values for the sensor */ + data->oversampling_humid = ilog2(2); /* 2X oversampling rate */ + data->oversampling_press = ilog2(4); /* 4X oversampling rate */ + data->oversampling_temp = ilog2(8); /* 8X oversampling rate */ + data->heater_temp = 320; /* degree Celsius */ + data->heater_dur = 150; /* milliseconds */ + + ret = bme680_chip_config(data); + if (ret < 0) { + dev_err(dev, "failed to set chip_config data\n"); + return ret; + } + + ret = bme680_gas_config(data); + if (ret < 0) { + dev_err(dev, "failed to set gas config data\n"); + return ret; + } + + ret = bme680_read_calib(data, &data->bme680); + if (ret < 0) { + dev_err(dev, + "failed to read calibration coefficients at probe\n"); + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_GPL(bme680_core_probe); + +MODULE_AUTHOR("Himanshu Jha <himanshujha199640@gmail.com>"); +MODULE_DESCRIPTION("Bosch BME680 Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/bme680_i2c.c b/drivers/iio/chemical/bme680_i2c.c new file mode 100644 index 000000000000..06d4be539d2e --- /dev/null +++ b/drivers/iio/chemical/bme680_i2c.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BME680 - I2C Driver + * + * Copyright (C) 2018 Himanshu Jha <himanshujha199640@gmail.com> + * + * 7-Bit I2C slave address is: + * - 0x76 if SDO is pulled to GND + * - 0x77 if SDO is pulled to VDDIO + * + * Note: SDO pin cannot be left floating otherwise I2C address + * will be undefined. + */ +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "bme680.h" + +static int bme680_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + const char *name = NULL; + unsigned int val; + int ret; + + regmap = devm_regmap_init_i2c(client, &bme680_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap %d\n", + (int)PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + ret = regmap_write(regmap, BME680_REG_SOFT_RESET_I2C, + BME680_CMD_SOFTRESET); + if (ret < 0) { + dev_err(&client->dev, "Failed to reset chip\n"); + return ret; + } + + ret = regmap_read(regmap, BME680_REG_CHIP_I2C_ID, &val); + if (ret < 0) { + dev_err(&client->dev, "Error reading I2C chip ID\n"); + return ret; + } + + if (val != BME680_CHIP_ID_VAL) { + dev_err(&client->dev, "Wrong chip ID, got %x expected %x\n", + val, BME680_CHIP_ID_VAL); + return -ENODEV; + } + + if (id) + name = id->name; + + return bme680_core_probe(&client->dev, regmap, name); +} + +static const struct i2c_device_id bme680_i2c_id[] = { + {"bme680", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bme680_i2c_id); + +static const struct acpi_device_id bme680_acpi_match[] = { + {"BME0680", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, bme680_acpi_match); + +static struct i2c_driver bme680_i2c_driver = { + .driver = { + .name = "bme680_i2c", + .acpi_match_table = ACPI_PTR(bme680_acpi_match), + }, + .probe = bme680_i2c_probe, + .id_table = bme680_i2c_id, +}; +module_i2c_driver(bme680_i2c_driver); + +MODULE_AUTHOR("Himanshu Jha <himanshujha199640@gmail.com>"); +MODULE_DESCRIPTION("BME680 I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/bme680_spi.c b/drivers/iio/chemical/bme680_spi.c new file mode 100644 index 000000000000..c9fb05e8d0b9 --- /dev/null +++ b/drivers/iio/chemical/bme680_spi.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BME680 - SPI Driver + * + * Copyright (C) 2018 Himanshu Jha <himanshujha199640@gmail.com> + */ +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> + +#include "bme680.h" + +static int bme680_regmap_spi_write(void *context, const void *data, + size_t count) +{ + struct spi_device *spi = context; + u8 buf[2]; + + memcpy(buf, data, 2); + /* + * The SPI register address (= full register address without bit 7) + * and the write command (bit7 = RW = '0') + */ + buf[0] &= ~0x80; + + return spi_write_then_read(spi, buf, 2, NULL, 0); +} + +static int bme680_regmap_spi_read(void *context, const void *reg, + size_t reg_size, void *val, size_t val_size) +{ + struct spi_device *spi = context; + + return spi_write_then_read(spi, reg, reg_size, val, val_size); +} + +static struct regmap_bus bme680_regmap_bus = { + .write = bme680_regmap_spi_write, + .read = bme680_regmap_spi_read, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static int bme680_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + unsigned int val; + int ret; + + spi->bits_per_word = 8; + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "spi_setup failed!\n"); + return ret; + } + + regmap = devm_regmap_init(&spi->dev, &bme680_regmap_bus, + &spi->dev, &bme680_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap %d\n", + (int)PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + ret = regmap_write(regmap, BME680_REG_SOFT_RESET_SPI, + BME680_CMD_SOFTRESET); + if (ret < 0) { + dev_err(&spi->dev, "Failed to reset chip\n"); + return ret; + } + + /* after power-on reset, Page 0(0x80-0xFF) of spi_mem_page is active */ + ret = regmap_read(regmap, BME680_REG_CHIP_SPI_ID, &val); + if (ret < 0) { + dev_err(&spi->dev, "Error reading SPI chip ID\n"); + return ret; + } + + if (val != BME680_CHIP_ID_VAL) { + dev_err(&spi->dev, "Wrong chip ID, got %x expected %x\n", + val, BME680_CHIP_ID_VAL); + return -ENODEV; + } + /* + * select Page 1 of spi_mem_page to enable access to + * to registers from address 0x00 to 0x7F. + */ + ret = regmap_write_bits(regmap, BME680_REG_STATUS, + BME680_SPI_MEM_PAGE_BIT, + BME680_SPI_MEM_PAGE_1_VAL); + if (ret < 0) { + dev_err(&spi->dev, "failed to set page 1 of spi_mem_page\n"); + return ret; + } + + return bme680_core_probe(&spi->dev, regmap, id->name); +} + +static const struct spi_device_id bme680_spi_id[] = { + {"bme680", 0}, + {}, +}; +MODULE_DEVICE_TABLE(spi, bme680_spi_id); + +static const struct acpi_device_id bme680_acpi_match[] = { + {"BME0680", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, bme680_acpi_match); + +static struct spi_driver bme680_spi_driver = { + .driver = { + .name = "bme680_spi", + .acpi_match_table = ACPI_PTR(bme680_acpi_match), + }, + .probe = bme680_spi_probe, + .id_table = bme680_spi_id, +}; +module_spi_driver(bme680_spi_driver); + +MODULE_AUTHOR("Himanshu Jha <himanshujha199640@gmail.com>"); +MODULE_DESCRIPTION("Bosch BME680 SPI driver"); +MODULE_LICENSE("GPL v2"); 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/counter/104-quad-8.c b/drivers/iio/counter/104-quad-8.c index b56985078d8c..92be8d0f7735 100644 --- a/drivers/iio/counter/104-quad-8.c +++ b/drivers/iio/counter/104-quad-8.c @@ -59,6 +59,39 @@ struct quad8_iio { unsigned int base; }; +#define QUAD8_REG_CHAN_OP 0x11 +#define QUAD8_REG_INDEX_INPUT_LEVELS 0x16 +/* Borrow Toggle flip-flop */ +#define QUAD8_FLAG_BT BIT(0) +/* Carry Toggle flip-flop */ +#define QUAD8_FLAG_CT BIT(1) +/* Error flag */ +#define QUAD8_FLAG_E BIT(4) +/* Up/Down flag */ +#define QUAD8_FLAG_UD BIT(5) +/* Reset and Load Signal Decoders */ +#define QUAD8_CTR_RLD 0x00 +/* Counter Mode Register */ +#define QUAD8_CTR_CMR 0x20 +/* Input / Output Control Register */ +#define QUAD8_CTR_IOR 0x40 +/* Index Control Register */ +#define QUAD8_CTR_IDR 0x60 +/* Reset Byte Pointer (three byte data pointer) */ +#define QUAD8_RLD_RESET_BP 0x01 +/* Reset Counter */ +#define QUAD8_RLD_RESET_CNTR 0x02 +/* Reset Borrow Toggle, Carry Toggle, Compare Toggle, and Sign flags */ +#define QUAD8_RLD_RESET_FLAGS 0x04 +/* Reset Error flag */ +#define QUAD8_RLD_RESET_E 0x06 +/* Preset Register to Counter */ +#define QUAD8_RLD_PRESET_CNTR 0x08 +/* Transfer Counter to Output Latch */ +#define QUAD8_RLD_CNTR_OUT 0x10 +#define QUAD8_CHAN_OP_ENABLE_COUNTERS 0x00 +#define QUAD8_CHAN_OP_RESET_COUNTERS 0x01 + static int quad8_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { @@ -72,19 +105,21 @@ static int quad8_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: if (chan->type == IIO_INDEX) { - *val = !!(inb(priv->base + 0x16) & BIT(chan->channel)); + *val = !!(inb(priv->base + QUAD8_REG_INDEX_INPUT_LEVELS) + & BIT(chan->channel)); return IIO_VAL_INT; } flags = inb(base_offset + 1); - borrow = flags & BIT(0); - carry = !!(flags & BIT(1)); + borrow = flags & QUAD8_FLAG_BT; + carry = !!(flags & QUAD8_FLAG_CT); /* Borrow XOR Carry effectively doubles count range */ *val = (borrow ^ carry) << 24; /* Reset Byte Pointer; transfer Counter to Output Latch */ - outb(0x11, base_offset + 1); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_CNTR_OUT, + base_offset + 1); for (i = 0; i < 3; i++) *val |= (unsigned int)inb(base_offset) << (8 * i); @@ -120,17 +155,17 @@ static int quad8_write_raw(struct iio_dev *indio_dev, return -EINVAL; /* Reset Byte Pointer */ - outb(0x01, base_offset + 1); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); /* Counter can only be set via Preset Register */ for (i = 0; i < 3; i++) outb(val >> (8 * i), base_offset); /* Transfer Preset Register to Counter */ - outb(0x08, base_offset + 1); + outb(QUAD8_CTR_RLD | QUAD8_RLD_PRESET_CNTR, base_offset + 1); /* Reset Byte Pointer */ - outb(0x01, base_offset + 1); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); /* Set Preset Register back to original value */ val = priv->preset[chan->channel]; @@ -138,9 +173,9 @@ static int quad8_write_raw(struct iio_dev *indio_dev, outb(val >> (8 * i), base_offset); /* Reset Borrow, Carry, Compare, and Sign flags */ - outb(0x02, base_offset + 1); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_FLAGS, base_offset + 1); /* Reset Error flag */ - outb(0x06, base_offset + 1); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_E, base_offset + 1); return 0; case IIO_CHAN_INFO_ENABLE: @@ -153,7 +188,7 @@ static int quad8_write_raw(struct iio_dev *indio_dev, ior_cfg = val | priv->preset_enable[chan->channel] << 1; /* Load I/O control configuration */ - outb(0x40 | ior_cfg, base_offset + 1); + outb(QUAD8_CTR_IOR | ior_cfg, base_offset + 1); return 0; case IIO_CHAN_INFO_SCALE: @@ -217,7 +252,7 @@ static ssize_t quad8_write_preset(struct iio_dev *indio_dev, uintptr_t private, priv->preset[chan->channel] = preset; /* Reset Byte Pointer */ - outb(0x01, base_offset + 1); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); /* Set Preset Register */ for (i = 0; i < 3; i++) @@ -258,7 +293,7 @@ static ssize_t quad8_write_set_to_preset_on_index(struct iio_dev *indio_dev, (unsigned int)preset_enable << 1; /* Load I/O control configuration to Input / Output Control Register */ - outb(0x40 | ior_cfg, base_offset); + outb(QUAD8_CTR_IOR | ior_cfg, base_offset); return len; } @@ -274,7 +309,7 @@ static int quad8_get_noise_error(struct iio_dev *indio_dev, struct quad8_iio *const priv = iio_priv(indio_dev); const int base_offset = priv->base + 2 * chan->channel + 1; - return !!(inb(base_offset) & BIT(4)); + return !!(inb(base_offset) & QUAD8_FLAG_E); } static const struct iio_enum quad8_noise_error_enum = { @@ -294,7 +329,7 @@ static int quad8_get_count_direction(struct iio_dev *indio_dev, struct quad8_iio *const priv = iio_priv(indio_dev); const int base_offset = priv->base + 2 * chan->channel + 1; - return !!(inb(base_offset) & BIT(5)); + return !!(inb(base_offset) & QUAD8_FLAG_UD); } static const struct iio_enum quad8_count_direction_enum = { @@ -324,7 +359,7 @@ static int quad8_set_count_mode(struct iio_dev *indio_dev, mode_cfg |= (priv->quadrature_scale[chan->channel] + 1) << 3; /* Load mode configuration to Counter Mode Register */ - outb(0x20 | mode_cfg, base_offset); + outb(QUAD8_CTR_CMR | mode_cfg, base_offset); return 0; } @@ -364,7 +399,7 @@ static int quad8_set_synchronous_mode(struct iio_dev *indio_dev, priv->synchronous_mode[chan->channel] = synchronous_mode; /* Load Index Control configuration to Index Control Register */ - outb(0x60 | idr_cfg, base_offset); + outb(QUAD8_CTR_IDR | idr_cfg, base_offset); return 0; } @@ -410,7 +445,7 @@ static int quad8_set_quadrature_mode(struct iio_dev *indio_dev, priv->quadrature_mode[chan->channel] = quadrature_mode; /* Load mode configuration to Counter Mode Register */ - outb(0x20 | mode_cfg, base_offset); + outb(QUAD8_CTR_CMR | mode_cfg, base_offset); return 0; } @@ -446,7 +481,7 @@ static int quad8_set_index_polarity(struct iio_dev *indio_dev, priv->index_polarity[chan->channel] = index_polarity; /* Load Index Control configuration to Index Control Register */ - outb(0x60 | idr_cfg, base_offset); + outb(QUAD8_CTR_IDR | idr_cfg, base_offset); return 0; } @@ -556,28 +591,28 @@ static int quad8_probe(struct device *dev, unsigned int id) priv->base = base[id]; /* Reset all counters and disable interrupt function */ - outb(0x01, base[id] + 0x11); + outb(QUAD8_CHAN_OP_RESET_COUNTERS, base[id] + QUAD8_REG_CHAN_OP); /* Set initial configuration for all counters */ for (i = 0; i < QUAD8_NUM_COUNTERS; i++) { base_offset = base[id] + 2 * i; /* Reset Byte Pointer */ - outb(0x01, base_offset + 1); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); /* Reset Preset Register */ for (j = 0; j < 3; j++) outb(0x00, base_offset); /* Reset Borrow, Carry, Compare, and Sign flags */ - outb(0x04, base_offset + 1); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_FLAGS, base_offset + 1); /* Reset Error flag */ - outb(0x06, base_offset + 1); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_E, base_offset + 1); /* Binary encoding; Normal count; non-quadrature mode */ - outb(0x20, base_offset + 1); + outb(QUAD8_CTR_CMR, base_offset + 1); /* Disable A and B inputs; preset on index; FLG1 as Carry */ - outb(0x40, base_offset + 1); + outb(QUAD8_CTR_IOR, base_offset + 1); /* Disable index function; negative index polarity */ - outb(0x60, base_offset + 1); + outb(QUAD8_CTR_IDR, base_offset + 1); } /* Enable all counters */ - outb(0x00, base[id] + 0x11); + outb(QUAD8_CHAN_OP_ENABLE_COUNTERS, base[id] + QUAD8_REG_CHAN_OP); return devm_iio_device_register(dev, indio_dev); } 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/ad5686.c b/drivers/iio/dac/ad5686.c index e136f0fd38f0..2ddbfc3fdbae 100644 --- a/drivers/iio/dac/ad5686.c +++ b/drivers/iio/dac/ad5686.c @@ -221,6 +221,7 @@ static struct iio_chan_spec name[] = { \ AD5868_CHANNEL(7, 7, bits, _shift), \ } +DECLARE_AD5693_CHANNELS(ad5311r_channels, 10, 6); DECLARE_AD5676_CHANNELS(ad5672_channels, 12, 4); DECLARE_AD5676_CHANNELS(ad5676_channels, 16, 0); DECLARE_AD5686_CHANNELS(ad5684_channels, 12, 4); @@ -231,6 +232,12 @@ DECLARE_AD5693_CHANNELS(ad5692r_channels, 14, 2); DECLARE_AD5693_CHANNELS(ad5691r_channels, 12, 4); static const struct ad5686_chip_info ad5686_chip_info_tbl[] = { + [ID_AD5311R] = { + .channels = ad5311r_channels, + .int_vref_mv = 2500, + .num_channels = 1, + .regmap_type = AD5693_REGMAP, + }, [ID_AD5671R] = { .channels = ad5672_channels, .int_vref_mv = 2500, diff --git a/drivers/iio/dac/ad5686.h b/drivers/iio/dac/ad5686.h index d05cda9f1edd..57b3c61bfb91 100644 --- a/drivers/iio/dac/ad5686.h +++ b/drivers/iio/dac/ad5686.h @@ -45,6 +45,7 @@ * ad5686_supported_device_ids: */ enum ad5686_supported_device_ids { + ID_AD5311R, ID_AD5671R, ID_AD5672R, ID_AD5675R, diff --git a/drivers/iio/dac/ad5696-i2c.c b/drivers/iio/dac/ad5696-i2c.c index d18735d7d938..7350d9806a11 100644 --- a/drivers/iio/dac/ad5696-i2c.c +++ b/drivers/iio/dac/ad5696-i2c.c @@ -71,6 +71,7 @@ static int ad5686_i2c_remove(struct i2c_client *i2c) } static const struct i2c_device_id ad5686_i2c_id[] = { + {"ad5311r", ID_AD5311R}, {"ad5671r", ID_AD5671R}, {"ad5675r", ID_AD5675R}, {"ad5691r", ID_AD5691R}, 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 <linux/bsearch.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/spi/spi.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* 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 <stefan.popa@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD5758 DAC"); +MODULE_LICENSE("GPL v2"); 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: 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) | diff --git a/drivers/iio/frequency/ad9523.c b/drivers/iio/frequency/ad9523.c index ddb6a334ae68..f4a508107f0d 100644 --- a/drivers/iio/frequency/ad9523.c +++ b/drivers/iio/frequency/ad9523.c @@ -12,6 +12,7 @@ #include <linux/sysfs.h> #include <linux/spi/spi.h> #include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> #include <linux/err.h> #include <linux/module.h> #include <linux/delay.h> @@ -268,6 +269,9 @@ struct ad9523_state { struct regulator *reg; struct ad9523_platform_data *pdata; struct iio_chan_spec ad9523_channels[AD9523_NUM_CHAN]; + struct gpio_desc *pwrdown_gpio; + struct gpio_desc *reset_gpio; + struct gpio_desc *sync_gpio; unsigned long vcxo_freq; unsigned long vco_freq; @@ -275,6 +279,15 @@ struct ad9523_state { 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 +513,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; @@ -508,9 +522,9 @@ static ssize_t ad9523_store(struct device *dev, return ret; if (!state) - return 0; + return len; - mutex_lock(&indio_dev->mlock); + mutex_lock(&st->lock); switch ((u32)this_attr->address) { case AD9523_SYNC: ret = ad9523_sync(indio_dev); @@ -521,7 +535,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 +546,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 +638,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; @@ -642,7 +657,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; @@ -659,7 +674,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 +720,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 +728,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 +744,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 +983,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); @@ -974,6 +992,32 @@ static int ad9523_probe(struct spi_device *spi) return ret; } + st->pwrdown_gpio = devm_gpiod_get_optional(&spi->dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(st->pwrdown_gpio)) { + ret = PTR_ERR(st->pwrdown_gpio); + goto error_disable_reg; + } + + st->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(st->reset_gpio)) { + ret = PTR_ERR(st->reset_gpio); + goto error_disable_reg; + } + + if (st->reset_gpio) { + udelay(1); + gpiod_direction_output(st->reset_gpio, 1); + } + + st->sync_gpio = devm_gpiod_get_optional(&spi->dev, "sync", + GPIOD_OUT_HIGH); + if (IS_ERR(st->sync_gpio)) { + ret = PTR_ERR(st->sync_gpio); + goto error_disable_reg; + } + spi_set_drvdata(spi, indio_dev); st->spi = spi; st->pdata = pdata; 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; diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index 42618fe4f83e..d80ef468508a 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -20,8 +20,6 @@ #include <linux/jiffies.h> #include <linux/irq.h> #include <linux/interrupt.h> -#include <linux/kfifo.h> -#include <linux/spinlock.h> #include <linux/iio/iio.h> #include <linux/acpi.h> #include <linux/platform_device.h> @@ -84,7 +82,7 @@ static const struct inv_mpu6050_reg_map reg_set_6050 = { static const struct inv_mpu6050_chip_config chip_config_6050 = { .fsr = INV_MPU6050_FSR_2000DPS, .lpf = INV_MPU6050_FILTER_20HZ, - .fifo_rate = INV_MPU6050_INIT_FIFO_RATE, + .divider = INV_MPU6050_FIFO_RATE_TO_DIVIDER(INV_MPU6050_INIT_FIFO_RATE), .gyro_fifo_enable = false, .accl_fifo_enable = false, .accl_fs = INV_MPU6050_FS_02G, @@ -106,6 +104,12 @@ static const struct inv_mpu6050_hw hw_info[] = { .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", .reg = ®_set_6050, @@ -280,7 +284,7 @@ static int inv_mpu6050_init_config(struct iio_dev *indio_dev) if (result) goto error_power_off; - d = INV_MPU6050_ONE_K_HZ / INV_MPU6050_INIT_FIFO_RATE - 1; + d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(INV_MPU6050_INIT_FIFO_RATE); result = regmap_write(st->map, st->reg->sample_rate_div, d); if (result) goto error_power_off; @@ -297,6 +301,13 @@ static int inv_mpu6050_init_config(struct iio_dev *indio_dev) memcpy(&st->chip_config, hw_info[st->chip_type].config, sizeof(struct inv_mpu6050_chip_config)); + /* + * Internal chip period is 1ms (1kHz). + * Let's use at the beginning the theorical value before measuring + * with interrupt timestamps. + */ + st->chip_period = NSEC_PER_MSEC; + return inv_mpu6050_set_power_itg(st, false); error_power_off: @@ -630,7 +641,7 @@ static ssize_t inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - s32 fifo_rate; + int fifo_rate; u8 d; int result; struct iio_dev *indio_dev = dev_to_iio_dev(dev); @@ -646,8 +657,13 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, if (result) return result; + /* compute the chip sample rate divider */ + d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(fifo_rate); + /* compute back the fifo rate to handle truncation cases */ + fifo_rate = INV_MPU6050_DIVIDER_TO_FIFO_RATE(d); + mutex_lock(&st->lock); - if (fifo_rate == st->chip_config.fifo_rate) { + if (d == st->chip_config.divider) { result = 0; goto fifo_rate_fail_unlock; } @@ -655,11 +671,10 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, if (result) goto fifo_rate_fail_unlock; - d = INV_MPU6050_ONE_K_HZ / fifo_rate - 1; result = regmap_write(st->map, st->reg->sample_rate_div, d); if (result) goto fifo_rate_fail_power_off; - st->chip_config.fifo_rate = fifo_rate; + st->chip_config.divider = d; result = inv_mpu6050_set_lpf(st, fifo_rate); if (result) @@ -687,7 +702,7 @@ inv_fifo_rate_show(struct device *dev, struct device_attribute *attr, unsigned fifo_rate; mutex_lock(&st->lock); - fifo_rate = st->chip_config.fifo_rate; + fifo_rate = INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); mutex_unlock(&st->lock); return scnprintf(buf, PAGE_SIZE, "%u\n", fifo_rate); @@ -1005,7 +1020,7 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, indio_dev->modes = INDIO_BUFFER_TRIGGERED; result = devm_iio_triggered_buffer_setup(dev, indio_dev, - inv_mpu6050_irq_handler, + iio_pollfunc_store_time, inv_mpu6050_read_fifo, NULL); if (result) { @@ -1018,8 +1033,6 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, return result; } - INIT_KFIFO(st->timestamps); - spin_lock_init(&st->time_stamp_lock); result = devm_iio_device_register(dev, indio_dev); if (result) { dev_err(dev, "IIO register fail %d\n", result); 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}, @@ -193,6 +194,10 @@ static const struct of_device_id inv_of_match[] = { .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 c54da777945d..e69a59659dbc 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -12,8 +12,6 @@ */ #include <linux/i2c.h> #include <linux/i2c-mux.h> -#include <linux/kfifo.h> -#include <linux/spinlock.h> #include <linux/mutex.h> #include <linux/iio/iio.h> #include <linux/iio/buffer.h> @@ -73,6 +71,7 @@ struct inv_mpu6050_reg_map { enum inv_devices { INV_MPU6050, INV_MPU6500, + INV_MPU6515, INV_MPU6000, INV_MPU9150, INV_MPU9250, @@ -88,7 +87,7 @@ enum inv_devices { * @accl_fs: accel full scale range. * @accl_fifo_enable: enable accel data output * @gyro_fifo_enable: enable gyro data output - * @fifo_rate: FIFO update rate. + * @divider: chip sample rate divider (sample rate divider - 1) */ struct inv_mpu6050_chip_config { unsigned int fsr:2; @@ -96,7 +95,7 @@ struct inv_mpu6050_chip_config { unsigned int accl_fs:2; unsigned int accl_fifo_enable:1; unsigned int gyro_fifo_enable:1; - u16 fifo_rate; + u8 divider; u8 user_ctrl; }; @@ -116,40 +115,40 @@ struct inv_mpu6050_hw { /* * struct inv_mpu6050_state - Driver state variables. - * @TIMESTAMP_FIFO_SIZE: fifo size for timestamp. * @lock: Chip access lock. * @trig: IIO trigger. * @chip_config: Cached attribute information. * @reg: Map of important registers. * @hw: Other hardware-specific information. * @chip_type: chip type. - * @time_stamp_lock: spin lock to time stamp. * @plat_data: platform data (deprecated in favor of @orientation). * @orientation: sensor chip orientation relative to main hardware. - * @timestamps: kfifo queue to store time stamp. * @map regmap pointer. * @irq interrupt number. * @irq_mask the int_pin_cfg mask to configure interrupt type. + * @chip_period: chip internal period estimation (~1kHz). + * @it_timestamp: timestamp from previous interrupt. + * @data_timestamp: timestamp for next data sample. */ struct inv_mpu6050_state { -#define TIMESTAMP_FIFO_SIZE 16 struct mutex lock; struct iio_trigger *trig; struct inv_mpu6050_chip_config chip_config; const struct inv_mpu6050_reg_map *reg; const struct inv_mpu6050_hw *hw; enum inv_devices chip_type; - spinlock_t time_stamp_lock; struct i2c_mux_core *muxc; struct i2c_client *mux_client; unsigned int powerup_count; struct inv_mpu6050_platform_data plat_data; struct iio_mount_matrix orientation; - DECLARE_KFIFO(timestamps, long long, TIMESTAMP_FIFO_SIZE); struct regmap *map; int irq; u8 irq_mask; unsigned skip_samples; + s64 chip_period; + s64 it_timestamp; + s64 data_timestamp; }; /*register and associated bit definition*/ @@ -174,6 +173,7 @@ struct inv_mpu6050_state { #define INV_MPU6050_REG_RAW_GYRO 0x43 #define INV_MPU6050_REG_INT_STATUS 0x3A +#define INV_MPU6050_BIT_FIFO_OVERFLOW_INT 0x10 #define INV_MPU6050_BIT_RAW_DATA_RDY_INT 0x01 #define INV_MPU6050_REG_USER_CTRL 0x6A @@ -198,7 +198,6 @@ struct inv_mpu6050_state { #define INV_MPU6050_BYTES_PER_3AXIS_SENSOR 6 #define INV_MPU6050_FIFO_COUNT_BYTE 2 -#define INV_MPU6050_FIFO_THRESHOLD 500 /* mpu6500 registers */ #define INV_MPU6500_REG_ACCEL_CONFIG_2 0x1D @@ -231,13 +230,24 @@ struct inv_mpu6050_state { #define INV_MPU6050_LATCH_INT_EN 0x20 #define INV_MPU6050_BIT_BYPASS_EN 0x2 +/* Allowed timestamp period jitter in percent */ +#define INV_MPU6050_TS_PERIOD_JITTER 4 /* init parameters */ #define INV_MPU6050_INIT_FIFO_RATE 50 -#define INV_MPU6050_TIME_STAMP_TOR 5 #define INV_MPU6050_MAX_FIFO_RATE 1000 #define INV_MPU6050_MIN_FIFO_RATE 4 -#define INV_MPU6050_ONE_K_HZ 1000 + +/* chip internal frequency: 1KHz */ +#define INV_MPU6050_INTERNAL_FREQ_HZ 1000 +/* return the frequency divider (chip sample rate divider + 1) */ +#define INV_MPU6050_FREQ_DIVIDER(st) \ + ((st)->chip_config.divider + 1) +/* chip sample rate divider to fifo rate */ +#define INV_MPU6050_FIFO_RATE_TO_DIVIDER(fifo_rate) \ + ((INV_MPU6050_INTERNAL_FREQ_HZ / (fifo_rate)) - 1) +#define INV_MPU6050_DIVIDER_TO_FIFO_RATE(divider) \ + (INV_MPU6050_INTERNAL_FREQ_HZ / ((divider) + 1)) #define INV_MPU6050_REG_WHOAMI 117 @@ -247,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 */ @@ -300,7 +311,6 @@ enum inv_mpu6050_clock_sel_e { NUM_CLK }; -irqreturn_t inv_mpu6050_irq_handler(int irq, void *p); irqreturn_t inv_mpu6050_read_fifo(int irq, void *p); int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev, int irq_type); int inv_reset_fifo(struct iio_dev *indio_dev); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c index 1795418438e4..548e042f7b5b 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -19,18 +19,83 @@ #include <linux/jiffies.h> #include <linux/irq.h> #include <linux/interrupt.h> -#include <linux/kfifo.h> #include <linux/poll.h> +#include <linux/math64.h> +#include <asm/unaligned.h> #include "inv_mpu_iio.h" -static void inv_clear_kfifo(struct inv_mpu6050_state *st) +/** + * inv_mpu6050_update_period() - Update chip internal period estimation + * + * @st: driver state + * @timestamp: the interrupt timestamp + * @nb: number of data set in the fifo + * + * This function uses interrupt timestamps to estimate the chip period and + * to choose the data timestamp to come. + */ +static void inv_mpu6050_update_period(struct inv_mpu6050_state *st, + s64 timestamp, size_t nb) { - unsigned long flags; + /* Period boundaries for accepting timestamp */ + const s64 period_min = + (NSEC_PER_MSEC * (100 - INV_MPU6050_TS_PERIOD_JITTER)) / 100; + const s64 period_max = + (NSEC_PER_MSEC * (100 + INV_MPU6050_TS_PERIOD_JITTER)) / 100; + const s32 divider = INV_MPU6050_FREQ_DIVIDER(st); + s64 delta, interval; + bool use_it_timestamp = false; + + if (st->it_timestamp == 0) { + /* not initialized, forced to use it_timestamp */ + use_it_timestamp = true; + } else if (nb == 1) { + /* + * Validate the use of it timestamp by checking if interrupt + * has been delayed. + * nb > 1 means interrupt was delayed for more than 1 sample, + * so it's obviously not good. + * Compute the chip period between 2 interrupts for validating. + */ + delta = div_s64(timestamp - st->it_timestamp, divider); + if (delta > period_min && delta < period_max) { + /* update chip period and use it timestamp */ + st->chip_period = (st->chip_period + delta) / 2; + use_it_timestamp = true; + } + } + + if (use_it_timestamp) { + /* + * Manage case of multiple samples in the fifo (nb > 1): + * compute timestamp corresponding to the first sample using + * estimated chip period. + */ + interval = (nb - 1) * st->chip_period * divider; + st->data_timestamp = timestamp - interval; + } - /* take the spin lock sem to avoid interrupt kick in */ - spin_lock_irqsave(&st->time_stamp_lock, flags); - kfifo_reset(&st->timestamps); - spin_unlock_irqrestore(&st->time_stamp_lock, flags); + /* save it timestamp */ + st->it_timestamp = timestamp; +} + +/** + * inv_mpu6050_get_timestamp() - Return the current data timestamp + * + * @st: driver state + * @return: current data timestamp + * + * This function returns the current data timestamp and prepares for next one. + */ +static s64 inv_mpu6050_get_timestamp(struct inv_mpu6050_state *st) +{ + s64 ts; + + /* return current data timestamp and increment */ + ts = st->data_timestamp; + st->data_timestamp += st->chip_period * INV_MPU6050_FREQ_DIVIDER(st); + + return ts; } int inv_reset_fifo(struct iio_dev *indio_dev) @@ -39,6 +104,9 @@ int inv_reset_fifo(struct iio_dev *indio_dev) u8 d; struct inv_mpu6050_state *st = iio_priv(indio_dev); + /* reset it timestamp validation */ + st->it_timestamp = 0; + /* disable interrupt */ result = regmap_write(st->map, st->reg->int_enable, 0); if (result) { @@ -62,9 +130,6 @@ int inv_reset_fifo(struct iio_dev *indio_dev) if (result) goto reset_fifo_fail; - /* clear timestamps fifo */ - inv_clear_kfifo(st); - /* enable interrupt */ if (st->chip_config.accl_fifo_enable || st->chip_config.gyro_fifo_enable) { @@ -99,23 +164,6 @@ reset_fifo_fail: } /** - * inv_mpu6050_irq_handler() - Cache a timestamp at each data ready interrupt. - */ -irqreturn_t inv_mpu6050_irq_handler(int irq, void *p) -{ - struct iio_poll_func *pf = p; - struct iio_dev *indio_dev = pf->indio_dev; - struct inv_mpu6050_state *st = iio_priv(indio_dev); - s64 timestamp; - - timestamp = iio_get_time_ns(indio_dev); - kfifo_in_spinlocked(&st->timestamps, ×tamp, 1, - &st->time_stamp_lock); - - return IRQ_WAKE_THREAD; -} - -/** * inv_mpu6050_read_fifo() - Transfer data from hardware FIFO to KFIFO. */ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) @@ -129,6 +177,7 @@ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) u16 fifo_count; s64 timestamp; int int_status; + size_t i, nb; mutex_lock(&st->lock); @@ -139,6 +188,9 @@ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) "failed to ack interrupt\n"); goto flush_fifo; } + /* handle fifo overflow by reseting fifo */ + if (int_status & INV_MPU6050_BIT_FIFO_OVERFLOW_INT) + goto flush_fifo; if (!(int_status & INV_MPU6050_BIT_RAW_DATA_RDY_INT)) { dev_warn(regmap_get_device(st->map), "spurious interrupt with status 0x%x\n", int_status); @@ -163,38 +215,23 @@ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) INV_MPU6050_FIFO_COUNT_BYTE); if (result) goto end_session; - fifo_count = be16_to_cpup((__be16 *)(&data[0])); - if (fifo_count < bytes_per_datum) - goto end_session; - /* fifo count can't be an odd number. If it is odd, reset the FIFO. */ - if (fifo_count & 1) - goto flush_fifo; - if (fifo_count > INV_MPU6050_FIFO_THRESHOLD) - goto flush_fifo; - /* Timestamp mismatch. */ - if (kfifo_len(&st->timestamps) > - fifo_count / bytes_per_datum + INV_MPU6050_TIME_STAMP_TOR) - goto flush_fifo; - do { + fifo_count = get_unaligned_be16(&data[0]); + /* compute and process all complete datum */ + nb = fifo_count / bytes_per_datum; + inv_mpu6050_update_period(st, pf->timestamp, nb); + for (i = 0; i < nb; ++i) { result = regmap_bulk_read(st->map, st->reg->fifo_r_w, data, bytes_per_datum); if (result) goto flush_fifo; - - result = kfifo_out(&st->timestamps, ×tamp, 1); - /* when there is no timestamp, put timestamp as 0 */ - if (result == 0) - timestamp = 0; - /* skip first samples if needed */ - if (st->skip_samples) + if (st->skip_samples) { st->skip_samples--; - else - iio_push_to_buffers_with_timestamp(indio_dev, data, - timestamp); - - fifo_count -= bytes_per_datum; - } while (fifo_count >= bytes_per_datum); + continue; + } + timestamp = inv_mpu6050_get_timestamp(st); + iio_push_to_buffers_with_timestamp(indio_dev, data, timestamp); + } end_session: mutex_unlock(&st->lock); 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; } diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 19bdf3d2962a..a062cfddc5af 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -85,6 +85,8 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_COUNT] = "count", [IIO_INDEX] = "index", [IIO_GRAVITY] = "gravity", + [IIO_POSITIONRELATIVE] = "positionrelative", + [IIO_PHASE] = "phase", }; static const char * const iio_modifier_names[] = { @@ -108,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", @@ -207,35 +210,27 @@ static int iio_device_set_clock(struct iio_dev *indio_dev, clockid_t clock_id) */ s64 iio_get_time_ns(const struct iio_dev *indio_dev) { - struct timespec tp; + struct timespec64 tp; switch (iio_device_get_clock(indio_dev)) { case CLOCK_REALTIME: - ktime_get_real_ts(&tp); - break; + return ktime_get_real_ns(); case CLOCK_MONOTONIC: - ktime_get_ts(&tp); - break; + return ktime_get_ns(); case CLOCK_MONOTONIC_RAW: - getrawmonotonic(&tp); - break; + return ktime_get_raw_ns(); case CLOCK_REALTIME_COARSE: - tp = current_kernel_time(); - break; + return ktime_to_ns(ktime_get_coarse_real()); case CLOCK_MONOTONIC_COARSE: - tp = get_monotonic_coarse(); - break; + ktime_get_coarse_ts64(&tp); + return timespec64_to_ns(&tp); case CLOCK_BOOTTIME: - get_monotonic_boottime(&tp); - break; + return ktime_get_boot_ns(); case CLOCK_TAI: - timekeeping_clocktai(&tp); - break; + return ktime_get_tai_ns(); default: BUG(); } - - return timespec_to_ns(&tp); } EXPORT_SYMBOL(iio_get_time_ns); diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index c7ef8d1862d6..d66ea754ffff 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 @@ -438,11 +450,12 @@ config US5182D will be called us5182d. config VCNL4000 - tristate "VCNL4000/4010/4020 combined ALS and proximity sensor" + tristate "VCNL4000/4010/4020/4200 combined ALS and proximity sensor" depends on I2C help Say Y here if you want to build a driver for the Vishay VCNL4000, - VCNL4010, VCNL4020 combined ambient light and proximity sensor. + VCNL4010, VCNL4020, VCNL4200 combined ambient light and proximity + sensor. To compile this driver as a module, choose M here: the module will be called vcnl4000. 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..015a21f0c2ef --- /dev/null +++ b/drivers/iio/light/si1133.c @@ -0,0 +1,1071 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * si1133.c - Support for Silabs SI1133 combined ambient + * light and UV index sensors + * + * Copyright 2018 Maxime Roussin-Belanger <maxime.roussinbelanger@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include <linux/util_macros.h> + +#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; + } + err = regmap_read(data->regmap, SI1133_REG_RESPONSE0, &resp); + if (err) + 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 <maxime.roussinbelanger@gmail.com>"); +MODULE_DESCRIPTION("Silabs SI1133, UV index sensor and ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c index c599a90506ad..04fd0d4b6f19 100644 --- a/drivers/iio/light/vcnl4000.c +++ b/drivers/iio/light/vcnl4000.c @@ -1,5 +1,5 @@ /* - * vcnl4000.c - Support for Vishay VCNL4000/4010/4020 combined ambient + * vcnl4000.c - Support for Vishay VCNL4000/4010/4020/4200 combined ambient * light and proximity sensor * * Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net> @@ -8,13 +8,15 @@ * the GNU General Public License. See the file COPYING in the main * directory of this archive for more details. * - * IIO driver for VCNL4000 (7-bit I2C slave address 0x13) + * IIO driver for: + * VCNL4000/10/20 (7-bit I2C slave address 0x13) + * VCNL4200 (7-bit I2C slave address 0x51) * * TODO: * allow to adjust IR current * proximity threshold and event handling * periodic ALS/proximity measurement (VCNL4010/20) - * interrupts (VCNL4010/20) + * interrupts (VCNL4010/20, VCNL4200) */ #include <linux/module.h> @@ -26,8 +28,9 @@ #include <linux/iio/sysfs.h> #define VCNL4000_DRV_NAME "vcnl4000" -#define VCNL4000_ID 0x01 -#define VCNL4010_ID 0x02 /* for VCNL4020, VCNL4010 */ +#define VCNL4000_PROD_ID 0x01 +#define VCNL4010_PROD_ID 0x02 /* for VCNL4020, VCNL4010 */ +#define VCNL4200_PROD_ID 0x58 #define VCNL4000_COMMAND 0x80 /* Command register */ #define VCNL4000_PROD_REV 0x81 /* Product ID and Revision ID */ @@ -40,23 +43,124 @@ #define VCNL4000_PS_MEAS_FREQ 0x89 /* Proximity test signal frequency */ #define VCNL4000_PS_MOD_ADJ 0x8a /* Proximity modulator timing adjustment */ +#define VCNL4200_AL_CONF 0x00 /* Ambient light configuration */ +#define VCNL4200_PS_CONF1 0x03 /* Proximity configuration */ +#define VCNL4200_PS_DATA 0x08 /* Proximity data */ +#define VCNL4200_AL_DATA 0x09 /* Ambient light data */ +#define VCNL4200_DEV_ID 0x0e /* Device ID, slave address and version */ + /* Bit masks for COMMAND register */ #define VCNL4000_AL_RDY BIT(6) /* ALS data ready? */ #define VCNL4000_PS_RDY BIT(5) /* proximity data ready? */ #define VCNL4000_AL_OD BIT(4) /* start on-demand ALS measurement */ #define VCNL4000_PS_OD BIT(3) /* start on-demand proximity measurement */ +enum vcnl4000_device_ids { + VCNL4000, + VCNL4010, + VCNL4200, +}; + +struct vcnl4200_channel { + u8 reg; + ktime_t last_measurement; + ktime_t sampling_rate; + struct mutex lock; +}; + struct vcnl4000_data { struct i2c_client *client; - struct mutex lock; + enum vcnl4000_device_ids id; + int rev; + int al_scale; + const struct vcnl4000_chip_spec *chip_spec; + struct mutex vcnl4000_lock; + struct vcnl4200_channel vcnl4200_al; + struct vcnl4200_channel vcnl4200_ps; +}; + +struct vcnl4000_chip_spec { + const char *prod; + int (*init)(struct vcnl4000_data *data); + int (*measure_light)(struct vcnl4000_data *data, int *val); + int (*measure_proximity)(struct vcnl4000_data *data, int *val); }; static const struct i2c_device_id vcnl4000_id[] = { - { "vcnl4000", 0 }, + { "vcnl4000", VCNL4000 }, + { "vcnl4010", VCNL4010 }, + { "vcnl4020", VCNL4010 }, + { "vcnl4200", VCNL4200 }, { } }; MODULE_DEVICE_TABLE(i2c, vcnl4000_id); +static int vcnl4000_init(struct vcnl4000_data *data) +{ + int ret, prod_id; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV); + if (ret < 0) + return ret; + + prod_id = ret >> 4; + switch (prod_id) { + case VCNL4000_PROD_ID: + if (data->id != VCNL4000) + dev_warn(&data->client->dev, + "wrong device id, use vcnl4000"); + break; + case VCNL4010_PROD_ID: + if (data->id != VCNL4010) + dev_warn(&data->client->dev, + "wrong device id, use vcnl4010/4020"); + break; + default: + return -ENODEV; + } + + data->rev = ret & 0xf; + data->al_scale = 250000; + mutex_init(&data->vcnl4000_lock); + + return 0; +}; + +static int vcnl4200_init(struct vcnl4000_data *data) +{ + int ret; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_DEV_ID); + if (ret < 0) + return ret; + + if ((ret & 0xff) != VCNL4200_PROD_ID) + return -ENODEV; + + data->rev = (ret >> 8) & 0xf; + + /* Set defaults and enable both channels */ + ret = i2c_smbus_write_byte_data(data->client, VCNL4200_AL_CONF, 0x00); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(data->client, VCNL4200_PS_CONF1, 0x00); + if (ret < 0) + return ret; + + data->al_scale = 24000; + data->vcnl4200_al.reg = VCNL4200_AL_DATA; + data->vcnl4200_ps.reg = VCNL4200_PS_DATA; + /* Integration time is 50ms, but the experiments show 54ms in total. */ + data->vcnl4200_al.sampling_rate = ktime_set(0, 54000 * 1000); + data->vcnl4200_ps.sampling_rate = ktime_set(0, 4200 * 1000); + data->vcnl4200_al.last_measurement = ktime_set(0, 0); + data->vcnl4200_ps.last_measurement = ktime_set(0, 0); + mutex_init(&data->vcnl4200_al.lock); + mutex_init(&data->vcnl4200_ps.lock); + + return 0; +}; + static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask, u8 rdy_mask, u8 data_reg, int *val) { @@ -64,7 +168,7 @@ static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask, __be16 buf; int ret; - mutex_lock(&data->lock); + mutex_lock(&data->vcnl4000_lock); ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, req_mask); @@ -93,16 +197,88 @@ static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask, if (ret < 0) goto fail; - mutex_unlock(&data->lock); + mutex_unlock(&data->vcnl4000_lock); *val = be16_to_cpu(buf); return 0; fail: - mutex_unlock(&data->lock); + mutex_unlock(&data->vcnl4000_lock); return ret; } +static int vcnl4200_measure(struct vcnl4000_data *data, + struct vcnl4200_channel *chan, int *val) +{ + int ret; + s64 delta; + ktime_t next_measurement; + + mutex_lock(&chan->lock); + + next_measurement = ktime_add(chan->last_measurement, + chan->sampling_rate); + delta = ktime_us_delta(next_measurement, ktime_get()); + if (delta > 0) + usleep_range(delta, delta + 500); + chan->last_measurement = ktime_get(); + + mutex_unlock(&chan->lock); + + ret = i2c_smbus_read_word_data(data->client, chan->reg); + if (ret < 0) + return ret; + + *val = ret; + + return 0; +} + +static int vcnl4000_measure_light(struct vcnl4000_data *data, int *val) +{ + return vcnl4000_measure(data, + VCNL4000_AL_OD, VCNL4000_AL_RDY, + VCNL4000_AL_RESULT_HI, val); +} + +static int vcnl4200_measure_light(struct vcnl4000_data *data, int *val) +{ + return vcnl4200_measure(data, &data->vcnl4200_al, val); +} + +static int vcnl4000_measure_proximity(struct vcnl4000_data *data, int *val) +{ + return vcnl4000_measure(data, + VCNL4000_PS_OD, VCNL4000_PS_RDY, + VCNL4000_PS_RESULT_HI, val); +} + +static int vcnl4200_measure_proximity(struct vcnl4000_data *data, int *val) +{ + return vcnl4200_measure(data, &data->vcnl4200_ps, val); +} + +static const struct vcnl4000_chip_spec vcnl4000_chip_spec_cfg[] = { + [VCNL4000] = { + .prod = "VCNL4000", + .init = vcnl4000_init, + .measure_light = vcnl4000_measure_light, + .measure_proximity = vcnl4000_measure_proximity, + }, + [VCNL4010] = { + .prod = "VCNL4010/4020", + .init = vcnl4000_init, + .measure_light = vcnl4000_measure_light, + .measure_proximity = vcnl4000_measure_proximity, + }, + [VCNL4200] = { + .prod = "VCNL4200", + .init = vcnl4200_init, + .measure_light = vcnl4200_measure_light, + .measure_proximity = vcnl4200_measure_proximity, + }, +}; + static const struct iio_chan_spec vcnl4000_channels[] = { { .type = IIO_LIGHT, @@ -125,16 +301,12 @@ static int vcnl4000_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_RAW: switch (chan->type) { case IIO_LIGHT: - ret = vcnl4000_measure(data, - VCNL4000_AL_OD, VCNL4000_AL_RDY, - VCNL4000_AL_RESULT_HI, val); + ret = data->chip_spec->measure_light(data, val); if (ret < 0) return ret; return IIO_VAL_INT; case IIO_PROXIMITY: - ret = vcnl4000_measure(data, - VCNL4000_PS_OD, VCNL4000_PS_RDY, - VCNL4000_PS_RESULT_HI, val); + ret = data->chip_spec->measure_proximity(data, val); if (ret < 0) return ret; return IIO_VAL_INT; @@ -146,7 +318,7 @@ static int vcnl4000_read_raw(struct iio_dev *indio_dev, return -EINVAL; *val = 0; - *val2 = 250000; + *val2 = data->al_scale; return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; @@ -162,7 +334,7 @@ static int vcnl4000_probe(struct i2c_client *client, { struct vcnl4000_data *data; struct iio_dev *indio_dev; - int ret, prod_id; + int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) @@ -171,19 +343,15 @@ static int vcnl4000_probe(struct i2c_client *client, data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); data->client = client; - mutex_init(&data->lock); + data->id = id->driver_data; + data->chip_spec = &vcnl4000_chip_spec_cfg[data->id]; - ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV); + ret = data->chip_spec->init(data); if (ret < 0) return ret; - prod_id = ret >> 4; - if (prod_id != VCNL4010_ID && prod_id != VCNL4000_ID) - return -ENODEV; - dev_dbg(&client->dev, "%s Ambient light/proximity sensor, Rev: %02x\n", - (prod_id == VCNL4010_ID) ? "VCNL4010/4020" : "VCNL4000", - ret & 0xf); + data->chip_spec->prod, data->rev); indio_dev->dev.parent = &client->dev; indio_dev->info = &vcnl4000_info; 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; 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 <m.othacehe@gmail.com> + * + * 7-bit I2C slave address: 0x57 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/of_device.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +/* 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 <m.othacehe@gmail.com>"); +MODULE_DESCRIPTION("ISL29501 Time of Flight sensor driver"); +MODULE_LICENSE("GPL v2"); |