diff options
Diffstat (limited to 'drivers/iio')
104 files changed, 10096 insertions, 477 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index ef8401ac1141..15de262015df 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -5,6 +5,37 @@ menu "Accelerometers" +config ADXL345 + tristate + +config ADXL345_I2C + tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer I2C Driver" + depends on INPUT_ADXL34X=n + depends on I2C + select ADXL345 + select REGMAP_I2C + help + Say Y here if you want to build support for the Analog Devices + ADXL345 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl345_i2c and you will also get adxl345_core + for the core module. + +config ADXL345_SPI + tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer SPI Driver" + depends on INPUT_ADXL34X=n + depends on SPI + select ADXL345 + select REGMAP_SPI + help + Say Y here if you want to build support for the Analog Devices + ADXL345 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl345_spi and you will also get adxl345_core + for the core module. + config BMA180 tristate "Bosch BMA180/BMA250 3-Axis Accelerometer Driver" depends on I2C diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index 69fe8edc57a2..31fba1974e95 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -3,6 +3,9 @@ # # When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ADXL345) += adxl345_core.o +obj-$(CONFIG_ADXL345_I2C) += adxl345_i2c.o +obj-$(CONFIG_ADXL345_SPI) += adxl345_spi.o obj-$(CONFIG_BMA180) += bma180.o obj-$(CONFIG_BMA220) += bma220_spi.o obj-$(CONFIG_BMC150_ACCEL) += bmc150-accel-core.o diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h new file mode 100644 index 000000000000..c1ddf3927c47 --- /dev/null +++ b/drivers/iio/accel/adxl345.h @@ -0,0 +1,18 @@ +/* + * ADXL345 3-Axis Digital Accelerometer + * + * Copyright (c) 2017 Eva Rachel Retuya <eraretuya@gmail.com> + * + * 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. + */ + +#ifndef _ADXL345_H_ +#define _ADXL345_H_ + +int adxl345_core_probe(struct device *dev, struct regmap *regmap, + 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 new file mode 100644 index 000000000000..9ccb5828db98 --- /dev/null +++ b/drivers/iio/accel/adxl345_core.c @@ -0,0 +1,179 @@ +/* + * ADXL345 3-Axis Digital Accelerometer IIO core driver + * + * Copyright (c) 2017 Eva Rachel Retuya <eraretuya@gmail.com> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + */ + +#include <linux/module.h> +#include <linux/regmap.h> + +#include <linux/iio/iio.h> + +#include "adxl345.h" + +#define ADXL345_REG_DEVID 0x00 +#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_POWER_CTL_MEASURE BIT(3) +#define ADXL345_POWER_CTL_STANDBY 0x00 + +#define ADXL345_DATA_FORMAT_FULL_RES BIT(3) /* Up to 13-bits resolution */ +#define ADXL345_DATA_FORMAT_2G 0 +#define ADXL345_DATA_FORMAT_4G 1 +#define ADXL345_DATA_FORMAT_8G 2 +#define ADXL345_DATA_FORMAT_16G 3 + +#define ADXL345_DEVID 0xE5 + +/* + * In full-resolution mode, scale factor is maintained at ~4 mg/LSB + * in all g ranges. + * + * At +/- 16g with 13-bit resolution, scale is computed as: + * (16 + 16) * 9.81 / (2^13 - 1) = 0.0383 + */ +static const int adxl345_uscale = 38300; + +struct adxl345_data { + struct regmap *regmap; + u8 data_range; +}; + +#define ADXL345_CHANNEL(reg, 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), \ +} + +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), +}; + +static int adxl345_read_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); + __le16 regval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Data is stored in adjacent registers: + * ADXL345_REG_DATA(X0/Y0/Z0) contain the least significant byte + * and ADXL345_REG_DATA(X0/Y0/Z0) + 1 the most significant byte + */ + ret = regmap_bulk_read(data->regmap, chan->address, ®val, + sizeof(regval)); + if (ret < 0) + return ret; + + *val = sign_extend32(le16_to_cpu(regval), 12); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = adxl345_uscale; + + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static const struct iio_info adxl345_info = { + .driver_module = THIS_MODULE, + .read_raw = adxl345_read_raw, +}; + +int adxl345_core_probe(struct device *dev, struct regmap *regmap, + const char *name) +{ + struct adxl345_data *data; + struct iio_dev *indio_dev; + u32 regval; + int ret; + + ret = regmap_read(regmap, ADXL345_REG_DEVID, ®val); + if (ret < 0) { + dev_err(dev, "Error reading device ID: %d\n", ret); + return ret; + } + + if (regval != ADXL345_DEVID) { + dev_err(dev, "Invalid device ID: %x, expected %x\n", + regval, ADXL345_DEVID); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + /* Enable full-resolution mode */ + data->data_range = ADXL345_DATA_FORMAT_FULL_RES; + + ret = regmap_write(data->regmap, ADXL345_REG_DATA_FORMAT, + data->data_range); + if (ret < 0) { + dev_err(dev, "Failed to set data range: %d\n", ret); + return ret; + } + + indio_dev->dev.parent = dev; + indio_dev->name = name; + indio_dev->info = &adxl345_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adxl345_channels; + indio_dev->num_channels = ARRAY_SIZE(adxl345_channels); + + /* Enable measurement mode */ + ret = regmap_write(data->regmap, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_MEASURE); + if (ret < 0) { + dev_err(dev, "Failed to enable measurement mode: %d\n", ret); + return ret; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "iio_device_register failed: %d\n", ret); + regmap_write(data->regmap, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_STANDBY); + } + + return ret; +} +EXPORT_SYMBOL_GPL(adxl345_core_probe); + +int adxl345_core_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adxl345_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + return regmap_write(data->regmap, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_STANDBY); +} +EXPORT_SYMBOL_GPL(adxl345_core_remove); + +MODULE_AUTHOR("Eva Rachel Retuya <eraretuya@gmail.com>"); +MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl345_i2c.c b/drivers/iio/accel/adxl345_i2c.c new file mode 100644 index 000000000000..05e1ec49700c --- /dev/null +++ b/drivers/iio/accel/adxl345_i2c.c @@ -0,0 +1,73 @@ +/* + * ADXL345 3-Axis Digital Accelerometer I2C driver + * + * Copyright (c) 2017 Eva Rachel Retuya <eraretuya@gmail.com> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * 7-bit I2C slave address: 0x1D (ALT ADDRESS pin tied to VDDIO) or + * 0x53 (ALT ADDRESS pin grounded) + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "adxl345.h" + +static const struct regmap_config adxl345_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int adxl345_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &adxl345_i2c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Error initializing i2c regmap: %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return adxl345_core_probe(&client->dev, regmap, id ? id->name : NULL); +} + +static int adxl345_i2c_remove(struct i2c_client *client) +{ + return adxl345_core_remove(&client->dev); +} + +static const struct i2c_device_id adxl345_i2c_id[] = { + { "adxl345", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, adxl345_i2c_id); + +static const struct of_device_id adxl345_of_match[] = { + { .compatible = "adi,adxl345" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, adxl345_of_match); + +static struct i2c_driver adxl345_i2c_driver = { + .driver = { + .name = "adxl345_i2c", + .of_match_table = adxl345_of_match, + }, + .probe = adxl345_i2c_probe, + .remove = adxl345_i2c_remove, + .id_table = adxl345_i2c_id, +}; + +module_i2c_driver(adxl345_i2c_driver); + +MODULE_AUTHOR("Eva Rachel Retuya <eraretuya@gmail.com>"); +MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl345_spi.c b/drivers/iio/accel/adxl345_spi.c new file mode 100644 index 000000000000..6d658196f81c --- /dev/null +++ b/drivers/iio/accel/adxl345_spi.c @@ -0,0 +1,81 @@ +/* + * ADXL345 3-Axis Digital Accelerometer SPI driver + * + * Copyright (c) 2017 Eva Rachel Retuya <eraretuya@gmail.com> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + */ + +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> + +#include "adxl345.h" + +#define ADXL345_MAX_SPI_FREQ_HZ 5000000 + +static const struct regmap_config adxl345_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + /* Setting bits 7 and 6 enables multiple-byte read */ + .read_flag_mask = BIT(7) | BIT(6), +}; + +static int adxl345_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + + /* Bail out if max_speed_hz exceeds 5 MHz */ + if (spi->max_speed_hz > ADXL345_MAX_SPI_FREQ_HZ) { + dev_err(&spi->dev, "SPI CLK, %d Hz exceeds 5 MHz\n", + spi->max_speed_hz); + return -EINVAL; + } + + regmap = devm_regmap_init_spi(spi, &adxl345_spi_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Error initializing spi regmap: %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return adxl345_core_probe(&spi->dev, regmap, id->name); +} + +static int adxl345_spi_remove(struct spi_device *spi) +{ + return adxl345_core_remove(&spi->dev); +} + +static const struct spi_device_id adxl345_spi_id[] = { + { "adxl345", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(spi, adxl345_spi_id); + +static const struct of_device_id adxl345_of_match[] = { + { .compatible = "adi,adxl345" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, adxl345_of_match); + +static struct spi_driver adxl345_spi_driver = { + .driver = { + .name = "adxl345_spi", + .of_match_table = adxl345_of_match, + }, + .probe = adxl345_spi_probe, + .remove = adxl345_spi_remove, + .id_table = adxl345_spi_id, +}; + +module_spi_driver(adxl345_spi_driver); + +MODULE_AUTHOR("Eva Rachel Retuya <eraretuya@gmail.com>"); +MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c index 0890934ef66f..efc67739c28f 100644 --- a/drivers/iio/accel/bma180.c +++ b/drivers/iio/accel/bma180.c @@ -18,6 +18,7 @@ #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/delay.h> +#include <linux/of_device.h> #include <linux/of.h> #include <linux/bitops.h> #include <linux/slab.h> @@ -32,7 +33,7 @@ #define BMA180_DRV_NAME "bma180" #define BMA180_IRQ_NAME "bma180_event" -enum { +enum chip_ids { BMA180, BMA250, }; @@ -41,11 +42,11 @@ struct bma180_data; struct bma180_part_info { const struct iio_chan_spec *channels; - unsigned num_channels; + unsigned int num_channels; const int *scale_table; - unsigned num_scales; + unsigned int num_scales; const int *bw_table; - unsigned num_bw; + unsigned int num_bw; u8 int_reset_reg, int_reset_mask; u8 sleep_reg, sleep_mask; @@ -408,7 +409,7 @@ err: dev_err(&data->client->dev, "failed to disable the chip\n"); } -static ssize_t bma180_show_avail(char *buf, const int *vals, unsigned n, +static ssize_t bma180_show_avail(char *buf, const int *vals, unsigned int n, bool micros) { size_t len = 0; @@ -707,6 +708,7 @@ static int bma180_probe(struct i2c_client *client, { struct bma180_data *data; struct iio_dev *indio_dev; + enum chip_ids chip; int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); @@ -716,7 +718,11 @@ static int bma180_probe(struct i2c_client *client, data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); data->client = client; - data->part_info = &bma180_part_info[id->driver_data]; + if (client->dev.of_node) + chip = (enum chip_ids)of_device_get_match_data(&client->dev); + else + chip = id->driver_data; + data->part_info = &bma180_part_info[chip]; ret = data->part_info->chip_config(data); if (ret < 0) @@ -844,10 +850,24 @@ static struct i2c_device_id bma180_ids[] = { MODULE_DEVICE_TABLE(i2c, bma180_ids); +static const struct of_device_id bma180_of_match[] = { + { + .compatible = "bosch,bma180", + .data = (void *)BMA180 + }, + { + .compatible = "bosch,bma250", + .data = (void *)BMA250 + }, + { } +}; +MODULE_DEVICE_TABLE(of, bma180_of_match); + static struct i2c_driver bma180_driver = { .driver = { .name = "bma180", .pm = BMA180_PM_OPS, + .of_match_table = bma180_of_match, }, .probe = bma180_probe, .remove = bma180_remove, diff --git a/drivers/iio/accel/mma7455_i2c.c b/drivers/iio/accel/mma7455_i2c.c index 3cab5fb4a3c4..73bf81a8ab14 100644 --- a/drivers/iio/accel/mma7455_i2c.c +++ b/drivers/iio/accel/mma7455_i2c.c @@ -41,12 +41,20 @@ static const struct i2c_device_id mma7455_i2c_ids[] = { }; MODULE_DEVICE_TABLE(i2c, mma7455_i2c_ids); +static const struct of_device_id mma7455_of_match[] = { + { .compatible = "fsl,mma7455" }, + { .compatible = "fsl,mma7456" }, + { } +}; +MODULE_DEVICE_TABLE(of, mma7455_of_match); + static struct i2c_driver mma7455_i2c_driver = { .probe = mma7455_i2c_probe, .remove = mma7455_i2c_remove, .id_table = mma7455_i2c_ids, .driver = { .name = "mma7455-i2c", + .of_match_table = mma7455_of_match, }, }; module_i2c_driver(mma7455_i2c_driver); diff --git a/drivers/iio/accel/mma7660.c b/drivers/iio/accel/mma7660.c index 3a40774cca74..42fa57e41bdd 100644 --- a/drivers/iio/accel/mma7660.c +++ b/drivers/iio/accel/mma7660.c @@ -253,6 +253,12 @@ static const struct i2c_device_id mma7660_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, mma7660_i2c_id); +static const struct of_device_id mma7660_of_match[] = { + { .compatible = "fsl,mma7660" }, + { } +}; +MODULE_DEVICE_TABLE(of, mma7660_of_match); + static const struct acpi_device_id mma7660_acpi_id[] = { {"MMA7660", 0}, {} @@ -264,6 +270,7 @@ static struct i2c_driver mma7660_driver = { .driver = { .name = "mma7660", .pm = MMA7660_PM_OPS, + .of_match_table = mma7660_of_match, .acpi_match_table = ACPI_PTR(mma7660_acpi_id), }, .probe = mma7660_probe, diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index dedae7adbce9..0b8915298bf1 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -130,6 +130,17 @@ config AD799X To compile this driver as a module, choose M here: the module will be called ad799x. +config ASPEED_ADC + tristate "Aspeed ADC" + depends on ARCH_ASPEED || COMPILE_TEST + depends on COMMON_CLK + help + If you say yes here you get support for the ADC included in Aspeed + BMC SoCs. + + To compile this driver as a module, choose M here: the module will be + called aspeed_adc. + config AT91_ADC tristate "Atmel AT91 ADC" depends on ARCH_AT91 @@ -195,6 +206,17 @@ config CC10001_ADC This driver can also be built as a module. If so, the module will be called cc10001_adc. +config CPCAP_ADC + tristate "Motorola CPCAP PMIC ADC driver" + depends on MFD_CPCAP + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Motorola CPCAP PMIC ADC. + + This driver can also be built as a module. If so, the module will be + called cpcap-adc. + config DA9150_GPADC tristate "Dialog DA9150 GPADC driver support" depends on MFD_DA9150 @@ -305,6 +327,18 @@ config LPC18XX_ADC To compile this driver as a module, choose M here: the module will be called lpc18xx_adc. +config LPC32XX_ADC + tristate "NXP LPC32XX ADC" + depends on ARCH_LPC32XX || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for the integrated ADC inside the + LPC32XX SoC. Note that this feature uses the same hardware as the + touchscreen driver, so you should either select only one of the two + drivers (lpc32xx_adc or lpc32xx_ts) or, in the OpenFirmware case, + activate only one via device tree selection. Provides direct access + via sysfs. + config LTC2485 tristate "Linear Technology LTC2485 ADC driver" depends on I2C @@ -314,6 +348,16 @@ config LTC2485 To compile this driver as a module, choose M here: the module will be called ltc2485. +config LTC2497 + tristate "Linear Technology LTC2497 ADC driver" + depends on I2C + help + Say yes here to build support for Linear Technology LTC2497 + 16-Bit 8-/16-Channel Delta Sigma ADC. + + To compile this driver as a module, choose M here: the module will be + called ltc2497. + config MAX1027 tristate "Maxim max1027 ADC driver" depends on SPI @@ -335,6 +379,18 @@ config MAX11100 To compile this driver as a module, choose M here: the module will be called max11100. +config MAX1118 + tristate "Maxim max1117/max1118/max1119 ADCs driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Maxim max1117/max1118/max1119 + 8-bit, dual-channel ADCs. + + To compile this driver as a module, choose M here: the module will be + called max1118. + config MAX1363 tristate "Maxim max1363 ADC driver" depends on I2C @@ -354,6 +410,16 @@ config MAX1363 To compile this driver as a module, choose M here: the module will be called max1363. +config MAX9611 + tristate "Maxim max9611/max9612 ADC driver" + depends on I2C + help + Say yes here to build support for Maxim max9611/max9612 current sense + amplifier with 12-bits ADC interface. + + To compile this driver as a module, choose M here: the module will be + called max9611. + config MCP320X tristate "Microchip Technology MCP3x01/02/04/08" depends on SPI @@ -442,6 +508,20 @@ config PALMAS_GPADC is used in smartphones and tablets and supports a 16 channel general purpose ADC. +config QCOM_VADC_COMMON + tristate + +config QCOM_PM8XXX_XOADC + tristate "Qualcomm SSBI PM8xxx PMIC XOADCs" + depends on MFD_PM8XXX + select QCOM_VADC_COMMON + help + ADC driver for the XOADC portions of the Qualcomm PM8xxx PMICs + using SSBI transport: PM8018, PM8038, PM8058, PM8921. + + To compile this driver as a module, choose M here: the module + will be called qcom-pm8xxx-xoadc. + config QCOM_SPMI_IADC tristate "Qualcomm SPMI PMIC current ADC" depends on SPMI @@ -460,6 +540,7 @@ config QCOM_SPMI_VADC tristate "Qualcomm SPMI PMIC voltage ADC" depends on SPMI select REGMAP_SPMI + select QCOM_VADC_COMMON help This is the IIO Voltage ADC driver for Qualcomm QPNP VADC Chip. @@ -494,6 +575,17 @@ config ROCKCHIP_SARADC To compile this driver as a module, choose M here: the module will be called rockchip_saradc. +config SPEAR_ADC + tristate "ST SPEAr ADC" + depends on PLAT_SPEAR || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for the integrated ADC inside the + ST SPEAr SoC. Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called spear_adc. + config STM32_ADC_CORE tristate "STMicroelectronics STM32 adc core" depends on ARCH_STM32 || COMPILE_TEST @@ -523,7 +615,7 @@ config STM32_ADC config STX104 tristate "Apex Embedded Systems STX104 driver" - depends on X86 && ISA_BUS_API + depends on PC104 && X86 && ISA_BUS_API select GPIOLIB help Say yes here to build support for the Apex Embedded Systems STX104 @@ -536,6 +628,24 @@ config STX104 The base port addresses for the devices may be configured via the base array module parameter. +config SUN4I_GPADC + tristate "Support for the Allwinner SoCs GPADC" + depends on IIO + depends on MFD_SUN4I_GPADC || MACH_SUN8I + depends on THERMAL || !THERMAL_OF + 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 + a touchscreen input and one channel for thermal sensor. + + The thermal sensor slows down ADC readings and can be disabled by + disabling CONFIG_THERMAL_OF. However, the thermal sensor should be + enabled by default since the SoC temperature is usually more critical + than ADC readings. + + To compile this driver as a module, choose M here: the module will be + called sun4i-gpadc-iio. + config TI_ADC081C tristate "Texas Instruments ADC081C/ADC101C/ADC121C family" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d0012620cd1c..56e5fabece4c 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -14,12 +14,14 @@ obj-$(CONFIG_AD7791) += ad7791.o obj-$(CONFIG_AD7793) += ad7793.o obj-$(CONFIG_AD7887) += ad7887.o obj-$(CONFIG_AD799X) += ad799x.o +obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o obj-$(CONFIG_AT91_ADC) += at91_adc.o obj-$(CONFIG_AT91_SAMA5D2_ADC) += at91-sama5d2_adc.o obj-$(CONFIG_AXP288_ADC) += axp288_adc.o obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o obj-$(CONFIG_BERLIN2_ADC) += berlin2-adc.o obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o +obj-$(CONFIG_CPCAP_ADC) += cpcap-adc.o obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o obj-$(CONFIG_ENVELOPE_DETECTOR) += envelope-detector.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o @@ -30,10 +32,14 @@ obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o +obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o obj-$(CONFIG_LTC2485) += ltc2485.o +obj-$(CONFIG_LTC2497) += ltc2497.o obj-$(CONFIG_MAX1027) += max1027.o obj-$(CONFIG_MAX11100) += max11100.o +obj-$(CONFIG_MAX1118) += max1118.o obj-$(CONFIG_MAX1363) += max1363.o +obj-$(CONFIG_MAX9611) += max9611.o obj-$(CONFIG_MCP320X) += mcp320x.o obj-$(CONFIG_MCP3422) += mcp3422.o obj-$(CONFIG_MEDIATEK_MT6577_AUXADC) += mt6577_auxadc.o @@ -43,10 +49,14 @@ obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o +obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o 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_SPEAR_ADC) += spear_adc.o obj-$(CONFIG_STX104) += stx104.o +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o diff --git a/drivers/iio/adc/ad799x.c b/drivers/iio/adc/ad799x.c index 9704090b7908..22426ae4af97 100644 --- a/drivers/iio/adc/ad799x.c +++ b/drivers/iio/adc/ad799x.c @@ -520,7 +520,7 @@ static struct attribute *ad799x_event_attributes[] = { NULL, }; -static struct attribute_group ad799x_event_attrs_group = { +static const struct attribute_group ad799x_event_attrs_group = { .attrs = ad799x_event_attributes, }; diff --git a/drivers/iio/adc/aspeed_adc.c b/drivers/iio/adc/aspeed_adc.c new file mode 100644 index 000000000000..62670cbfa2bb --- /dev/null +++ b/drivers/iio/adc/aspeed_adc.c @@ -0,0 +1,295 @@ +/* + * Aspeed AST2400/2500 ADC + * + * Copyright (C) 2017 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include <linux/iio/iio.h> +#include <linux/iio/driver.h> + +#define ASPEED_RESOLUTION_BITS 10 +#define ASPEED_CLOCKS_PER_SAMPLE 12 + +#define ASPEED_REG_ENGINE_CONTROL 0x00 +#define ASPEED_REG_INTERRUPT_CONTROL 0x04 +#define ASPEED_REG_VGA_DETECT_CONTROL 0x08 +#define ASPEED_REG_CLOCK_CONTROL 0x0C +#define ASPEED_REG_MAX 0xC0 + +#define ASPEED_OPERATION_MODE_POWER_DOWN (0x0 << 1) +#define ASPEED_OPERATION_MODE_STANDBY (0x1 << 1) +#define ASPEED_OPERATION_MODE_NORMAL (0x7 << 1) + +#define ASPEED_ENGINE_ENABLE BIT(0) + +struct aspeed_adc_model_data { + const char *model_name; + unsigned int min_sampling_rate; // Hz + unsigned int max_sampling_rate; // Hz + unsigned int vref_voltage; // mV +}; + +struct aspeed_adc_data { + struct device *dev; + void __iomem *base; + spinlock_t clk_lock; + struct clk_hw *clk_prescaler; + struct clk_hw *clk_scaler; +}; + +#define ASPEED_CHAN(_idx, _data_reg_addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .address = (_data_reg_addr), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec aspeed_adc_iio_channels[] = { + ASPEED_CHAN(0, 0x10), + ASPEED_CHAN(1, 0x12), + ASPEED_CHAN(2, 0x14), + ASPEED_CHAN(3, 0x16), + ASPEED_CHAN(4, 0x18), + ASPEED_CHAN(5, 0x1A), + ASPEED_CHAN(6, 0x1C), + ASPEED_CHAN(7, 0x1E), + ASPEED_CHAN(8, 0x20), + ASPEED_CHAN(9, 0x22), + ASPEED_CHAN(10, 0x24), + ASPEED_CHAN(11, 0x26), + ASPEED_CHAN(12, 0x28), + ASPEED_CHAN(13, 0x2A), + ASPEED_CHAN(14, 0x2C), + ASPEED_CHAN(15, 0x2E), +}; + +static int aspeed_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct aspeed_adc_data *data = iio_priv(indio_dev); + const struct aspeed_adc_model_data *model_data = + of_device_get_match_data(data->dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = readw(data->base + chan->address); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = model_data->vref_voltage; + *val2 = ASPEED_RESOLUTION_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = clk_get_rate(data->clk_scaler->clk) / + ASPEED_CLOCKS_PER_SAMPLE; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int aspeed_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct aspeed_adc_data *data = iio_priv(indio_dev); + const struct aspeed_adc_model_data *model_data = + of_device_get_match_data(data->dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val < model_data->min_sampling_rate || + val > model_data->max_sampling_rate) + return -EINVAL; + + clk_set_rate(data->clk_scaler->clk, + val * ASPEED_CLOCKS_PER_SAMPLE); + return 0; + + case IIO_CHAN_INFO_SCALE: + case IIO_CHAN_INFO_RAW: + /* + * Technically, these could be written but the only reasons + * for doing so seem better handled in userspace. EPERM is + * returned to signal this is a policy choice rather than a + * hardware limitation. + */ + return -EPERM; + + default: + return -EINVAL; + } +} + +static int aspeed_adc_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct aspeed_adc_data *data = iio_priv(indio_dev); + + if (!readval || reg % 4 || reg > ASPEED_REG_MAX) + return -EINVAL; + + *readval = readl(data->base + reg); + + return 0; +} + +static const struct iio_info aspeed_adc_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = aspeed_adc_read_raw, + .write_raw = aspeed_adc_write_raw, + .debugfs_reg_access = aspeed_adc_reg_access, +}; + +static int aspeed_adc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct aspeed_adc_data *data; + const struct aspeed_adc_model_data *model_data; + struct resource *res; + const char *clk_parent_name; + int ret; + u32 adc_engine_control_reg_val; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + /* Register ADC clock prescaler with source specified by device tree. */ + spin_lock_init(&data->clk_lock); + clk_parent_name = of_clk_get_parent_name(pdev->dev.of_node, 0); + + data->clk_prescaler = clk_hw_register_divider( + &pdev->dev, "prescaler", clk_parent_name, 0, + data->base + ASPEED_REG_CLOCK_CONTROL, + 17, 15, 0, &data->clk_lock); + if (IS_ERR(data->clk_prescaler)) + return PTR_ERR(data->clk_prescaler); + + /* + * Register ADC clock scaler downstream from the prescaler. Allow rate + * setting to adjust the prescaler as well. + */ + data->clk_scaler = clk_hw_register_divider( + &pdev->dev, "scaler", "prescaler", + CLK_SET_RATE_PARENT, + data->base + ASPEED_REG_CLOCK_CONTROL, + 0, 10, 0, &data->clk_lock); + if (IS_ERR(data->clk_scaler)) { + ret = PTR_ERR(data->clk_scaler); + goto scaler_error; + } + + /* Start all channels in normal mode. */ + clk_prepare_enable(data->clk_scaler->clk); + adc_engine_control_reg_val = GENMASK(31, 16) | + ASPEED_OPERATION_MODE_NORMAL | ASPEED_ENGINE_ENABLE; + writel(adc_engine_control_reg_val, + data->base + ASPEED_REG_ENGINE_CONTROL); + + model_data = of_device_get_match_data(&pdev->dev); + indio_dev->name = model_data->model_name; + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &aspeed_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = aspeed_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(aspeed_adc_iio_channels); + + ret = iio_device_register(indio_dev); + if (ret) + goto iio_register_error; + + return 0; + +iio_register_error: + writel(ASPEED_OPERATION_MODE_POWER_DOWN, + data->base + ASPEED_REG_ENGINE_CONTROL); + clk_disable_unprepare(data->clk_scaler->clk); + clk_hw_unregister_divider(data->clk_scaler); + +scaler_error: + clk_hw_unregister_divider(data->clk_prescaler); + return ret; +} + +static int aspeed_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct aspeed_adc_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + writel(ASPEED_OPERATION_MODE_POWER_DOWN, + data->base + ASPEED_REG_ENGINE_CONTROL); + clk_disable_unprepare(data->clk_scaler->clk); + clk_hw_unregister_divider(data->clk_scaler); + clk_hw_unregister_divider(data->clk_prescaler); + + return 0; +} + +static const struct aspeed_adc_model_data ast2400_model_data = { + .model_name = "ast2400-adc", + .vref_voltage = 2500, // mV + .min_sampling_rate = 10000, + .max_sampling_rate = 500000, +}; + +static const struct aspeed_adc_model_data ast2500_model_data = { + .model_name = "ast2500-adc", + .vref_voltage = 1800, // mV + .min_sampling_rate = 1, + .max_sampling_rate = 1000000, +}; + +static const struct of_device_id aspeed_adc_matches[] = { + { .compatible = "aspeed,ast2400-adc", .data = &ast2400_model_data }, + { .compatible = "aspeed,ast2500-adc", .data = &ast2500_model_data }, + {}, +}; +MODULE_DEVICE_TABLE(of, aspeed_adc_matches); + +static struct platform_driver aspeed_adc_driver = { + .probe = aspeed_adc_probe, + .remove = aspeed_adc_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_adc_matches, + } +}; + +module_platform_driver(aspeed_adc_driver); + +MODULE_AUTHOR("Rick Altherr <raltherr@google.com>"); +MODULE_DESCRIPTION("Aspeed AST2400/2500 ADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/cpcap-adc.c b/drivers/iio/adc/cpcap-adc.c new file mode 100644 index 000000000000..62d37f8725b8 --- /dev/null +++ b/drivers/iio/adc/cpcap-adc.c @@ -0,0 +1,1007 @@ +/* + * Copyright (C) 2017 Tony Lindgren <tony@atomide.com> + * + * Rewritten for Linux IIO framework with some code based on + * earlier driver found in the Motorola Linux kernel: + * + * Copyright (C) 2009-2010 Motorola, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/driver.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/mfd/motorola-cpcap.h> + +/* Register CPCAP_REG_ADCC1 bits */ +#define CPCAP_BIT_ADEN_AUTO_CLR BIT(15) /* Currently unused */ +#define CPCAP_BIT_CAL_MODE BIT(14) /* Set with BIT_RAND0 */ +#define CPCAP_BIT_ADC_CLK_SEL1 BIT(13) /* Currently unused */ +#define CPCAP_BIT_ADC_CLK_SEL0 BIT(12) /* Currently unused */ +#define CPCAP_BIT_ATOX BIT(11) +#define CPCAP_BIT_ATO3 BIT(10) +#define CPCAP_BIT_ATO2 BIT(9) +#define CPCAP_BIT_ATO1 BIT(8) +#define CPCAP_BIT_ATO0 BIT(7) +#define CPCAP_BIT_ADA2 BIT(6) +#define CPCAP_BIT_ADA1 BIT(5) +#define CPCAP_BIT_ADA0 BIT(4) +#define CPCAP_BIT_AD_SEL1 BIT(3) /* Set for bank1 */ +#define CPCAP_BIT_RAND1 BIT(2) /* Set for channel 16 & 17 */ +#define CPCAP_BIT_RAND0 BIT(1) /* Set with CAL_MODE */ +#define CPCAP_BIT_ADEN BIT(0) /* Currently unused */ + +/* Register CPCAP_REG_ADCC2 bits */ +#define CPCAP_BIT_CAL_FACTOR_ENABLE BIT(15) /* Currently unused */ +#define CPCAP_BIT_BATDETB_EN BIT(14) /* Currently unused */ +#define CPCAP_BIT_ADTRIG_ONESHOT BIT(13) /* Set for !TIMING_IMM */ +#define CPCAP_BIT_ASC BIT(12) /* Set for TIMING_IMM */ +#define CPCAP_BIT_ATOX_PS_FACTOR BIT(11) +#define CPCAP_BIT_ADC_PS_FACTOR1 BIT(10) +#define CPCAP_BIT_ADC_PS_FACTOR0 BIT(9) +#define CPCAP_BIT_AD4_SELECT BIT(8) /* Currently unused */ +#define CPCAP_BIT_ADC_BUSY BIT(7) /* Currently unused */ +#define CPCAP_BIT_THERMBIAS_EN BIT(6) /* Currently unused */ +#define CPCAP_BIT_ADTRIG_DIS BIT(5) /* Disable interrupt */ +#define CPCAP_BIT_LIADC BIT(4) /* Currently unused */ +#define CPCAP_BIT_TS_REFEN BIT(3) /* Currently unused */ +#define CPCAP_BIT_TS_M2 BIT(2) /* Currently unused */ +#define CPCAP_BIT_TS_M1 BIT(1) /* Currently unused */ +#define CPCAP_BIT_TS_M0 BIT(0) /* Currently unused */ + +#define CPCAP_MAX_TEMP_LVL 27 +#define CPCAP_FOUR_POINT_TWO_ADC 801 +#define ST_ADC_CAL_CHRGI_HIGH_THRESHOLD 530 +#define ST_ADC_CAL_CHRGI_LOW_THRESHOLD 494 +#define ST_ADC_CAL_BATTI_HIGH_THRESHOLD 530 +#define ST_ADC_CAL_BATTI_LOW_THRESHOLD 494 +#define ST_ADC_CALIBRATE_DIFF_THRESHOLD 3 + +#define CPCAP_ADC_MAX_RETRIES 5 /* Calibration and quirk */ + +/** + * struct cpcap_adc_ato - timing settings for cpcap adc + * + * Unfortunately no cpcap documentation available, please document when + * using these. + */ +struct cpcap_adc_ato { + unsigned short ato_in; + unsigned short atox_in; + unsigned short adc_ps_factor_in; + unsigned short atox_ps_factor_in; + unsigned short ato_out; + unsigned short atox_out; + unsigned short adc_ps_factor_out; + unsigned short atox_ps_factor_out; +}; + +/** + * struct cpcap-adc - cpcap adc device driver data + * @reg: cpcap regmap + * @dev: struct device + * @vendor: cpcap vendor + * @irq: interrupt + * @lock: mutex + * @ato: request timings + * @wq_data_avail: work queue + * @done: work done + */ +struct cpcap_adc { + struct regmap *reg; + struct device *dev; + u16 vendor; + int irq; + struct mutex lock; /* ADC register access lock */ + const struct cpcap_adc_ato *ato; + wait_queue_head_t wq_data_avail; + bool done; +}; + +/** + * enum cpcap_adc_channel - cpcap adc channels + */ +enum cpcap_adc_channel { + /* Bank0 channels */ + CPCAP_ADC_AD0_BATTDETB, /* Battery detection */ + CPCAP_ADC_BATTP, /* Battery voltage */ + CPCAP_ADC_VBUS, /* USB VBUS voltage */ + CPCAP_ADC_AD3, /* Battery temperature when charging */ + CPCAP_ADC_BPLUS_AD4, /* Another battery or system voltage */ + CPCAP_ADC_CHG_ISENSE, /* Calibrated charge current */ + CPCAP_ADC_BATTI, /* Calibrated system current */ + CPCAP_ADC_USB_ID, /* USB OTG ID, unused on droid 4? */ + + /* Bank1 channels */ + CPCAP_ADC_AD8, /* Seems unused */ + CPCAP_ADC_AD9, /* Seems unused */ + CPCAP_ADC_LICELL, /* Maybe system voltage? Always 3V */ + CPCAP_ADC_HV_BATTP, /* Another battery detection? */ + CPCAP_ADC_TSX1_AD12, /* Seems unused, for touchscreen? */ + CPCAP_ADC_TSX2_AD13, /* Seems unused, for touchscreen? */ + CPCAP_ADC_TSY1_AD14, /* Seems unused, for touchscreen? */ + CPCAP_ADC_TSY2_AD15, /* Seems unused, for touchscreen? */ + + /* Remuxed channels using bank0 entries */ + CPCAP_ADC_BATTP_PI16, /* Alternative mux mode for BATTP */ + CPCAP_ADC_BATTI_PI17, /* Alternative mux mode for BATTI */ + + CPCAP_ADC_CHANNEL_NUM, +}; + +/** + * enum cpcap_adc_timing - cpcap adc timing options + * + * CPCAP_ADC_TIMING_IMM seems to be immediate with no timings. + * Please document when using. + */ +enum cpcap_adc_timing { + CPCAP_ADC_TIMING_IMM, + CPCAP_ADC_TIMING_IN, + CPCAP_ADC_TIMING_OUT, +}; + +/** + * struct cpcap_adc_phasing_tbl - cpcap phasing table + * @offset: offset in the phasing table + * @multiplier: multiplier in the phasing table + * @divider: divider in the phasing table + * @min: minimum value + * @max: maximum value + */ +struct cpcap_adc_phasing_tbl { + short offset; + unsigned short multiplier; + unsigned short divider; + short min; + short max; +}; + +/** + * struct cpcap_adc_conversion_tbl - cpcap conversion table + * @conv_type: conversion type + * @align_offset: align offset + * @conv_offset: conversion offset + * @cal_offset: calibration offset + * @multiplier: conversion multiplier + * @divider: conversion divider + */ +struct cpcap_adc_conversion_tbl { + enum iio_chan_info_enum conv_type; + int align_offset; + int conv_offset; + int cal_offset; + int multiplier; + int divider; +}; + +/** + * struct cpcap_adc_request - cpcap adc request + * @channel: request channel + * @phase_tbl: channel phasing table + * @conv_tbl: channel conversion table + * @bank_index: channel index within the bank + * @timing: timing settings + * @result: result + */ +struct cpcap_adc_request { + int channel; + const struct cpcap_adc_phasing_tbl *phase_tbl; + const struct cpcap_adc_conversion_tbl *conv_tbl; + int bank_index; + enum cpcap_adc_timing timing; + int result; +}; + +/* Phasing table for channels. Note that channels 16 & 17 use BATTP and BATTI */ +static const struct cpcap_adc_phasing_tbl bank_phasing[] = { + /* Bank0 */ + [CPCAP_ADC_AD0_BATTDETB] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_BATTP] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_VBUS] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_AD3] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_BPLUS_AD4] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_CHG_ISENSE] = {0, 0x80, 0x80, -512, 511}, + [CPCAP_ADC_BATTI] = {0, 0x80, 0x80, -512, 511}, + [CPCAP_ADC_USB_ID] = {0, 0x80, 0x80, 0, 1023}, + + /* Bank1 */ + [CPCAP_ADC_AD8] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_AD9] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_LICELL] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_HV_BATTP] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSX1_AD12] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSX2_AD13] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSY1_AD14] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSY2_AD15] = {0, 0x80, 0x80, 0, 1023}, +}; + +/* + * Conversion table for channels. Updated during init based on calibration. + * Here too channels 16 & 17 use BATTP and BATTI. + */ +static struct cpcap_adc_conversion_tbl bank_conversion[] = { + /* Bank0 */ + [CPCAP_ADC_AD0_BATTDETB] = { + IIO_CHAN_INFO_PROCESSED, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_BATTP] = { + IIO_CHAN_INFO_PROCESSED, 0, 2400, 0, 2300, 1023, + }, + [CPCAP_ADC_VBUS] = { + IIO_CHAN_INFO_PROCESSED, 0, 0, 0, 10000, 1023, + }, + [CPCAP_ADC_AD3] = { + IIO_CHAN_INFO_PROCESSED, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_BPLUS_AD4] = { + IIO_CHAN_INFO_PROCESSED, 0, 2400, 0, 2300, 1023, + }, + [CPCAP_ADC_CHG_ISENSE] = { + IIO_CHAN_INFO_PROCESSED, -512, 2, 0, 5000, 1023, + }, + [CPCAP_ADC_BATTI] = { + IIO_CHAN_INFO_PROCESSED, -512, 2, 0, 5000, 1023, + }, + [CPCAP_ADC_USB_ID] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + + /* Bank1 */ + [CPCAP_ADC_AD8] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_AD9] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_LICELL] = { + IIO_CHAN_INFO_PROCESSED, 0, 0, 0, 3400, 1023, + }, + [CPCAP_ADC_HV_BATTP] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_TSX1_AD12] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_TSX2_AD13] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_TSY1_AD14] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_TSY2_AD15] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, +}; + +/* + * Temperature lookup table of register values to milliCelcius. + * REVISIT: Check the duplicate 0x3ff entry in a freezer + */ +static const int temp_map[CPCAP_MAX_TEMP_LVL][2] = { + { 0x03ff, -40000 }, + { 0x03ff, -35000 }, + { 0x03ef, -30000 }, + { 0x03b2, -25000 }, + { 0x036c, -20000 }, + { 0x0320, -15000 }, + { 0x02d0, -10000 }, + { 0x027f, -5000 }, + { 0x022f, 0 }, + { 0x01e4, 5000 }, + { 0x019f, 10000 }, + { 0x0161, 15000 }, + { 0x012b, 20000 }, + { 0x00fc, 25000 }, + { 0x00d4, 30000 }, + { 0x00b2, 35000 }, + { 0x0095, 40000 }, + { 0x007d, 45000 }, + { 0x0069, 50000 }, + { 0x0059, 55000 }, + { 0x004b, 60000 }, + { 0x003f, 65000 }, + { 0x0036, 70000 }, + { 0x002e, 75000 }, + { 0x0027, 80000 }, + { 0x0022, 85000 }, + { 0x001d, 90000 }, +}; + +#define CPCAP_CHAN(_type, _index, _address, _datasheet_name) { \ + .type = (_type), \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 10, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + .datasheet_name = (_datasheet_name), \ +} + +/* + * The datasheet names are from Motorola mapphone Linux kernel except + * for the last two which might be uncalibrated charge voltage and + * current. + */ +static const struct iio_chan_spec cpcap_adc_channels[] = { + /* Bank0 */ + CPCAP_CHAN(IIO_TEMP, 0, CPCAP_REG_ADCD0, "battdetb"), + CPCAP_CHAN(IIO_VOLTAGE, 1, CPCAP_REG_ADCD1, "battp"), + CPCAP_CHAN(IIO_VOLTAGE, 2, CPCAP_REG_ADCD2, "vbus"), + CPCAP_CHAN(IIO_TEMP, 3, CPCAP_REG_ADCD3, "ad3"), + CPCAP_CHAN(IIO_VOLTAGE, 4, CPCAP_REG_ADCD4, "ad4"), + CPCAP_CHAN(IIO_CURRENT, 5, CPCAP_REG_ADCD5, "chg_isense"), + CPCAP_CHAN(IIO_CURRENT, 6, CPCAP_REG_ADCD6, "batti"), + CPCAP_CHAN(IIO_VOLTAGE, 7, CPCAP_REG_ADCD7, "usb_id"), + + /* Bank1 */ + CPCAP_CHAN(IIO_CURRENT, 8, CPCAP_REG_ADCD0, "ad8"), + CPCAP_CHAN(IIO_VOLTAGE, 9, CPCAP_REG_ADCD1, "ad9"), + CPCAP_CHAN(IIO_VOLTAGE, 10, CPCAP_REG_ADCD2, "licell"), + CPCAP_CHAN(IIO_VOLTAGE, 11, CPCAP_REG_ADCD3, "hv_battp"), + CPCAP_CHAN(IIO_VOLTAGE, 12, CPCAP_REG_ADCD4, "tsx1_ad12"), + CPCAP_CHAN(IIO_VOLTAGE, 13, CPCAP_REG_ADCD5, "tsx2_ad13"), + CPCAP_CHAN(IIO_VOLTAGE, 14, CPCAP_REG_ADCD6, "tsy1_ad14"), + CPCAP_CHAN(IIO_VOLTAGE, 15, CPCAP_REG_ADCD7, "tsy2_ad15"), + + /* There are two registers with multiplexed functionality */ + CPCAP_CHAN(IIO_VOLTAGE, 16, CPCAP_REG_ADCD0, "chg_vsense"), + CPCAP_CHAN(IIO_CURRENT, 17, CPCAP_REG_ADCD1, "batti2"), +}; + +static irqreturn_t cpcap_adc_irq_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct cpcap_adc *ddata = iio_priv(indio_dev); + int error; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS, + CPCAP_BIT_ADTRIG_DIS); + if (error) + return IRQ_NONE; + + ddata->done = true; + wake_up_interruptible(&ddata->wq_data_avail); + + return IRQ_HANDLED; +} + +/* ADC calibration functions */ +static void cpcap_adc_setup_calibrate(struct cpcap_adc *ddata, + enum cpcap_adc_channel chan) +{ + unsigned int value = 0; + unsigned long timeout = jiffies + msecs_to_jiffies(3000); + int error; + + if ((chan != CPCAP_ADC_CHG_ISENSE) && + (chan != CPCAP_ADC_BATTI)) + return; + + value |= CPCAP_BIT_CAL_MODE | CPCAP_BIT_RAND0; + value |= ((chan << 4) & + (CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | CPCAP_BIT_ADA0)); + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC1, + CPCAP_BIT_CAL_MODE | CPCAP_BIT_ATOX | + CPCAP_BIT_ATO3 | CPCAP_BIT_ATO2 | + CPCAP_BIT_ATO1 | CPCAP_BIT_ATO0 | + CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | + CPCAP_BIT_ADA0 | CPCAP_BIT_AD_SEL1 | + CPCAP_BIT_RAND1 | CPCAP_BIT_RAND0, + value); + if (error) + return; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ATOX_PS_FACTOR | + CPCAP_BIT_ADC_PS_FACTOR1 | + CPCAP_BIT_ADC_PS_FACTOR0, + 0); + if (error) + return; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS, + CPCAP_BIT_ADTRIG_DIS); + if (error) + return; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ASC, + CPCAP_BIT_ASC); + if (error) + return; + + do { + schedule_timeout_uninterruptible(1); + error = regmap_read(ddata->reg, CPCAP_REG_ADCC2, &value); + if (error) + return; + } while ((value & CPCAP_BIT_ASC) && time_before(jiffies, timeout)); + + if (value & CPCAP_BIT_ASC) + dev_err(ddata->dev, + "Timeout waiting for calibration to complete\n"); + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC1, + CPCAP_BIT_CAL_MODE, 0); + if (error) + return; +} + +static int cpcap_adc_calibrate_one(struct cpcap_adc *ddata, + int channel, + u16 calibration_register, + int lower_threshold, + int upper_threshold) +{ + unsigned int calibration_data[2]; + unsigned short cal_data_diff; + int i, error; + + for (i = 0; i < CPCAP_ADC_MAX_RETRIES; i++) { + calibration_data[0] = 0; + calibration_data[1] = 0; + cal_data_diff = 0; + cpcap_adc_setup_calibrate(ddata, channel); + error = regmap_read(ddata->reg, calibration_register, + &calibration_data[0]); + if (error) + return error; + cpcap_adc_setup_calibrate(ddata, channel); + error = regmap_read(ddata->reg, calibration_register, + &calibration_data[1]); + if (error) + return error; + + if (calibration_data[0] > calibration_data[1]) + cal_data_diff = + calibration_data[0] - calibration_data[1]; + else + cal_data_diff = + calibration_data[1] - calibration_data[0]; + + if (((calibration_data[1] >= lower_threshold) && + (calibration_data[1] <= upper_threshold) && + (cal_data_diff <= ST_ADC_CALIBRATE_DIFF_THRESHOLD)) || + (ddata->vendor == CPCAP_VENDOR_TI)) { + bank_conversion[channel].cal_offset = + ((short)calibration_data[1] * -1) + 512; + dev_dbg(ddata->dev, "ch%i calibration complete: %i\n", + channel, bank_conversion[channel].cal_offset); + break; + } + usleep_range(5000, 10000); + } + + return 0; +} + +static int cpcap_adc_calibrate(struct cpcap_adc *ddata) +{ + int error; + + error = cpcap_adc_calibrate_one(ddata, CPCAP_ADC_CHG_ISENSE, + CPCAP_REG_ADCAL1, + ST_ADC_CAL_CHRGI_LOW_THRESHOLD, + ST_ADC_CAL_CHRGI_HIGH_THRESHOLD); + if (error) + return error; + + error = cpcap_adc_calibrate_one(ddata, CPCAP_ADC_BATTI, + CPCAP_REG_ADCAL2, + ST_ADC_CAL_BATTI_LOW_THRESHOLD, + ST_ADC_CAL_BATTI_HIGH_THRESHOLD); + if (error) + return error; + + return 0; +} + +/* ADC setup, read and scale functions */ +static void cpcap_adc_setup_bank(struct cpcap_adc *ddata, + struct cpcap_adc_request *req) +{ + const struct cpcap_adc_ato *ato = ddata->ato; + unsigned short value1 = 0; + unsigned short value2 = 0; + int error; + + if (!ato) + return; + + switch (req->channel) { + case CPCAP_ADC_AD8 ... CPCAP_ADC_TSY2_AD15: + value1 |= CPCAP_BIT_AD_SEL1; + break; + case CPCAP_ADC_BATTP_PI16 ... CPCAP_ADC_BATTI_PI17: + value1 |= CPCAP_BIT_RAND1; + default: + break; + } + + switch (req->timing) { + case CPCAP_ADC_TIMING_IN: + value1 |= ato->ato_in; + value1 |= ato->atox_in; + value2 |= ato->adc_ps_factor_in; + value2 |= ato->atox_ps_factor_in; + break; + case CPCAP_ADC_TIMING_OUT: + value1 |= ato->ato_out; + value1 |= ato->atox_out; + value2 |= ato->adc_ps_factor_out; + value2 |= ato->atox_ps_factor_out; + break; + + case CPCAP_ADC_TIMING_IMM: + default: + break; + } + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC1, + CPCAP_BIT_CAL_MODE | CPCAP_BIT_ATOX | + CPCAP_BIT_ATO3 | CPCAP_BIT_ATO2 | + CPCAP_BIT_ATO1 | CPCAP_BIT_ATO0 | + CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | + CPCAP_BIT_ADA0 | CPCAP_BIT_AD_SEL1 | + CPCAP_BIT_RAND1 | CPCAP_BIT_RAND0, + value1); + if (error) + return; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ATOX_PS_FACTOR | + CPCAP_BIT_ADC_PS_FACTOR1 | + CPCAP_BIT_ADC_PS_FACTOR0, + value2); + if (error) + return; + + if (req->timing == CPCAP_ADC_TIMING_IMM) { + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS, + CPCAP_BIT_ADTRIG_DIS); + if (error) + return; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ASC, + CPCAP_BIT_ASC); + if (error) + return; + } else { + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_ONESHOT, + CPCAP_BIT_ADTRIG_ONESHOT); + if (error) + return; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS, 0); + if (error) + return; + } +} + +/* + * Occasionally the ADC does not seem to start and there will be no + * interrupt. Let's re-init interrupt to prevent the ADC from hanging + * for the next request. It is unclear why this happens, but the next + * request will usually work after doing this. + */ +static void cpcap_adc_quirk_reset_lost_irq(struct cpcap_adc *ddata) +{ + int error; + + dev_info(ddata->dev, "lost ADC irq, attempting to reinit\n"); + disable_irq(ddata->irq); + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS, + CPCAP_BIT_ADTRIG_DIS); + if (error) + dev_warn(ddata->dev, "%s reset failed: %i\n", + __func__, error); + enable_irq(ddata->irq); +} + +static int cpcap_adc_start_bank(struct cpcap_adc *ddata, + struct cpcap_adc_request *req) +{ + int i, error; + + req->timing = CPCAP_ADC_TIMING_IMM; + ddata->done = false; + + for (i = 0; i < CPCAP_ADC_MAX_RETRIES; i++) { + cpcap_adc_setup_bank(ddata, req); + error = wait_event_interruptible_timeout(ddata->wq_data_avail, + ddata->done, + msecs_to_jiffies(50)); + if (error > 0) + return 0; + + if (error == 0) { + cpcap_adc_quirk_reset_lost_irq(ddata); + error = -ETIMEDOUT; + continue; + } + + if (error < 0) + return error; + } + + return error; +} + +static void cpcap_adc_phase(struct cpcap_adc_request *req) +{ + const struct cpcap_adc_conversion_tbl *conv_tbl = req->conv_tbl; + const struct cpcap_adc_phasing_tbl *phase_tbl = req->phase_tbl; + int index = req->channel; + + /* Remuxed channels 16 and 17 use BATTP and BATTI entries */ + switch (req->channel) { + case CPCAP_ADC_BATTP: + case CPCAP_ADC_BATTP_PI16: + index = req->bank_index; + req->result -= phase_tbl[index].offset; + req->result -= CPCAP_FOUR_POINT_TWO_ADC; + req->result *= phase_tbl[index].multiplier; + if (phase_tbl[index].divider == 0) + return; + req->result /= phase_tbl[index].divider; + req->result += CPCAP_FOUR_POINT_TWO_ADC; + break; + case CPCAP_ADC_BATTI_PI17: + index = req->bank_index; + /* fallthrough */ + default: + req->result += conv_tbl[index].cal_offset; + req->result += conv_tbl[index].align_offset; + req->result *= phase_tbl[index].multiplier; + if (phase_tbl[index].divider == 0) + return; + req->result /= phase_tbl[index].divider; + req->result += phase_tbl[index].offset; + break; + } + + if (req->result < phase_tbl[index].min) + req->result = phase_tbl[index].min; + else if (req->result > phase_tbl[index].max) + req->result = phase_tbl[index].max; +} + +/* Looks up temperatures in a table and calculates averages if needed */ +static int cpcap_adc_table_to_millicelcius(unsigned short value) +{ + int i, result = 0, alpha; + + if (value <= temp_map[CPCAP_MAX_TEMP_LVL - 1][0]) + return temp_map[CPCAP_MAX_TEMP_LVL - 1][1]; + + if (value >= temp_map[0][0]) + return temp_map[0][1]; + + for (i = 0; i < CPCAP_MAX_TEMP_LVL - 1; i++) { + if ((value <= temp_map[i][0]) && + (value >= temp_map[i + 1][0])) { + if (value == temp_map[i][0]) { + result = temp_map[i][1]; + } else if (value == temp_map[i + 1][0]) { + result = temp_map[i + 1][1]; + } else { + alpha = ((value - temp_map[i][0]) * 1000) / + (temp_map[i + 1][0] - temp_map[i][0]); + + result = temp_map[i][1] + + ((alpha * (temp_map[i + 1][1] - + temp_map[i][1])) / 1000); + } + break; + } + } + + return result; +} + +static void cpcap_adc_convert(struct cpcap_adc_request *req) +{ + const struct cpcap_adc_conversion_tbl *conv_tbl = req->conv_tbl; + int index = req->channel; + + /* Remuxed channels 16 and 17 use BATTP and BATTI entries */ + switch (req->channel) { + case CPCAP_ADC_BATTP_PI16: + index = CPCAP_ADC_BATTP; + break; + case CPCAP_ADC_BATTI_PI17: + index = CPCAP_ADC_BATTI; + break; + default: + break; + } + + /* No conversion for raw channels */ + if (conv_tbl[index].conv_type == IIO_CHAN_INFO_RAW) + return; + + /* Temperatures use a lookup table instead of conversion table */ + if ((req->channel == CPCAP_ADC_AD0_BATTDETB) || + (req->channel == CPCAP_ADC_AD3)) { + req->result = + cpcap_adc_table_to_millicelcius(req->result); + + return; + } + + /* All processed channels use a conversion table */ + req->result *= conv_tbl[index].multiplier; + if (conv_tbl[index].divider == 0) + return; + req->result /= conv_tbl[index].divider; + req->result += conv_tbl[index].conv_offset; +} + +/* + * REVISIT: Check if timed sampling can use multiple channels at the + * same time. If not, replace channel_mask with just channel. + */ +static int cpcap_adc_read_bank_scaled(struct cpcap_adc *ddata, + struct cpcap_adc_request *req) +{ + int calibration_data, error, addr; + + if (ddata->vendor == CPCAP_VENDOR_TI) { + error = regmap_read(ddata->reg, CPCAP_REG_ADCAL1, + &calibration_data); + if (error) + return error; + bank_conversion[CPCAP_ADC_CHG_ISENSE].cal_offset = + ((short)calibration_data * -1) + 512; + + error = regmap_read(ddata->reg, CPCAP_REG_ADCAL2, + &calibration_data); + if (error) + return error; + bank_conversion[CPCAP_ADC_BATTI].cal_offset = + ((short)calibration_data * -1) + 512; + } + + addr = CPCAP_REG_ADCD0 + req->bank_index * 4; + + error = regmap_read(ddata->reg, addr, &req->result); + if (error) + return error; + + req->result &= 0x3ff; + cpcap_adc_phase(req); + cpcap_adc_convert(req); + + return 0; +} + +static int cpcap_adc_init_request(struct cpcap_adc_request *req, + int channel) +{ + req->channel = channel; + req->phase_tbl = bank_phasing; + req->conv_tbl = bank_conversion; + + switch (channel) { + case CPCAP_ADC_AD0_BATTDETB ... CPCAP_ADC_USB_ID: + req->bank_index = channel; + break; + case CPCAP_ADC_AD8 ... CPCAP_ADC_TSY2_AD15: + req->bank_index = channel - 8; + break; + case CPCAP_ADC_BATTP_PI16: + req->bank_index = CPCAP_ADC_BATTP; + break; + case CPCAP_ADC_BATTI_PI17: + req->bank_index = CPCAP_ADC_BATTI; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cpcap_adc_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cpcap_adc *ddata = iio_priv(indio_dev); + struct cpcap_adc_request req; + int error; + + error = cpcap_adc_init_request(&req, chan->channel); + if (error) + return error; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&ddata->lock); + error = cpcap_adc_start_bank(ddata, &req); + if (error) + goto err_unlock; + error = regmap_read(ddata->reg, chan->address, val); + if (error) + goto err_unlock; + mutex_unlock(&ddata->lock); + break; + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&ddata->lock); + error = cpcap_adc_start_bank(ddata, &req); + if (error) + goto err_unlock; + error = cpcap_adc_read_bank_scaled(ddata, &req); + if (error) + goto err_unlock; + mutex_unlock(&ddata->lock); + *val = req.result; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; + +err_unlock: + mutex_unlock(&ddata->lock); + dev_err(ddata->dev, "error reading ADC: %i\n", error); + + return error; +} + +static const struct iio_info cpcap_adc_info = { + .read_raw = &cpcap_adc_read, + .driver_module = THIS_MODULE, +}; + +/* + * Configuration for Motorola mapphone series such as droid 4. + * Copied from the Motorola mapphone kernel tree. + */ +static const struct cpcap_adc_ato mapphone_adc = { + .ato_in = 0x0480, + .atox_in = 0, + .adc_ps_factor_in = 0x0200, + .atox_ps_factor_in = 0, + .ato_out = 0, + .atox_out = 0, + .adc_ps_factor_out = 0, + .atox_ps_factor_out = 0, +}; + +static const struct of_device_id cpcap_adc_id_table[] = { + { + .compatible = "motorola,cpcap-adc", + }, + { + .compatible = "motorola,mapphone-cpcap-adc", + .data = &mapphone_adc, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, cpcap_adc_id_table); + +static int cpcap_adc_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct cpcap_adc *ddata; + struct iio_dev *indio_dev; + int error; + + match = of_match_device(of_match_ptr(cpcap_adc_id_table), + &pdev->dev); + if (!match) + return -EINVAL; + + if (!match->data) { + dev_err(&pdev->dev, "no configuration data found\n"); + + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ddata)); + if (!indio_dev) { + dev_err(&pdev->dev, "failed to allocate iio device\n"); + + return -ENOMEM; + } + ddata = iio_priv(indio_dev); + ddata->ato = match->data; + ddata->dev = &pdev->dev; + + mutex_init(&ddata->lock); + init_waitqueue_head(&ddata->wq_data_avail); + + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->channels = cpcap_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(cpcap_adc_channels); + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &cpcap_adc_info; + + ddata->reg = dev_get_regmap(pdev->dev.parent, NULL); + if (!ddata->reg) + return -ENODEV; + + error = cpcap_get_vendor(ddata->dev, ddata->reg, &ddata->vendor); + if (error) + return error; + + platform_set_drvdata(pdev, indio_dev); + + ddata->irq = platform_get_irq_byname(pdev, "adcdone"); + if (!ddata->irq) + return -ENODEV; + + error = devm_request_threaded_irq(&pdev->dev, ddata->irq, NULL, + cpcap_adc_irq_thread, + IRQF_TRIGGER_NONE, + "cpcap-adc", indio_dev); + if (error) { + dev_err(&pdev->dev, "could not get irq: %i\n", + error); + + return error; + } + + error = cpcap_adc_calibrate(ddata); + if (error) + return error; + + dev_info(&pdev->dev, "CPCAP ADC device probed\n"); + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver cpcap_adc_driver = { + .driver = { + .name = "cpcap_adc", + .of_match_table = of_match_ptr(cpcap_adc_id_table), + }, + .probe = cpcap_adc_probe, +}; + +module_platform_driver(cpcap_adc_driver); + +MODULE_ALIAS("platform:cpcap_adc"); +MODULE_DESCRIPTION("CPCAP ADC driver"); +MODULE_AUTHOR("Tony Lindgren <tony@atomide.com"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index ad1775b5f83c..6c5a7be9f8c1 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -579,7 +579,7 @@ static int exynos_read_s3c64xx_ts(struct iio_dev *indio_dev, int *x, int *y) static irqreturn_t exynos_adc_isr(int irq, void *dev_id) { - struct exynos_adc *info = (struct exynos_adc *)dev_id; + struct exynos_adc *info = dev_id; u32 mask = info->data->mask; /* Read value */ diff --git a/drivers/iio/adc/hx711.c b/drivers/iio/adc/hx711.c index 139639f73769..27005d84ed73 100644 --- a/drivers/iio/adc/hx711.c +++ b/drivers/iio/adc/hx711.c @@ -369,7 +369,7 @@ static struct attribute *hx711_attributes[] = { NULL, }; -static struct attribute_group hx711_attribute_group = { +static const struct attribute_group hx711_attribute_group = { .attrs = hx711_attributes, }; diff --git a/drivers/iio/adc/imx7d_adc.c b/drivers/iio/adc/imx7d_adc.c index e2241ee94783..254b29a68b9d 100644 --- a/drivers/iio/adc/imx7d_adc.c +++ b/drivers/iio/adc/imx7d_adc.c @@ -365,7 +365,7 @@ static int imx7d_adc_read_data(struct imx7d_adc *info) static irqreturn_t imx7d_adc_isr(int irq, void *dev_id) { - struct imx7d_adc *info = (struct imx7d_adc *)dev_id; + struct imx7d_adc *info = dev_id; int status; status = readl(info->regs + IMX7D_REG_ADC_INT_STATUS); diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c index 3263231276ca..db9838230257 100644 --- a/drivers/iio/adc/ina2xx-adc.c +++ b/drivers/iio/adc/ina2xx-adc.c @@ -28,6 +28,7 @@ #include <linux/iio/sysfs.h> #include <linux/kthread.h> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/regmap.h> #include <linux/util_macros.h> @@ -635,6 +636,7 @@ static int ina2xx_probe(struct i2c_client *client, struct iio_dev *indio_dev; struct iio_buffer *buffer; unsigned int val; + enum ina2xx_ids type; int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); @@ -652,7 +654,11 @@ static int ina2xx_probe(struct i2c_client *client, return PTR_ERR(chip->regmap); } - chip->config = &ina2xx_config[id->driver_data]; + if (client->dev.of_node) + type = (enum ina2xx_ids)of_device_get_match_data(&client->dev); + else + type = id->driver_data; + chip->config = &ina2xx_config[type]; mutex_init(&chip->state_lock); @@ -726,9 +732,35 @@ static const struct i2c_device_id ina2xx_id[] = { }; MODULE_DEVICE_TABLE(i2c, ina2xx_id); +static const struct of_device_id ina2xx_of_match[] = { + { + .compatible = "ti,ina219", + .data = (void *)ina219 + }, + { + .compatible = "ti,ina220", + .data = (void *)ina219 + }, + { + .compatible = "ti,ina226", + .data = (void *)ina226 + }, + { + .compatible = "ti,ina230", + .data = (void *)ina226 + }, + { + .compatible = "ti,ina231", + .data = (void *)ina226 + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ina2xx_of_match); + static struct i2c_driver ina2xx_driver = { .driver = { .name = KBUILD_MODNAME, + .of_match_table = ina2xx_of_match, }, .probe = ina2xx_probe, .remove = ina2xx_remove, diff --git a/drivers/iio/adc/lpc32xx_adc.c b/drivers/iio/adc/lpc32xx_adc.c new file mode 100644 index 000000000000..0de709b4288b --- /dev/null +++ b/drivers/iio/adc/lpc32xx_adc.c @@ -0,0 +1,219 @@ +/* + * lpc32xx_adc.c - Support for ADC in LPC32XX + * + * 3-channel, 10-bit ADC + * + * Copyright (C) 2011, 2012 Roland Stigge <stigge@antcom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/completion.h> +#include <linux/of.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* + * LPC32XX registers definitions + */ +#define LPC32XXAD_SELECT(x) ((x) + 0x04) +#define LPC32XXAD_CTRL(x) ((x) + 0x08) +#define LPC32XXAD_VALUE(x) ((x) + 0x48) + +/* Bit definitions for LPC32XXAD_SELECT: */ +/* constant, always write this value! */ +#define LPC32XXAD_REFm 0x00000200 +/* constant, always write this value! */ +#define LPC32XXAD_REFp 0x00000080 + /* multiple of this is the channel number: 0, 1, 2 */ +#define LPC32XXAD_IN 0x00000010 +/* constant, always write this value! */ +#define LPC32XXAD_INTERNAL 0x00000004 + +/* Bit definitions for LPC32XXAD_CTRL: */ +#define LPC32XXAD_STROBE 0x00000002 +#define LPC32XXAD_PDN_CTRL 0x00000004 + +/* Bit definitions for LPC32XXAD_VALUE: */ +#define LPC32XXAD_VALUE_MASK 0x000003FF + +#define LPC32XXAD_NAME "lpc32xx-adc" + +struct lpc32xx_adc_state { + void __iomem *adc_base; + struct clk *clk; + struct completion completion; + + u32 value; +}; + +static int lpc32xx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct lpc32xx_adc_state *st = iio_priv(indio_dev); + + if (mask == IIO_CHAN_INFO_RAW) { + mutex_lock(&indio_dev->mlock); + clk_prepare_enable(st->clk); + /* Measurement setup */ + __raw_writel(LPC32XXAD_INTERNAL | (chan->address) | + LPC32XXAD_REFp | LPC32XXAD_REFm, + LPC32XXAD_SELECT(st->adc_base)); + /* Trigger conversion */ + __raw_writel(LPC32XXAD_PDN_CTRL | LPC32XXAD_STROBE, + LPC32XXAD_CTRL(st->adc_base)); + wait_for_completion(&st->completion); /* set by ISR */ + clk_disable_unprepare(st->clk); + *val = st->value; + mutex_unlock(&indio_dev->mlock); + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info lpc32xx_adc_iio_info = { + .read_raw = &lpc32xx_read_raw, + .driver_module = THIS_MODULE, +}; + +#define LPC32XX_ADC_CHANNEL(_index) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .address = LPC32XXAD_IN * _index, \ + .scan_index = _index, \ +} + +static const struct iio_chan_spec lpc32xx_adc_iio_channels[] = { + LPC32XX_ADC_CHANNEL(0), + LPC32XX_ADC_CHANNEL(1), + LPC32XX_ADC_CHANNEL(2), +}; + +static irqreturn_t lpc32xx_adc_isr(int irq, void *dev_id) +{ + struct lpc32xx_adc_state *st = dev_id; + + /* Read value and clear irq */ + st->value = __raw_readl(LPC32XXAD_VALUE(st->adc_base)) & + LPC32XXAD_VALUE_MASK; + complete(&st->completion); + + return IRQ_HANDLED; +} + +static int lpc32xx_adc_probe(struct platform_device *pdev) +{ + struct lpc32xx_adc_state *st = NULL; + struct resource *res; + int retval = -ENODEV; + struct iio_dev *iodev = NULL; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform I/O memory\n"); + return -ENXIO; + } + + iodev = devm_iio_device_alloc(&pdev->dev, sizeof(*st)); + if (!iodev) + return -ENOMEM; + + st = iio_priv(iodev); + + st->adc_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!st->adc_base) { + dev_err(&pdev->dev, "failed mapping memory\n"); + return -EBUSY; + } + + st->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(st->clk)) { + dev_err(&pdev->dev, "failed getting clock\n"); + return PTR_ERR(st->clk); + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, "failed getting interrupt resource\n"); + return -ENXIO; + } + + retval = devm_request_irq(&pdev->dev, irq, lpc32xx_adc_isr, 0, + LPC32XXAD_NAME, st); + if (retval < 0) { + dev_err(&pdev->dev, "failed requesting interrupt\n"); + return retval; + } + + platform_set_drvdata(pdev, iodev); + + init_completion(&st->completion); + + iodev->name = LPC32XXAD_NAME; + iodev->dev.parent = &pdev->dev; + iodev->info = &lpc32xx_adc_iio_info; + iodev->modes = INDIO_DIRECT_MODE; + iodev->channels = lpc32xx_adc_iio_channels; + iodev->num_channels = ARRAY_SIZE(lpc32xx_adc_iio_channels); + + retval = devm_iio_device_register(&pdev->dev, iodev); + if (retval) + return retval; + + dev_info(&pdev->dev, "LPC32XX ADC driver loaded, IRQ %d\n", irq); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id lpc32xx_adc_match[] = { + { .compatible = "nxp,lpc3220-adc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, lpc32xx_adc_match); +#endif + +static struct platform_driver lpc32xx_adc_driver = { + .probe = lpc32xx_adc_probe, + .driver = { + .name = LPC32XXAD_NAME, + .of_match_table = of_match_ptr(lpc32xx_adc_match), + }, +}; + +module_platform_driver(lpc32xx_adc_driver); + +MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>"); +MODULE_DESCRIPTION("LPC32XX ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ltc2497.c b/drivers/iio/adc/ltc2497.c new file mode 100644 index 000000000000..2691b10023f5 --- /dev/null +++ b/drivers/iio/adc/ltc2497.c @@ -0,0 +1,279 @@ +/* + * ltc2497.c - Driver for Analog Devices/Linear Technology LTC2497 ADC + * + * Copyright (C) 2017 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + * Datasheet: http://cds.linear.com/docs/en/datasheet/2497fd.pdf + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#define LTC2497_ENABLE 0xA0 +#define LTC2497_SGL BIT(4) +#define LTC2497_DIFF 0 +#define LTC2497_SIGN BIT(3) +#define LTC2497_CONFIG_DEFAULT LTC2497_ENABLE +#define LTC2497_CONVERSION_TIME_MS 150ULL + +struct ltc2497_st { + struct i2c_client *client; + struct regulator *ref; + ktime_t time_prev; + u8 addr_prev; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __be32 buf ____cacheline_aligned; +}; + +static int ltc2497_wait_conv(struct ltc2497_st *st) +{ + s64 time_elapsed; + + time_elapsed = ktime_ms_delta(ktime_get(), st->time_prev); + + if (time_elapsed < LTC2497_CONVERSION_TIME_MS) { + /* delay if conversion time not passed + * since last read or write + */ + if (msleep_interruptible( + LTC2497_CONVERSION_TIME_MS - time_elapsed)) + return -ERESTARTSYS; + + return 0; + } + + if (time_elapsed - LTC2497_CONVERSION_TIME_MS <= 0) { + /* We're in automatic mode - + * so the last reading is stil not outdated + */ + return 0; + } + + return 1; +} + +static int ltc2497_read(struct ltc2497_st *st, u8 address, int *val) +{ + struct i2c_client *client = st->client; + int ret; + + ret = ltc2497_wait_conv(st); + if (ret < 0) + return ret; + + if (ret || st->addr_prev != address) { + ret = i2c_smbus_write_byte(st->client, + LTC2497_ENABLE | address); + if (ret < 0) + return ret; + st->addr_prev = address; + if (msleep_interruptible(LTC2497_CONVERSION_TIME_MS)) + return -ERESTARTSYS; + } + ret = i2c_master_recv(client, (char *)&st->buf, 3); + if (ret < 0) { + dev_err(&client->dev, "i2c_master_recv failed\n"); + return ret; + } + st->time_prev = ktime_get(); + + /* convert and shift the result, + * and finally convert from offset binary to signed integer + */ + *val = (be32_to_cpu(st->buf) >> 14) - (1 << 17); + + return ret; +} + +static int ltc2497_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ltc2497_st *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + ret = ltc2497_read(st, chan->address, val); + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(st->ref); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = 17; + + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +#define LTC2497_CHAN(_chan, _addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_chan), \ + .address = (_addr | (_chan / 2) | ((_chan & 1) ? LTC2497_SIGN : 0)), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +#define LTC2497_CHAN_DIFF(_chan, _addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_chan) * 2 + ((_addr) & LTC2497_SIGN ? 1 : 0), \ + .channel2 = (_chan) * 2 + ((_addr) & LTC2497_SIGN ? 0 : 1),\ + .address = (_addr | _chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .differential = 1, \ +} + +static const struct iio_chan_spec ltc2497_channel[] = { + LTC2497_CHAN(0, LTC2497_SGL), + LTC2497_CHAN(1, LTC2497_SGL), + LTC2497_CHAN(2, LTC2497_SGL), + LTC2497_CHAN(3, LTC2497_SGL), + LTC2497_CHAN(4, LTC2497_SGL), + LTC2497_CHAN(5, LTC2497_SGL), + LTC2497_CHAN(6, LTC2497_SGL), + LTC2497_CHAN(7, LTC2497_SGL), + LTC2497_CHAN(8, LTC2497_SGL), + LTC2497_CHAN(9, LTC2497_SGL), + LTC2497_CHAN(10, LTC2497_SGL), + LTC2497_CHAN(11, LTC2497_SGL), + LTC2497_CHAN(12, LTC2497_SGL), + LTC2497_CHAN(13, LTC2497_SGL), + LTC2497_CHAN(14, LTC2497_SGL), + LTC2497_CHAN(15, LTC2497_SGL), + LTC2497_CHAN_DIFF(0, LTC2497_DIFF), + LTC2497_CHAN_DIFF(1, LTC2497_DIFF), + LTC2497_CHAN_DIFF(2, LTC2497_DIFF), + LTC2497_CHAN_DIFF(3, LTC2497_DIFF), + LTC2497_CHAN_DIFF(4, LTC2497_DIFF), + LTC2497_CHAN_DIFF(5, LTC2497_DIFF), + LTC2497_CHAN_DIFF(6, LTC2497_DIFF), + LTC2497_CHAN_DIFF(7, LTC2497_DIFF), + LTC2497_CHAN_DIFF(0, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(1, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(2, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(3, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(4, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(5, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(6, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(7, LTC2497_DIFF | LTC2497_SIGN), +}; + +static const struct iio_info ltc2497_info = { + .read_raw = ltc2497_read_raw, + .driver_module = THIS_MODULE, +}; + +static int ltc2497_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct ltc2497_st *st; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | + I2C_FUNC_SMBUS_WRITE_BYTE)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + st->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->info = <c2497_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ltc2497_channel; + indio_dev->num_channels = ARRAY_SIZE(ltc2497_channel); + + st->ref = devm_regulator_get(&client->dev, "vref"); + if (IS_ERR(st->ref)) + return PTR_ERR(st->ref); + + ret = regulator_enable(st->ref); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte(st->client, LTC2497_CONFIG_DEFAULT); + if (ret < 0) + goto err_regulator_disable; + + st->addr_prev = LTC2497_CONFIG_DEFAULT; + st->time_prev = ktime_get(); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto err_regulator_disable; + + return 0; + +err_regulator_disable: + regulator_disable(st->ref); + + return ret; +} + +static int ltc2497_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ltc2497_st *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(st->ref); + + return 0; +} + +static const struct i2c_device_id ltc2497_id[] = { + { "ltc2497", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc2497_id); + +static const struct of_device_id ltc2497_of_match[] = { + { .compatible = "lltc,ltc2497", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ltc2497_of_match); + +static struct i2c_driver ltc2497_driver = { + .driver = { + .name = "ltc2497", + .of_match_table = of_match_ptr(ltc2497_of_match), + }, + .probe = ltc2497_probe, + .remove = ltc2497_remove, + .id_table = ltc2497_id, +}; +module_i2c_driver(ltc2497_driver); + +MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>"); +MODULE_DESCRIPTION("Linear Technology LTC2497 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/max1027.c b/drivers/iio/adc/max1027.c index 3b7c4f78f37a..ebc715927e63 100644 --- a/drivers/iio/adc/max1027.c +++ b/drivers/iio/adc/max1027.c @@ -364,7 +364,7 @@ static int max1027_set_trigger_state(struct iio_trigger *trig, bool state) static irqreturn_t max1027_trigger_handler(int irq, void *private) { - struct iio_poll_func *pf = (struct iio_poll_func *)private; + struct iio_poll_func *pf = private; struct iio_dev *indio_dev = pf->indio_dev; struct max1027_state *st = iio_priv(indio_dev); diff --git a/drivers/iio/adc/max11100.c b/drivers/iio/adc/max11100.c index a088cf99bfe1..1180bcc22ff1 100644 --- a/drivers/iio/adc/max11100.c +++ b/drivers/iio/adc/max11100.c @@ -124,8 +124,8 @@ static int max11100_probe(struct spi_device *spi) indio_dev->name = "max11100"; indio_dev->info = &max11100_info; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = max11100_channels, - indio_dev->num_channels = ARRAY_SIZE(max11100_channels), + indio_dev->channels = max11100_channels; + indio_dev->num_channels = ARRAY_SIZE(max11100_channels); state->vref_reg = devm_regulator_get(&spi->dev, "vref"); if (IS_ERR(state->vref_reg)) @@ -167,7 +167,6 @@ MODULE_DEVICE_TABLE(of, max11100_ids); static struct spi_driver max11100_driver = { .driver = { .name = "max11100", - .owner = THIS_MODULE, .of_match_table = of_match_ptr(max11100_ids), }, .probe = max11100_probe, diff --git a/drivers/iio/adc/max1118.c b/drivers/iio/adc/max1118.c new file mode 100644 index 000000000000..2e9648a078c4 --- /dev/null +++ b/drivers/iio/adc/max1118.c @@ -0,0 +1,307 @@ +/* + * MAX1117/MAX1118/MAX1119 8-bit, dual-channel ADCs driver + * + * Copyright (c) 2017 Akinobu Mita <akinobu.mita@gmail.com> + * + * 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: https://datasheets.maximintegrated.com/en/ds/MAX1117-MAX1119.pdf + * + * SPI interface connections + * + * SPI MAXIM + * Master Direction MAX1117/8/9 + * ------ --------- ----------- + * nCS --> CNVST + * SCK --> SCLK + * MISO <-- DOUT + * ------ --------- ----------- + */ + +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/regulator/consumer.h> + +enum max1118_id { + max1117, + max1118, + max1119, +}; + +struct max1118 { + struct spi_device *spi; + struct mutex lock; + struct regulator *reg; + + u8 data ____cacheline_aligned; +}; + +#define MAX1118_CHANNEL(ch) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = ch, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 8, \ + }, \ + } + +static const struct iio_chan_spec max1118_channels[] = { + MAX1118_CHANNEL(0), + MAX1118_CHANNEL(1), + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static int max1118_read(struct spi_device *spi, int channel) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct max1118 *adc = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + /* + * To select CH1 for conversion, CNVST pin must be brought high + * and low for a second time. + */ + { + .len = 0, + .delay_usecs = 1, /* > CNVST Low Time 100 ns */ + .cs_change = 1, + }, + /* + * The acquisition interval begins with the falling edge of + * CNVST. The total acquisition and conversion process takes + * <7.5us. + */ + { + .len = 0, + .delay_usecs = 8, + }, + { + .rx_buf = &adc->data, + .len = 1, + }, + }; + int ret; + + if (channel == 0) + ret = spi_sync_transfer(spi, xfers + 1, 2); + else + ret = spi_sync_transfer(spi, xfers, 3); + + if (ret) + return ret; + + return adc->data; +} + +static int max1118_get_vref_mV(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct max1118 *adc = iio_priv(indio_dev); + const struct spi_device_id *id = spi_get_device_id(spi); + int vref_uV; + + switch (id->driver_data) { + case max1117: + return 2048; + case max1119: + return 4096; + case max1118: + vref_uV = regulator_get_voltage(adc->reg); + if (vref_uV < 0) + return vref_uV; + return vref_uV / 1000; + } + + return -ENODEV; +} + +static int max1118_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max1118 *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + *val = max1118_read(adc->spi, chan->channel); + mutex_unlock(&adc->lock); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = max1118_get_vref_mV(adc->spi); + if (*val < 0) + return *val; + *val2 = 8; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static const struct iio_info max1118_info = { + .read_raw = max1118_read_raw, + .driver_module = THIS_MODULE, +}; + +static irqreturn_t max1118_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct max1118 *adc = iio_priv(indio_dev); + u8 data[16] = { }; /* 2x 8-bit ADC data + padding + 8 bytes timestamp */ + int scan_index; + int i = 0; + + mutex_lock(&adc->lock); + + for_each_set_bit(scan_index, indio_dev->active_scan_mask, + indio_dev->masklength) { + const struct iio_chan_spec *scan_chan = + &indio_dev->channels[scan_index]; + int ret = max1118_read(adc->spi, scan_chan->channel); + + if (ret < 0) { + dev_warn(&adc->spi->dev, + "failed to get conversion data\n"); + goto out; + } + + data[i] = ret; + i++; + } + iio_push_to_buffers_with_timestamp(indio_dev, data, + iio_get_time_ns(indio_dev)); +out: + mutex_unlock(&adc->lock); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int max1118_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct max1118 *adc; + const struct spi_device_id *id = spi_get_device_id(spi); + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + mutex_init(&adc->lock); + + if (id->driver_data == max1118) { + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) { + dev_err(&spi->dev, "failed to get vref regulator\n"); + return PTR_ERR(adc->reg); + } + ret = regulator_enable(adc->reg); + if (ret) + return ret; + } + + spi_set_drvdata(spi, indio_dev); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &max1118_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max1118_channels; + indio_dev->num_channels = ARRAY_SIZE(max1118_channels); + + /* + * To reinitiate a conversion on CH0, it is necessary to allow for a + * conversion to be complete and all of the data to be read out. Once + * a conversion has been completed, the MAX1117/MAX1118/MAX1119 will go + * into AutoShutdown mode until the next conversion is initiated. + */ + max1118_read(spi, 0); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + max1118_trigger_handler, NULL); + if (ret) + goto err_reg_disable; + + ret = iio_device_register(indio_dev); + if (ret) + goto err_buffer_cleanup; + + return 0; + +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_reg_disable: + if (id->driver_data == max1118) + regulator_disable(adc->reg); + + return ret; +} + +static int max1118_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct max1118 *adc = iio_priv(indio_dev); + const struct spi_device_id *id = spi_get_device_id(spi); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (id->driver_data == max1118) + return regulator_disable(adc->reg); + + return 0; +} + +static const struct spi_device_id max1118_id[] = { + { "max1117", max1117 }, + { "max1118", max1118 }, + { "max1119", max1119 }, + {} +}; +MODULE_DEVICE_TABLE(spi, max1118_id); + +#ifdef CONFIG_OF + +static const struct of_device_id max1118_dt_ids[] = { + { .compatible = "maxim,max1117" }, + { .compatible = "maxim,max1118" }, + { .compatible = "maxim,max1119" }, + {}, +}; +MODULE_DEVICE_TABLE(of, max1118_dt_ids); + +#endif + +static struct spi_driver max1118_spi_driver = { + .driver = { + .name = "max1118", + .of_match_table = of_match_ptr(max1118_dt_ids), + }, + .probe = max1118_probe, + .remove = max1118_remove, + .id_table = max1118_id, +}; +module_spi_driver(max1118_spi_driver); + +MODULE_AUTHOR("Akinobu Mita <akinobu.mita@gmail.com>"); +MODULE_DESCRIPTION("MAXIM MAX1117/MAX1118/MAX1119 ADCs driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/max1363.c b/drivers/iio/adc/max1363.c index c6c12feb4a08..80eada4886b3 100644 --- a/drivers/iio/adc/max1363.c +++ b/drivers/iio/adc/max1363.c @@ -1007,7 +1007,7 @@ static struct attribute *max1363_event_attributes[] = { NULL, }; -static struct attribute_group max1363_event_attribute_group = { +static const struct attribute_group max1363_event_attribute_group = { .attrs = max1363_event_attributes, }; diff --git a/drivers/iio/adc/max9611.c b/drivers/iio/adc/max9611.c new file mode 100644 index 000000000000..ec82106480e1 --- /dev/null +++ b/drivers/iio/adc/max9611.c @@ -0,0 +1,585 @@ +/* + * iio/adc/max9611.c + * + * Maxim max9611/max9612 high side current sense amplifier with + * 12-bit ADC interface. + * + * Copyright (C) 2017 Jacopo Mondi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * This driver supports input common-mode voltage, current-sense + * amplifier with programmable gains and die temperature reading from + * Maxim max9611/max9612. + * + * Op-amp, analog comparator, and watchdog functionalities are not + * supported by this driver. + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/module.h> +#include <linux/of_device.h> + +#define DRIVER_NAME "max9611" + +/* max9611 register addresses */ +#define MAX9611_REG_CSA_DATA 0x00 +#define MAX9611_REG_RS_DATA 0x02 +#define MAX9611_REG_TEMP_DATA 0x08 +#define MAX9611_REG_CTRL1 0x0a +#define MAX9611_REG_CTRL2 0x0b + +/* max9611 REG1 mux configuration options */ +#define MAX9611_MUX_MASK GENMASK(3, 0) +#define MAX9611_MUX_SENSE_1x 0x00 +#define MAX9611_MUX_SENSE_4x 0x01 +#define MAX9611_MUX_SENSE_8x 0x02 +#define MAX9611_INPUT_VOLT 0x03 +#define MAX9611_MUX_TEMP 0x06 + +/* max9611 voltage (both csa and input) helper macros */ +#define MAX9611_VOLTAGE_SHIFT 0x04 +#define MAX9611_VOLTAGE_RAW(_r) ((_r) >> MAX9611_VOLTAGE_SHIFT) + +/* + * max9611 current sense amplifier voltage output: + * LSB and offset values depends on selected gain (1x, 4x, 8x) + * + * GAIN LSB (nV) OFFSET (LSB steps) + * 1x 107500 1 + * 4x 26880 1 + * 8x 13440 3 + * + * The complete formula to calculate current sense voltage is: + * (((adc_read >> 4) - offset) / ((1 / LSB) * 10^-3) + */ +#define MAX9611_CSA_1X_LSB_nV 107500 +#define MAX9611_CSA_4X_LSB_nV 26880 +#define MAX9611_CSA_8X_LSB_nV 13440 + +#define MAX9611_CSA_1X_OFFS_RAW 1 +#define MAX9611_CSA_4X_OFFS_RAW 1 +#define MAX9611_CSA_8X_OFFS_RAW 3 + +/* + * max9611 common input mode (CIM): LSB is 14mV, with 14mV offset at 25 C + * + * The complete formula to calculate input common voltage is: + * (((adc_read >> 4) * 1000) - offset) / (1 / 14 * 1000) + */ +#define MAX9611_CIM_LSB_mV 14 +#define MAX9611_CIM_OFFSET_RAW 1 + +/* + * max9611 temperature reading: LSB is 480 milli degrees Celsius + * + * The complete formula to calculate temperature is: + * ((adc_read >> 7) * 1000) / (1 / 480 * 1000) + */ +#define MAX9611_TEMP_MAX_POS 0x7f80 +#define MAX9611_TEMP_MAX_NEG 0xff80 +#define MAX9611_TEMP_MIN_NEG 0xd980 +#define MAX9611_TEMP_MASK GENMASK(7, 15) +#define MAX9611_TEMP_SHIFT 0x07 +#define MAX9611_TEMP_RAW(_r) ((_r) >> MAX9611_TEMP_SHIFT) +#define MAX9611_TEMP_SCALE_NUM 1000000 +#define MAX9611_TEMP_SCALE_DIV 2083 + +struct max9611_dev { + struct device *dev; + struct i2c_client *i2c_client; + struct mutex lock; + unsigned int shunt_resistor_uohm; +}; + +enum max9611_conf_ids { + CONF_SENSE_1x, + CONF_SENSE_4x, + CONF_SENSE_8x, + CONF_IN_VOLT, + CONF_TEMP, +}; + +/** + * max9611_mux_conf - associate ADC mux configuration with register address + * where data shall be read from + */ +static const unsigned int max9611_mux_conf[][2] = { + /* CONF_SENSE_1x */ + { MAX9611_MUX_SENSE_1x, MAX9611_REG_CSA_DATA }, + /* CONF_SENSE_4x */ + { MAX9611_MUX_SENSE_4x, MAX9611_REG_CSA_DATA }, + /* CONF_SENSE_8x */ + { MAX9611_MUX_SENSE_8x, MAX9611_REG_CSA_DATA }, + /* CONF_IN_VOLT */ + { MAX9611_INPUT_VOLT, MAX9611_REG_RS_DATA }, + /* CONF_TEMP */ + { MAX9611_MUX_TEMP, MAX9611_REG_TEMP_DATA }, +}; + +enum max9611_csa_gain { + CSA_GAIN_1x, + CSA_GAIN_4x, + CSA_GAIN_8x, +}; + +enum max9611_csa_gain_params { + CSA_GAIN_LSB_nV, + CSA_GAIN_OFFS_RAW, +}; + +/** + * max9611_csa_gain_conf - associate gain multiplier with LSB and + * offset values. + * + * Group together parameters associated with configurable gain + * on current sense amplifier path to ADC interface. + * Current sense read routine adjusts gain until it gets a meaningful + * value; use this structure to retrieve the correct LSB and offset values. + */ +static const unsigned int max9611_gain_conf[][2] = { + { /* [0] CSA_GAIN_1x */ + MAX9611_CSA_1X_LSB_nV, + MAX9611_CSA_1X_OFFS_RAW, + }, + { /* [1] CSA_GAIN_4x */ + MAX9611_CSA_4X_LSB_nV, + MAX9611_CSA_4X_OFFS_RAW, + }, + { /* [2] CSA_GAIN_8x */ + MAX9611_CSA_8X_LSB_nV, + MAX9611_CSA_8X_OFFS_RAW, + }, +}; + +enum max9611_chan_addrs { + MAX9611_CHAN_VOLTAGE_INPUT, + MAX9611_CHAN_VOLTAGE_SENSE, + MAX9611_CHAN_TEMPERATURE, + MAX9611_CHAN_CURRENT_LOAD, + MAX9611_CHAN_POWER_LOAD, +}; + +static const struct iio_chan_spec max9611_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = MAX9611_CHAN_TEMPERATURE, + }, + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = MAX9611_CHAN_VOLTAGE_SENSE, + .indexed = 1, + .channel = 0, + }, + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = MAX9611_CHAN_VOLTAGE_INPUT, + .indexed = 1, + .channel = 1, + }, + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = MAX9611_CHAN_CURRENT_LOAD, + }, + { + .type = IIO_POWER, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = MAX9611_CHAN_POWER_LOAD + }, +}; + +/** + * max9611_read_single() - read a single value from ADC interface + * + * Data registers are 16 bit long, spread between two 8 bit registers + * with consecutive addresses. + * Configure ADC mux first, then read register at address "reg_addr". + * The smbus_read_word routine asks for 16 bits and the ADC is kind enough + * to return values from "reg_addr" and "reg_addr + 1" consecutively. + * Data are transmitted with big-endian ordering: MSB arrives first. + * + * @max9611: max9611 device + * @selector: index for mux and register configuration + * @raw_val: the value returned from ADC + */ +static int max9611_read_single(struct max9611_dev *max9611, + enum max9611_conf_ids selector, + u16 *raw_val) +{ + int ret; + + u8 mux_conf = max9611_mux_conf[selector][0] & MAX9611_MUX_MASK; + u8 reg_addr = max9611_mux_conf[selector][1]; + + /* + * Keep mutex lock held during read-write to avoid mux register + * (CTRL1) re-configuration. + */ + mutex_lock(&max9611->lock); + ret = i2c_smbus_write_byte_data(max9611->i2c_client, + MAX9611_REG_CTRL1, mux_conf); + if (ret) { + dev_err(max9611->dev, "i2c write byte failed: 0x%2x - 0x%2x\n", + MAX9611_REG_CTRL1, mux_conf); + mutex_unlock(&max9611->lock); + return ret; + } + + /* + * need a delay here to make register configuration + * stabilize. 1 msec at least, from empirical testing. + */ + usleep_range(1000, 2000); + + ret = i2c_smbus_read_word_swapped(max9611->i2c_client, reg_addr); + if (ret < 0) { + dev_err(max9611->dev, "i2c read word from 0x%2x failed\n", + reg_addr); + mutex_unlock(&max9611->lock); + return ret; + } + + *raw_val = ret; + mutex_unlock(&max9611->lock); + + return 0; +} + +/** + * max9611_read_csa_voltage() - read current sense amplifier output voltage + * + * Current sense amplifier output voltage is read through a configurable + * 1x, 4x or 8x gain. + * Start with plain 1x gain, and adjust gain control properly until a + * meaningful value is read from ADC output. + * + * @max9611: max9611 device + * @adc_raw: raw value read from ADC output + * @csa_gain: gain configuration option selector + */ +static int max9611_read_csa_voltage(struct max9611_dev *max9611, + u16 *adc_raw, + enum max9611_csa_gain *csa_gain) +{ + enum max9611_conf_ids gain_selectors[] = { + CONF_SENSE_1x, + CONF_SENSE_4x, + CONF_SENSE_8x + }; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(gain_selectors); ++i) { + ret = max9611_read_single(max9611, gain_selectors[i], adc_raw); + if (ret) + return ret; + + if (*adc_raw > 0) { + *csa_gain = gain_selectors[i]; + return 0; + } + } + + return -EIO; +} + +static int max9611_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max9611_dev *dev = iio_priv(indio_dev); + enum max9611_csa_gain gain_selector; + const unsigned int *csa_gain; + u16 adc_data; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + + switch (chan->address) { + case MAX9611_CHAN_TEMPERATURE: + ret = max9611_read_single(dev, CONF_TEMP, + &adc_data); + if (ret) + return -EINVAL; + + *val = MAX9611_TEMP_RAW(adc_data); + return IIO_VAL_INT; + + case MAX9611_CHAN_VOLTAGE_INPUT: + ret = max9611_read_single(dev, CONF_IN_VOLT, + &adc_data); + if (ret) + return -EINVAL; + + *val = MAX9611_VOLTAGE_RAW(adc_data); + return IIO_VAL_INT; + } + + break; + + case IIO_CHAN_INFO_OFFSET: + /* MAX9611_CHAN_VOLTAGE_INPUT */ + *val = MAX9611_CIM_OFFSET_RAW; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + + switch (chan->address) { + case MAX9611_CHAN_TEMPERATURE: + *val = MAX9611_TEMP_SCALE_NUM; + *val2 = MAX9611_TEMP_SCALE_DIV; + + return IIO_VAL_FRACTIONAL; + + case MAX9611_CHAN_VOLTAGE_INPUT: + *val = MAX9611_CIM_LSB_mV; + + return IIO_VAL_INT; + } + + break; + + case IIO_CHAN_INFO_PROCESSED: + + switch (chan->address) { + case MAX9611_CHAN_VOLTAGE_SENSE: + /* + * processed (mV): (raw - offset) * LSB (nV) / 10^6 + * + * Even if max9611 can output raw csa voltage readings, + * use a produced value as scale depends on gain. + */ + ret = max9611_read_csa_voltage(dev, &adc_data, + &gain_selector); + if (ret) + return -EINVAL; + + csa_gain = max9611_gain_conf[gain_selector]; + + adc_data -= csa_gain[CSA_GAIN_OFFS_RAW]; + *val = MAX9611_VOLTAGE_RAW(adc_data) * + csa_gain[CSA_GAIN_LSB_nV]; + *val2 = 1000000; + + return IIO_VAL_FRACTIONAL; + + case MAX9611_CHAN_CURRENT_LOAD: + /* processed (mA): Vcsa (nV) / Rshunt (uOhm) */ + ret = max9611_read_csa_voltage(dev, &adc_data, + &gain_selector); + if (ret) + return -EINVAL; + + csa_gain = max9611_gain_conf[gain_selector]; + + adc_data -= csa_gain[CSA_GAIN_OFFS_RAW]; + *val = MAX9611_VOLTAGE_RAW(adc_data) * + csa_gain[CSA_GAIN_LSB_nV]; + *val2 = dev->shunt_resistor_uohm; + + return IIO_VAL_FRACTIONAL; + + case MAX9611_CHAN_POWER_LOAD: + /* + * processed (mW): Vin (mV) * Vcsa (uV) / + * Rshunt (uOhm) + */ + ret = max9611_read_single(dev, CONF_IN_VOLT, + &adc_data); + if (ret) + return -EINVAL; + + adc_data -= MAX9611_CIM_OFFSET_RAW; + *val = MAX9611_VOLTAGE_RAW(adc_data) * + MAX9611_CIM_LSB_mV; + + ret = max9611_read_csa_voltage(dev, &adc_data, + &gain_selector); + if (ret) + return -EINVAL; + + csa_gain = max9611_gain_conf[gain_selector]; + + /* divide by 10^3 here to avoid 32bit overflow */ + adc_data -= csa_gain[CSA_GAIN_OFFS_RAW]; + *val *= MAX9611_VOLTAGE_RAW(adc_data) * + csa_gain[CSA_GAIN_LSB_nV] / 1000; + *val2 = dev->shunt_resistor_uohm; + + return IIO_VAL_FRACTIONAL; + } + + break; + } + + return -EINVAL; +} + +static ssize_t max9611_shunt_resistor_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max9611_dev *max9611 = iio_priv(dev_to_iio_dev(dev)); + unsigned int i, r; + + i = max9611->shunt_resistor_uohm / 1000; + r = max9611->shunt_resistor_uohm % 1000; + + return sprintf(buf, "%u.%03u\n", i, r); +} + +static IIO_DEVICE_ATTR(in_power_shunt_resistor, 0444, + max9611_shunt_resistor_show, NULL, 0); +static IIO_DEVICE_ATTR(in_current_shunt_resistor, 0444, + max9611_shunt_resistor_show, NULL, 0); + +static struct attribute *max9611_attributes[] = { + &iio_dev_attr_in_power_shunt_resistor.dev_attr.attr, + &iio_dev_attr_in_current_shunt_resistor.dev_attr.attr, + NULL, +}; + +static const struct attribute_group max9611_attribute_group = { + .attrs = max9611_attributes, +}; + +static const struct iio_info indio_info = { + .driver_module = THIS_MODULE, + .read_raw = max9611_read_raw, + .attrs = &max9611_attribute_group, +}; + +static int max9611_init(struct max9611_dev *max9611) +{ + struct i2c_client *client = max9611->i2c_client; + u16 regval; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_WORD_DATA)) { + dev_err(max9611->dev, + "I2c adapter does not support smbus write_byte or read_word functionalities: aborting probe.\n"); + return -EINVAL; + } + + /* Make sure die temperature is in range to test communications. */ + ret = max9611_read_single(max9611, CONF_TEMP, ®val); + if (ret) + return ret; + + regval = ret & MAX9611_TEMP_MASK; + + if ((regval > MAX9611_TEMP_MAX_POS && + regval < MAX9611_TEMP_MIN_NEG) || + regval > MAX9611_TEMP_MAX_NEG) { + dev_err(max9611->dev, + "Invalid value received from ADC 0x%4x: aborting\n", + regval); + return -EIO; + } + + /* Mux shall be zeroed back before applying other configurations */ + ret = i2c_smbus_write_byte_data(max9611->i2c_client, + MAX9611_REG_CTRL1, 0); + if (ret) { + dev_err(max9611->dev, "i2c write byte failed: 0x%2x - 0x%2x\n", + MAX9611_REG_CTRL1, 0); + return ret; + } + + ret = i2c_smbus_write_byte_data(max9611->i2c_client, + MAX9611_REG_CTRL2, 0); + if (ret) { + dev_err(max9611->dev, "i2c write byte failed: 0x%2x - 0x%2x\n", + MAX9611_REG_CTRL2, 0); + return ret; + } + usleep_range(1000, 2000); + + return 0; +} + +static const struct of_device_id max9611_of_table[] = { + {.compatible = "maxim,max9611", .data = "max9611"}, + {.compatible = "maxim,max9612", .data = "max9612"}, + { }, +}; + +MODULE_DEVICE_TABLE(of, max9611_of_table); +static int max9611_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const char * const shunt_res_prop = "shunt-resistor-micro-ohms"; + const struct device_node *of_node = client->dev.of_node; + const struct of_device_id *of_id = + of_match_device(max9611_of_table, &client->dev); + struct max9611_dev *max9611; + struct iio_dev *indio_dev; + unsigned int of_shunt; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*max9611)); + if (IS_ERR(indio_dev)) + return PTR_ERR(indio_dev); + + i2c_set_clientdata(client, indio_dev); + + max9611 = iio_priv(indio_dev); + max9611->dev = &client->dev; + max9611->i2c_client = client; + mutex_init(&max9611->lock); + + ret = of_property_read_u32(of_node, shunt_res_prop, &of_shunt); + if (ret) { + dev_err(&client->dev, + "Missing %s property for %s node\n", + shunt_res_prop, of_node->full_name); + return ret; + } + max9611->shunt_resistor_uohm = of_shunt; + + ret = max9611_init(max9611); + if (ret) + return ret; + + indio_dev->dev.parent = &client->dev; + indio_dev->dev.of_node = client->dev.of_node; + indio_dev->name = of_id->data; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &indio_info; + indio_dev->channels = max9611_channels; + indio_dev->num_channels = ARRAY_SIZE(max9611_channels); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static struct i2c_driver max9611_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = max9611_of_table, + }, + .probe = max9611_probe, +}; +module_i2c_driver(max9611_driver); + +MODULE_AUTHOR("Jacopo Mondi <jacopo+renesas@jmondi.org>"); +MODULE_DESCRIPTION("Maxim max9611/12 current sense amplifier with 12bit ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c index 89def6034f40..dd4190b50df6 100644 --- a/drivers/iio/adc/meson_saradc.c +++ b/drivers/iio/adc/meson_saradc.c @@ -18,7 +18,9 @@ #include <linux/io.h> #include <linux/iio/iio.h> #include <linux/module.h> +#include <linux/interrupt.h> #include <linux/of.h> +#include <linux/of_irq.h> #include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/regmap.h> @@ -163,6 +165,9 @@ #define MESON_SAR_ADC_REG13_12BIT_CALIBRATION_MASK GENMASK(13, 8) #define MESON_SAR_ADC_MAX_FIFO_SIZE 32 +#define MESON_SAR_ADC_TIMEOUT 100 /* ms */ +/* for use with IIO_VAL_INT_PLUS_MICRO */ +#define MILLION 1000000 #define MESON_SAR_ADC_CHAN(_chan) { \ .type = IIO_VOLTAGE, \ @@ -170,7 +175,9 @@ .channel = _chan, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ BIT(IIO_CHAN_INFO_AVERAGE_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ .datasheet_name = "SAR_ADC_CH"#_chan, \ } @@ -229,6 +236,9 @@ struct meson_sar_adc_priv { struct clk_gate clk_gate; struct clk *adc_div_clk; struct clk_divider clk_div; + struct completion done; + int calibbias; + int calibscale; }; static const struct regmap_config meson_sar_adc_regmap_config = { @@ -248,6 +258,17 @@ static unsigned int meson_sar_adc_get_fifo_count(struct iio_dev *indio_dev) return FIELD_GET(MESON_SAR_ADC_REG0_FIFO_COUNT_MASK, regval); } +static int meson_sar_adc_calib_val(struct iio_dev *indio_dev, int val) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int tmp; + + /* use val_calib = scale * val_raw + offset calibration function */ + tmp = div_s64((s64)val * priv->calibscale, MILLION) + priv->calibbias; + + return clamp(tmp, 0, (1 << priv->data->resolution) - 1); +} + static int meson_sar_adc_wait_busy_clear(struct iio_dev *indio_dev) { struct meson_sar_adc_priv *priv = iio_priv(indio_dev); @@ -274,33 +295,31 @@ static int meson_sar_adc_read_raw_sample(struct iio_dev *indio_dev, int *val) { struct meson_sar_adc_priv *priv = iio_priv(indio_dev); - int ret, regval, fifo_chan, fifo_val, sum = 0, count = 0; + int regval, fifo_chan, fifo_val, count; - ret = meson_sar_adc_wait_busy_clear(indio_dev); - if (ret) - return ret; - - while (meson_sar_adc_get_fifo_count(indio_dev) > 0 && - count < MESON_SAR_ADC_MAX_FIFO_SIZE) { - regmap_read(priv->regmap, MESON_SAR_ADC_FIFO_RD, ®val); - - fifo_chan = FIELD_GET(MESON_SAR_ADC_FIFO_RD_CHAN_ID_MASK, - regval); - if (fifo_chan != chan->channel) - continue; - - fifo_val = FIELD_GET(MESON_SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK, - regval); - fifo_val &= (BIT(priv->data->resolution) - 1); + if(!wait_for_completion_timeout(&priv->done, + msecs_to_jiffies(MESON_SAR_ADC_TIMEOUT))) + return -ETIMEDOUT; - sum += fifo_val; - count++; + count = meson_sar_adc_get_fifo_count(indio_dev); + if (count != 1) { + dev_err(&indio_dev->dev, + "ADC FIFO has %d element(s) instead of one\n", count); + return -EINVAL; } - if (!count) - return -ENOENT; + regmap_read(priv->regmap, MESON_SAR_ADC_FIFO_RD, ®val); + fifo_chan = FIELD_GET(MESON_SAR_ADC_FIFO_RD_CHAN_ID_MASK, regval); + if (fifo_chan != chan->channel) { + dev_err(&indio_dev->dev, + "ADC FIFO entry belongs to channel %d instead of %d\n", + fifo_chan, chan->channel); + return -EINVAL; + } - *val = sum / count; + fifo_val = FIELD_GET(MESON_SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK, regval); + fifo_val &= GENMASK(priv->data->resolution - 1, 0); + *val = meson_sar_adc_calib_val(indio_dev, fifo_val); return 0; } @@ -378,6 +397,12 @@ static void meson_sar_adc_start_sample_engine(struct iio_dev *indio_dev) { struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + reinit_completion(&priv->done); + + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_FIFO_IRQ_EN, + MESON_SAR_ADC_REG0_FIFO_IRQ_EN); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE, MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE); @@ -392,6 +417,9 @@ static void meson_sar_adc_stop_sample_engine(struct iio_dev *indio_dev) struct meson_sar_adc_priv *priv = iio_priv(indio_dev); regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_FIFO_IRQ_EN, 0); + + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, MESON_SAR_ADC_REG0_SAMPLING_STOP, MESON_SAR_ADC_REG0_SAMPLING_STOP); @@ -516,6 +544,15 @@ static int meson_sar_adc_iio_info_read_raw(struct iio_dev *indio_dev, *val2 = priv->data->resolution; return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_CALIBBIAS: + *val = priv->calibbias; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_CALIBSCALE: + *val = priv->calibscale / MILLION; + *val2 = priv->calibscale % MILLION; + return IIO_VAL_INT_PLUS_MICRO; + default: return -EINVAL; } @@ -643,6 +680,7 @@ static int meson_sar_adc_hw_enable(struct iio_dev *indio_dev) { struct meson_sar_adc_priv *priv = iio_priv(indio_dev); int ret; + u32 regval; ret = meson_sar_adc_lock(indio_dev); if (ret) @@ -667,6 +705,9 @@ static int meson_sar_adc_hw_enable(struct iio_dev *indio_dev) goto err_sana_clk; } + regval = FIELD_PREP(MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK, 1); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK, regval); regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11, MESON_SAR_ADC_REG11_BANDGAP_EN, MESON_SAR_ADC_REG11_BANDGAP_EN); @@ -728,6 +769,66 @@ static int meson_sar_adc_hw_disable(struct iio_dev *indio_dev) return 0; } +static irqreturn_t meson_sar_adc_irq(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + unsigned int cnt, threshold; + u32 regval; + + regmap_read(priv->regmap, MESON_SAR_ADC_REG0, ®val); + cnt = FIELD_GET(MESON_SAR_ADC_REG0_FIFO_COUNT_MASK, regval); + threshold = FIELD_GET(MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK, regval); + + if (cnt < threshold) + return IRQ_NONE; + + complete(&priv->done); + + return IRQ_HANDLED; +} + +static int meson_sar_adc_calib(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int ret, nominal0, nominal1, value0, value1; + + /* use points 25% and 75% for calibration */ + nominal0 = (1 << priv->data->resolution) / 4; + nominal1 = (1 << priv->data->resolution) * 3 / 4; + + meson_sar_adc_set_chan7_mux(indio_dev, CHAN7_MUX_VDD_DIV4); + usleep_range(10, 20); + ret = meson_sar_adc_get_sample(indio_dev, + &meson_sar_adc_iio_channels[7], + MEAN_AVERAGING, EIGHT_SAMPLES, &value0); + if (ret < 0) + goto out; + + meson_sar_adc_set_chan7_mux(indio_dev, CHAN7_MUX_VDD_MUL3_DIV4); + usleep_range(10, 20); + ret = meson_sar_adc_get_sample(indio_dev, + &meson_sar_adc_iio_channels[7], + MEAN_AVERAGING, EIGHT_SAMPLES, &value1); + if (ret < 0) + goto out; + + if (value1 <= value0) { + ret = -EINVAL; + goto out; + } + + priv->calibscale = div_s64((nominal1 - nominal0) * (s64)MILLION, + value1 - value0); + priv->calibbias = nominal0 - div_s64((s64)value0 * priv->calibscale, + MILLION); + ret = 0; +out: + meson_sar_adc_set_chan7_mux(indio_dev, CHAN7_MUX_CH7_INPUT); + + return ret; +} + static const struct iio_info meson_sar_adc_iio_info = { .read_raw = meson_sar_adc_iio_info_read_raw, .driver_module = THIS_MODULE, @@ -770,7 +871,7 @@ static int meson_sar_adc_probe(struct platform_device *pdev) struct resource *res; void __iomem *base; const struct of_device_id *match; - int ret; + int irq, ret; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv)); if (!indio_dev) { @@ -779,6 +880,7 @@ static int meson_sar_adc_probe(struct platform_device *pdev) } priv = iio_priv(indio_dev); + init_completion(&priv->done); match = of_match_device(meson_sar_adc_of_match, &pdev->dev); priv->data = match->data; @@ -797,6 +899,15 @@ static int meson_sar_adc_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); + irq = irq_of_parse_and_map(pdev->dev.of_node, 0); + if (!irq) + return -EINVAL; + + ret = devm_request_irq(&pdev->dev, irq, meson_sar_adc_irq, IRQF_SHARED, + dev_name(&pdev->dev), indio_dev); + if (ret) + return ret; + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, &meson_sar_adc_regmap_config); if (IS_ERR(priv->regmap)) @@ -857,6 +968,8 @@ static int meson_sar_adc_probe(struct platform_device *pdev) return PTR_ERR(priv->vref); } + priv->calibscale = MILLION; + ret = meson_sar_adc_init(indio_dev); if (ret) goto err; @@ -865,6 +978,10 @@ static int meson_sar_adc_probe(struct platform_device *pdev) if (ret) goto err; + ret = meson_sar_adc_calib(indio_dev); + if (ret) + dev_warn(&pdev->dev, "calibration failed\n"); + platform_set_drvdata(pdev, indio_dev); ret = iio_device_register(indio_dev); diff --git a/drivers/iio/adc/qcom-pm8xxx-xoadc.c b/drivers/iio/adc/qcom-pm8xxx-xoadc.c new file mode 100644 index 000000000000..cea8f1fb444a --- /dev/null +++ b/drivers/iio/adc/qcom-pm8xxx-xoadc.c @@ -0,0 +1,1036 @@ +/* + * Qualcomm PM8xxx PMIC XOADC driver + * + * These ADCs are known as HK/XO (house keeping / chrystal oscillator) + * "XO" in "XOADC" means Chrystal Oscillator. It's a bunch of + * specific-purpose and general purpose ADC converters and channels. + * + * Copyright (C) 2017 Linaro Ltd. + * Author: Linus Walleij <linus.walleij@linaro.org> + */ + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/regulator/consumer.h> + +#include "qcom-vadc-common.h" + +/* + * Definitions for the "user processor" registers lifted from the v3.4 + * Qualcomm tree. Their kernel has two out-of-tree drivers for the ADC: + * drivers/misc/pmic8058-xoadc.c + * drivers/hwmon/pm8xxx-adc.c + * None of them contain any complete register specification, so this is + * a best effort of combining the information. + */ + +/* These appear to be "battery monitor" registers */ +#define ADC_ARB_BTM_CNTRL1 0x17e +#define ADC_ARB_BTM_CNTRL1_EN_BTM BIT(0) +#define ADC_ARB_BTM_CNTRL1_SEL_OP_MODE BIT(1) +#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL1 BIT(2) +#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL2 BIT(3) +#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL3 BIT(4) +#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL4 BIT(5) +#define ADC_ARB_BTM_CNTRL1_EOC BIT(6) +#define ADC_ARB_BTM_CNTRL1_REQ BIT(7) + +#define ADC_ARB_BTM_AMUX_CNTRL 0x17f +#define ADC_ARB_BTM_ANA_PARAM 0x180 +#define ADC_ARB_BTM_DIG_PARAM 0x181 +#define ADC_ARB_BTM_RSV 0x182 +#define ADC_ARB_BTM_DATA1 0x183 +#define ADC_ARB_BTM_DATA0 0x184 +#define ADC_ARB_BTM_BAT_COOL_THR1 0x185 +#define ADC_ARB_BTM_BAT_COOL_THR0 0x186 +#define ADC_ARB_BTM_BAT_WARM_THR1 0x187 +#define ADC_ARB_BTM_BAT_WARM_THR0 0x188 +#define ADC_ARB_BTM_CNTRL2 0x18c + +/* Proper ADC registers */ + +#define ADC_ARB_USRP_CNTRL 0x197 +#define ADC_ARB_USRP_CNTRL_EN_ARB BIT(0) +#define ADC_ARB_USRP_CNTRL_RSV1 BIT(1) +#define ADC_ARB_USRP_CNTRL_RSV2 BIT(2) +#define ADC_ARB_USRP_CNTRL_RSV3 BIT(3) +#define ADC_ARB_USRP_CNTRL_RSV4 BIT(4) +#define ADC_ARB_USRP_CNTRL_RSV5 BIT(5) +#define ADC_ARB_USRP_CNTRL_EOC BIT(6) +#define ADC_ARB_USRP_CNTRL_REQ BIT(7) + +#define ADC_ARB_USRP_AMUX_CNTRL 0x198 +/* + * The channel mask includes the bits selecting channel mux and prescaler + * on PM8058, or channel mux and premux on PM8921. + */ +#define ADC_ARB_USRP_AMUX_CNTRL_CHAN_MASK 0xfc +#define ADC_ARB_USRP_AMUX_CNTRL_RSV0 BIT(0) +#define ADC_ARB_USRP_AMUX_CNTRL_RSV1 BIT(1) +/* On PM8058 this is prescaling, on PM8921 this is premux */ +#define ADC_ARB_USRP_AMUX_CNTRL_PRESCALEMUX0 BIT(2) +#define ADC_ARB_USRP_AMUX_CNTRL_PRESCALEMUX1 BIT(3) +#define ADC_ARB_USRP_AMUX_CNTRL_SEL0 BIT(4) +#define ADC_ARB_USRP_AMUX_CNTRL_SEL1 BIT(5) +#define ADC_ARB_USRP_AMUX_CNTRL_SEL2 BIT(6) +#define ADC_ARB_USRP_AMUX_CNTRL_SEL3 BIT(7) +#define ADC_AMUX_PREMUX_SHIFT 2 +#define ADC_AMUX_SEL_SHIFT 4 + +/* We know very little about the bits in this register */ +#define ADC_ARB_USRP_ANA_PARAM 0x199 +#define ADC_ARB_USRP_ANA_PARAM_DIS 0xFE +#define ADC_ARB_USRP_ANA_PARAM_EN 0xFF + +#define ADC_ARB_USRP_DIG_PARAM 0x19A +#define ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0 BIT(0) +#define ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1 BIT(1) +#define ADC_ARB_USRP_DIG_PARAM_CLK_RATE0 BIT(2) +#define ADC_ARB_USRP_DIG_PARAM_CLK_RATE1 BIT(3) +#define ADC_ARB_USRP_DIG_PARAM_EOC BIT(4) +/* + * On a later ADC the decimation factors are defined as + * 00 = 512, 01 = 1024, 10 = 2048, 11 = 4096 so assume this + * holds also for this older XOADC. + */ +#define ADC_ARB_USRP_DIG_PARAM_DEC_RATE0 BIT(5) +#define ADC_ARB_USRP_DIG_PARAM_DEC_RATE1 BIT(6) +#define ADC_ARB_USRP_DIG_PARAM_EN BIT(7) +#define ADC_DIG_PARAM_DEC_SHIFT 5 + +#define ADC_ARB_USRP_RSV 0x19B +#define ADC_ARB_USRP_RSV_RST BIT(0) +#define ADC_ARB_USRP_RSV_DTEST0 BIT(1) +#define ADC_ARB_USRP_RSV_DTEST1 BIT(2) +#define ADC_ARB_USRP_RSV_OP BIT(3) +#define ADC_ARB_USRP_RSV_IP_SEL0 BIT(4) +#define ADC_ARB_USRP_RSV_IP_SEL1 BIT(5) +#define ADC_ARB_USRP_RSV_IP_SEL2 BIT(6) +#define ADC_ARB_USRP_RSV_TRM BIT(7) +#define ADC_RSV_IP_SEL_SHIFT 4 + +#define ADC_ARB_USRP_DATA0 0x19D +#define ADC_ARB_USRP_DATA1 0x19C + +/** + * Physical channels which MUST exist on all PM variants in order to provide + * proper reference points for calibration. + * + * @PM8XXX_CHANNEL_INTERNAL: 625mV reference channel + * @PM8XXX_CHANNEL_125V: 1250mV reference channel + * @PM8XXX_CHANNEL_INTERNAL_2: 325mV reference channel + * @PM8XXX_CHANNEL_MUXOFF: channel to reduce input load on mux, apparently also + * measures XO temperature + */ +#define PM8XXX_CHANNEL_INTERNAL 0x0c +#define PM8XXX_CHANNEL_125V 0x0d +#define PM8XXX_CHANNEL_INTERNAL_2 0x0e +#define PM8XXX_CHANNEL_MUXOFF 0x0f + +/* + * PM8058 AMUX premux scaling, two bits. This is done of the channel before + * reaching the AMUX. + */ +#define PM8058_AMUX_PRESCALE_0 0x0 /* No scaling on the signal */ +#define PM8058_AMUX_PRESCALE_1 0x1 /* Unity scaling selected by the user */ +#define PM8058_AMUX_PRESCALE_1_DIV3 0x2 /* 1/3 prescaler on the input */ + +/* Defines reference voltage for the XOADC */ +#define AMUX_RSV0 0x0 /* XO_IN/XOADC_GND, special selection to read XO temp */ +#define AMUX_RSV1 0x1 /* PMIC_IN/XOADC_GND */ +#define AMUX_RSV2 0x2 /* PMIC_IN/BMS_CSP */ +#define AMUX_RSV3 0x3 /* not used */ +#define AMUX_RSV4 0x4 /* XOADC_GND/XOADC_GND */ +#define AMUX_RSV5 0x5 /* XOADC_VREF/XOADC_GND */ +#define XOADC_RSV_MAX 5 /* 3 bits 0..7, 3 and 6,7 are invalid */ + +/** + * struct xoadc_channel - encodes channel properties and defaults + * @datasheet_name: the hardwarename of this channel + * @pre_scale_mux: prescale (PM8058) or premux (PM8921) for selecting + * this channel. Both this and the amux channel is needed to uniquely + * identify a channel. Values 0..3. + * @amux_channel: value of the ADC_ARB_USRP_AMUX_CNTRL register for this + * channel, bits 4..7, selects the amux, values 0..f + * @prescale: the channels have hard-coded prescale ratios defined + * by the hardware, this tells us what it is + * @type: corresponding IIO channel type, usually IIO_VOLTAGE or + * IIO_TEMP + * @scale_fn_type: the liner interpolation etc to convert the + * ADC code to the value that IIO expects, in uV or millicelsius + * etc. This scale function can be pretty elaborate if different + * thermistors are connected or other hardware characteristics are + * deployed. + * @amux_ip_rsv: ratiometric scale value used by the analog muxer: this + * selects the reference voltage for ratiometric scaling + */ +struct xoadc_channel { + const char *datasheet_name; + u8 pre_scale_mux:2; + u8 amux_channel:4; + const struct vadc_prescale_ratio prescale; + enum iio_chan_type type; + enum vadc_scale_fn_type scale_fn_type; + u8 amux_ip_rsv:3; +}; + +/** + * struct xoadc_variant - encodes the XOADC variant characteristics + * @name: name of this PMIC variant + * @channels: the hardware channels and respective settings and defaults + * @broken_ratiometric: if the PMIC has broken ratiometric scaling (this + * is a known problem on PM8058) + * @prescaling: this variant uses AMUX bits 2 & 3 for prescaling (PM8058) + * @second_level_mux: this variant uses AMUX bits 2 & 3 for a second level + * mux + */ +struct xoadc_variant { + const char name[16]; + const struct xoadc_channel *channels; + bool broken_ratiometric; + bool prescaling; + bool second_level_mux; +}; + +/* + * XOADC_CHAN macro parameters: + * _dname: the name of the channel + * _presmux: prescaler (PM8058) or premux (PM8921) setting for this channel + * _amux: the value in bits 2..7 of the ADC_ARB_USRP_AMUX_CNTRL register + * for this channel. On some PMICs some of the bits select a prescaler, and + * on some PMICs some of the bits select various complex multiplex settings. + * _type: IIO channel type + * _prenum: prescaler numerator (dividend) + * _preden: prescaler denominator (divisor) + * _scale: scaling function type, this selects how the raw valued is mangled + * to output the actual processed measurement + * _amip: analog mux input parent when using ratiometric measurements + */ +#define XOADC_CHAN(_dname, _presmux, _amux, _type, _prenum, _preden, _scale, _amip) \ + { \ + .datasheet_name = __stringify(_dname), \ + .pre_scale_mux = _presmux, \ + .amux_channel = _amux, \ + .prescale = { .num = _prenum, .den = _preden }, \ + .type = _type, \ + .scale_fn_type = _scale, \ + .amux_ip_rsv = _amip, \ + } + +/* + * Taken from arch/arm/mach-msm/board-9615.c in the vendor tree: + * TODO: incomplete, needs testing. + */ +static const struct xoadc_channel pm8018_xoadc_channels[] = { + XOADC_CHAN(VCOIN, 0x00, 0x00, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VBAT, 0x00, 0x01, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VPH_PWR, 0x00, 0x02, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DIE_TEMP, 0x00, 0x0b, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1), + /* Used for battery ID or battery temperature */ + XOADC_CHAN(AMUX8, 0x00, 0x08, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV2), + XOADC_CHAN(INTERNAL, 0x00, 0x0c, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(125V, 0x00, 0x0d, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(MUXOFF, 0x00, 0x0f, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0), + { }, /* Sentinel */ +}; + +/* + * Taken from arch/arm/mach-msm/board-8930-pmic.c in the vendor tree: + * TODO: needs testing. + */ +static const struct xoadc_channel pm8038_xoadc_channels[] = { + XOADC_CHAN(VCOIN, 0x00, 0x00, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VBAT, 0x00, 0x01, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DCIN, 0x00, 0x02, IIO_VOLTAGE, 1, 6, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ICHG, 0x00, 0x03, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VPH_PWR, 0x00, 0x04, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX5, 0x00, 0x05, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX6, 0x00, 0x06, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX7, 0x00, 0x07, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* AMUX8 used for battery temperature in most cases */ + XOADC_CHAN(AMUX8, 0x00, 0x08, IIO_TEMP, 1, 1, SCALE_THERM_100K_PULLUP, AMUX_RSV2), + XOADC_CHAN(AMUX9, 0x00, 0x09, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(USB_VBUS, 0x00, 0x0a, IIO_VOLTAGE, 1, 4, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DIE_TEMP, 0x00, 0x0b, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1), + XOADC_CHAN(INTERNAL, 0x00, 0x0c, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(125V, 0x00, 0x0d, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(INTERNAL_2, 0x00, 0x0e, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(MUXOFF, 0x00, 0x0f, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0), + { }, /* Sentinel */ +}; + +/* + * This was created by cross-referencing the vendor tree + * arch/arm/mach-msm/board-msm8x60.c msm_adc_channels_data[] + * with the "channel types" (first field) to find the right + * configuration for these channels on an MSM8x60 i.e. PM8058 + * setup. + */ +static const struct xoadc_channel pm8058_xoadc_channels[] = { + XOADC_CHAN(VCOIN, 0x00, 0x00, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VBAT, 0x00, 0x01, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DCIN, 0x00, 0x02, IIO_VOLTAGE, 1, 10, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ICHG, 0x00, 0x03, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VPH_PWR, 0x00, 0x04, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + /* + * AMUX channels 5 thru 9 are referred to as MPP5 thru MPP9 in + * some code and documentation. But they are really just 5 + * channels just like any other. They are connected to a switching + * matrix where they can be routed to any of the MPPs, not just + * 1-to-1 onto MPP5 thru 9, so naming them MPP5 thru MPP9 is + * very confusing. + */ + XOADC_CHAN(AMUX5, 0x00, 0x05, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX6, 0x00, 0x06, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX7, 0x00, 0x07, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX8, 0x00, 0x08, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX9, 0x00, 0x09, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(USB_VBUS, 0x00, 0x0a, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DIE_TEMP, 0x00, 0x0b, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1), + XOADC_CHAN(INTERNAL, 0x00, 0x0c, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(125V, 0x00, 0x0d, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(INTERNAL_2, 0x00, 0x0e, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(MUXOFF, 0x00, 0x0f, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0), + /* There are also "unity" and divided by 3 channels (prescaler) but noone is using them */ + { }, /* Sentinel */ +}; + +/* + * The PM8921 has some pre-muxing on its channels, this comes from the vendor tree + * include/linux/mfd/pm8xxx/pm8xxx-adc.h + * board-flo-pmic.c (Nexus 7) and board-8064-pmic.c + */ +static const struct xoadc_channel pm8921_xoadc_channels[] = { + XOADC_CHAN(VCOIN, 0x00, 0x00, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VBAT, 0x00, 0x01, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DCIN, 0x00, 0x02, IIO_VOLTAGE, 1, 6, SCALE_DEFAULT, AMUX_RSV1), + /* channel "ICHG" is reserved and not used on PM8921 */ + XOADC_CHAN(VPH_PWR, 0x00, 0x04, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(IBAT, 0x00, 0x05, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* CHAN 6 & 7 (MPP1 & MPP2) are reserved for MPP channels on PM8921 */ + XOADC_CHAN(BATT_THERM, 0x00, 0x08, IIO_TEMP, 1, 1, SCALE_THERM_100K_PULLUP, AMUX_RSV1), + XOADC_CHAN(BATT_ID, 0x00, 0x09, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(USB_VBUS, 0x00, 0x0a, IIO_VOLTAGE, 1, 4, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DIE_TEMP, 0x00, 0x0b, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1), + XOADC_CHAN(INTERNAL, 0x00, 0x0c, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(125V, 0x00, 0x0d, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* FIXME: look into the scaling of this temperature */ + XOADC_CHAN(CHG_TEMP, 0x00, 0x0e, IIO_TEMP, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(MUXOFF, 0x00, 0x0f, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0), + /* The following channels have premux bit 0 set to 1 (all end in 4) */ + XOADC_CHAN(ATEST_8, 0x01, 0x00, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* Set scaling to 1/2 based on the name for these two */ + XOADC_CHAN(USB_SNS_DIV20, 0x01, 0x01, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DCIN_SNS_DIV20, 0x01, 0x02, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX3, 0x01, 0x03, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX4, 0x01, 0x04, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX5, 0x01, 0x05, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX6, 0x01, 0x06, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX7, 0x01, 0x07, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX8, 0x01, 0x08, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* Internal test signals, I think */ + XOADC_CHAN(ATEST_1, 0x01, 0x09, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_2, 0x01, 0x0a, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_3, 0x01, 0x0b, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_4, 0x01, 0x0c, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_5, 0x01, 0x0d, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_6, 0x01, 0x0e, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_7, 0x01, 0x0f, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* The following channels have premux bit 1 set to 1 (all end in 8) */ + /* I guess even ATEST8 will be divided by 3 here */ + XOADC_CHAN(ATEST_8, 0x02, 0x00, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + /* I guess div 2 div 3 becomes div 6 */ + XOADC_CHAN(USB_SNS_DIV20_DIV3, 0x02, 0x01, IIO_VOLTAGE, 1, 6, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DCIN_SNS_DIV20_DIV3, 0x02, 0x02, IIO_VOLTAGE, 1, 6, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX3_DIV3, 0x02, 0x03, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX4_DIV3, 0x02, 0x04, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX5_DIV3, 0x02, 0x05, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX6_DIV3, 0x02, 0x06, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX7_DIV3, 0x02, 0x07, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX8_DIV3, 0x02, 0x08, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_1_DIV3, 0x02, 0x09, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_2_DIV3, 0x02, 0x0a, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_3_DIV3, 0x02, 0x0b, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_4_DIV3, 0x02, 0x0c, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_5_DIV3, 0x02, 0x0d, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_6_DIV3, 0x02, 0x0e, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_7_DIV3, 0x02, 0x0f, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + { }, /* Sentinel */ +}; + +/** + * struct pm8xxx_chan_info - ADC channel information + * @name: name of this channel + * @hwchan: pointer to hardware channel information (muxing & scaling settings) + * @calibration: whether to use absolute or ratiometric calibration + * @scale_fn_type: scaling function type + * @decimation: 0,1,2,3 + * @amux_ip_rsv: ratiometric scale value if using ratiometric + * calibration: 0, 1, 2, 4, 5. + */ +struct pm8xxx_chan_info { + const char *name; + const struct xoadc_channel *hwchan; + enum vadc_calibration calibration; + u8 decimation:2; + u8 amux_ip_rsv:3; +}; + +/** + * struct pm8xxx_xoadc - state container for the XOADC + * @dev: pointer to device + * @map: regmap to access registers + * @vref: reference voltage regulator + * characteristics of the channels, and sensible default settings + * @nchans: number of channels, configured by the device tree + * @chans: the channel information per-channel, configured by the device tree + * @iio_chans: IIO channel specifiers + * @graph: linear calibration parameters for absolute and + * ratiometric measurements + * @complete: completion to indicate end of conversion + * @lock: lock to restrict access to the hardware to one client at the time + */ +struct pm8xxx_xoadc { + struct device *dev; + struct regmap *map; + const struct xoadc_variant *variant; + struct regulator *vref; + unsigned int nchans; + struct pm8xxx_chan_info *chans; + struct iio_chan_spec *iio_chans; + struct vadc_linear_graph graph[2]; + struct completion complete; + struct mutex lock; +}; + +static irqreturn_t pm8xxx_eoc_irq(int irq, void *d) +{ + struct iio_dev *indio_dev = d; + struct pm8xxx_xoadc *adc = iio_priv(indio_dev); + + complete(&adc->complete); + + return IRQ_HANDLED; +} + +static struct pm8xxx_chan_info * +pm8xxx_get_channel(struct pm8xxx_xoadc *adc, u8 chan) +{ + struct pm8xxx_chan_info *ch; + int i; + + for (i = 0; i < adc->nchans; i++) { + ch = &adc->chans[i]; + if (ch->hwchan->amux_channel == chan) + break; + } + if (i == adc->nchans) + return NULL; + + return ch; +} + +static int pm8xxx_read_channel_rsv(struct pm8xxx_xoadc *adc, + const struct pm8xxx_chan_info *ch, + u8 rsv, u16 *adc_code, + bool force_ratiometric) +{ + int ret; + unsigned int val; + u8 rsvmask, rsvval; + u8 lsb, msb; + + dev_dbg(adc->dev, "read channel \"%s\", amux %d, prescale/mux: %d, rsv %d\n", + ch->name, ch->hwchan->amux_channel, ch->hwchan->pre_scale_mux, rsv); + + mutex_lock(&adc->lock); + + /* Mux in this channel */ + val = ch->hwchan->amux_channel << ADC_AMUX_SEL_SHIFT; + val |= ch->hwchan->pre_scale_mux << ADC_AMUX_PREMUX_SHIFT; + ret = regmap_write(adc->map, ADC_ARB_USRP_AMUX_CNTRL, val); + if (ret) + goto unlock; + + /* Set up ratiometric scale value, mask off all bits except these */ + rsvmask = (ADC_ARB_USRP_RSV_RST | ADC_ARB_USRP_RSV_DTEST0 | + ADC_ARB_USRP_RSV_DTEST1 | ADC_ARB_USRP_RSV_OP); + if (adc->variant->broken_ratiometric && !force_ratiometric) { + /* + * Apparently the PM8058 has some kind of bug which is + * reflected in the vendor tree drivers/misc/pmix8058-xoadc.c + * which just hardcodes the RSV selector to SEL1 (0x20) for + * most cases and SEL0 (0x10) for the MUXOFF channel only. + * If we force ratiometric (currently only done when attempting + * to do ratiometric calibration) this doesn't seem to work + * very well and I suspect ratiometric conversion is simply + * broken or not supported on the PM8058. + * + * Maybe IO_SEL2 doesn't exist on PM8058 and bits 4 & 5 select + * the mode alone. + * + * Some PM8058 register documentation would be nice to get + * this right. + */ + if (ch->hwchan->amux_channel == PM8XXX_CHANNEL_MUXOFF) + rsvval = ADC_ARB_USRP_RSV_IP_SEL0; + else + rsvval = ADC_ARB_USRP_RSV_IP_SEL1; + } else { + if (rsv == 0xff) + rsvval = (ch->amux_ip_rsv << ADC_RSV_IP_SEL_SHIFT) | + ADC_ARB_USRP_RSV_TRM; + else + rsvval = (rsv << ADC_RSV_IP_SEL_SHIFT) | + ADC_ARB_USRP_RSV_TRM; + } + + ret = regmap_update_bits(adc->map, + ADC_ARB_USRP_RSV, + ~rsvmask, + rsvval); + if (ret) + goto unlock; + + ret = regmap_write(adc->map, ADC_ARB_USRP_ANA_PARAM, + ADC_ARB_USRP_ANA_PARAM_DIS); + if (ret) + goto unlock; + + /* Decimation factor */ + ret = regmap_write(adc->map, ADC_ARB_USRP_DIG_PARAM, + ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0 | + ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1 | + ch->decimation << ADC_DIG_PARAM_DEC_SHIFT); + if (ret) + goto unlock; + + ret = regmap_write(adc->map, ADC_ARB_USRP_ANA_PARAM, + ADC_ARB_USRP_ANA_PARAM_EN); + if (ret) + goto unlock; + + /* Enable the arbiter, the Qualcomm code does it twice like this */ + ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, + ADC_ARB_USRP_CNTRL_EN_ARB); + if (ret) + goto unlock; + ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, + ADC_ARB_USRP_CNTRL_EN_ARB); + if (ret) + goto unlock; + + + /* Fire a request! */ + reinit_completion(&adc->complete); + ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, + ADC_ARB_USRP_CNTRL_EN_ARB | + ADC_ARB_USRP_CNTRL_REQ); + if (ret) + goto unlock; + + /* Next the interrupt occurs */ + ret = wait_for_completion_timeout(&adc->complete, + VADC_CONV_TIME_MAX_US); + if (!ret) { + dev_err(adc->dev, "conversion timed out\n"); + ret = -ETIMEDOUT; + goto unlock; + } + + ret = regmap_read(adc->map, ADC_ARB_USRP_DATA0, &val); + if (ret) + goto unlock; + lsb = val; + ret = regmap_read(adc->map, ADC_ARB_USRP_DATA1, &val); + if (ret) + goto unlock; + msb = val; + *adc_code = (msb << 8) | lsb; + + /* Turn off the ADC by setting the arbiter to 0 twice */ + ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, 0); + if (ret) + goto unlock; + ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, 0); + if (ret) + goto unlock; + +unlock: + mutex_unlock(&adc->lock); + return ret; +} + +static int pm8xxx_read_channel(struct pm8xxx_xoadc *adc, + const struct pm8xxx_chan_info *ch, + u16 *adc_code) +{ + /* + * Normally we just use the ratiometric scale value (RSV) predefined + * for the channel, but during calibration we need to modify this + * so this wrapper is a helper hiding the more complex version. + */ + return pm8xxx_read_channel_rsv(adc, ch, 0xff, adc_code, false); +} + +static int pm8xxx_calibrate_device(struct pm8xxx_xoadc *adc) +{ + const struct pm8xxx_chan_info *ch; + u16 read_1250v; + u16 read_0625v; + u16 read_nomux_rsv5; + u16 read_nomux_rsv4; + int ret; + + adc->graph[VADC_CALIB_ABSOLUTE].dx = VADC_ABSOLUTE_RANGE_UV; + adc->graph[VADC_CALIB_RATIOMETRIC].dx = VADC_RATIOMETRIC_RANGE; + + /* Common reference channel calibration */ + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_125V); + if (!ch) + return -ENODEV; + ret = pm8xxx_read_channel(adc, ch, &read_1250v); + if (ret) { + dev_err(adc->dev, "could not read 1.25V reference channel\n"); + return -ENODEV; + } + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_INTERNAL); + if (!ch) + return -ENODEV; + ret = pm8xxx_read_channel(adc, ch, &read_0625v); + if (ret) { + dev_err(adc->dev, "could not read 0.625V reference channel\n"); + return -ENODEV; + } + if (read_1250v == read_0625v) { + dev_err(adc->dev, "read same ADC code for 1.25V and 0.625V\n"); + return -ENODEV; + } + + adc->graph[VADC_CALIB_ABSOLUTE].dy = read_1250v - read_0625v; + adc->graph[VADC_CALIB_ABSOLUTE].gnd = read_0625v; + + dev_info(adc->dev, "absolute calibration dx = %d uV, dy = %d units\n", + VADC_ABSOLUTE_RANGE_UV, adc->graph[VADC_CALIB_ABSOLUTE].dy); + + /* Ratiometric calibration */ + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_MUXOFF); + if (!ch) + return -ENODEV; + ret = pm8xxx_read_channel_rsv(adc, ch, AMUX_RSV5, + &read_nomux_rsv5, true); + if (ret) { + dev_err(adc->dev, "could not read MUXOFF reference channel\n"); + return -ENODEV; + } + ret = pm8xxx_read_channel_rsv(adc, ch, AMUX_RSV4, + &read_nomux_rsv4, true); + if (ret) { + dev_err(adc->dev, "could not read MUXOFF reference channel\n"); + return -ENODEV; + } + adc->graph[VADC_CALIB_RATIOMETRIC].dy = + read_nomux_rsv5 - read_nomux_rsv4; + adc->graph[VADC_CALIB_RATIOMETRIC].gnd = read_nomux_rsv4; + + dev_info(adc->dev, "ratiometric calibration dx = %d, dy = %d units\n", + VADC_RATIOMETRIC_RANGE, + adc->graph[VADC_CALIB_RATIOMETRIC].dy); + + return 0; +} + +static int pm8xxx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct pm8xxx_xoadc *adc = iio_priv(indio_dev); + const struct pm8xxx_chan_info *ch; + u16 adc_code; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ch = pm8xxx_get_channel(adc, chan->address); + if (!ch) { + dev_err(adc->dev, "no such channel %lu\n", + chan->address); + return -EINVAL; + } + ret = pm8xxx_read_channel(adc, ch, &adc_code); + if (ret) + return ret; + + ret = qcom_vadc_scale(ch->hwchan->scale_fn_type, + &adc->graph[ch->calibration], + &ch->hwchan->prescale, + (ch->calibration == VADC_CALIB_ABSOLUTE), + adc_code, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + ch = pm8xxx_get_channel(adc, chan->address); + if (!ch) { + dev_err(adc->dev, "no such channel %lu\n", + chan->address); + return -EINVAL; + } + ret = pm8xxx_read_channel(adc, ch, &adc_code); + if (ret) + return ret; + + *val = (int)adc_code; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int pm8xxx_of_xlate(struct iio_dev *indio_dev, + const struct of_phandle_args *iiospec) +{ + struct pm8xxx_xoadc *adc = iio_priv(indio_dev); + u8 pre_scale_mux; + u8 amux_channel; + unsigned int i; + + /* + * First cell is prescaler or premux, second cell is analog + * mux. + */ + if (iiospec->args_count != 2) { + dev_err(&indio_dev->dev, "wrong number of arguments for %s need 2 got %d\n", + iiospec->np->name, + iiospec->args_count); + return -EINVAL; + } + pre_scale_mux = (u8)iiospec->args[0]; + amux_channel = (u8)iiospec->args[1]; + dev_dbg(&indio_dev->dev, "pre scale/mux: %02x, amux: %02x\n", + pre_scale_mux, amux_channel); + + /* We need to match exactly on the prescale/premux and channel */ + for (i = 0; i < adc->nchans; i++) + if (adc->chans[i].hwchan->pre_scale_mux == pre_scale_mux && + adc->chans[i].hwchan->amux_channel == amux_channel) + return i; + + return -EINVAL; +} + +static const struct iio_info pm8xxx_xoadc_info = { + .driver_module = THIS_MODULE, + .of_xlate = pm8xxx_of_xlate, + .read_raw = pm8xxx_read_raw, +}; + +static int pm8xxx_xoadc_parse_channel(struct device *dev, + struct device_node *np, + const struct xoadc_channel *hw_channels, + struct iio_chan_spec *iio_chan, + struct pm8xxx_chan_info *ch) +{ + const char *name = np->name; + const struct xoadc_channel *hwchan; + u32 pre_scale_mux, amux_channel; + u32 rsv, dec; + int ret; + int chid; + + ret = of_property_read_u32_index(np, "reg", 0, &pre_scale_mux); + if (ret) { + dev_err(dev, "invalid pre scale/mux number %s\n", name); + return ret; + } + ret = of_property_read_u32_index(np, "reg", 1, &amux_channel); + if (ret) { + dev_err(dev, "invalid amux channel number %s\n", name); + return ret; + } + + /* Find the right channel setting */ + chid = 0; + hwchan = &hw_channels[0]; + while (hwchan && hwchan->datasheet_name) { + if (hwchan->pre_scale_mux == pre_scale_mux && + hwchan->amux_channel == amux_channel) + break; + hwchan++; + chid++; + } + /* The sentinel does not have a name assigned */ + if (!hwchan->datasheet_name) { + dev_err(dev, "could not locate channel %02x/%02x\n", + pre_scale_mux, amux_channel); + return -EINVAL; + } + ch->name = name; + ch->hwchan = hwchan; + /* Everyone seems to use absolute calibration except in special cases */ + ch->calibration = VADC_CALIB_ABSOLUTE; + /* Everyone seems to use default ("type 2") decimation */ + ch->decimation = VADC_DEF_DECIMATION; + + if (!of_property_read_u32(np, "qcom,ratiometric", &rsv)) { + ch->calibration = VADC_CALIB_RATIOMETRIC; + if (rsv > XOADC_RSV_MAX) { + dev_err(dev, "%s too large RSV value %d\n", name, rsv); + return -EINVAL; + } + if (rsv == AMUX_RSV3) { + dev_err(dev, "%s invalid RSV value %d\n", name, rsv); + return -EINVAL; + } + } + + /* Optional decimation, if omitted we use the default */ + ret = of_property_read_u32(np, "qcom,decimation", &dec); + if (!ret) { + ret = qcom_vadc_decimation_from_dt(dec); + if (ret < 0) { + dev_err(dev, "%s invalid decimation %d\n", + name, dec); + return ret; + } + ch->decimation = ret; + } + + iio_chan->channel = chid; + iio_chan->address = hwchan->amux_channel; + iio_chan->datasheet_name = hwchan->datasheet_name; + iio_chan->type = hwchan->type; + /* All channels are raw or processed */ + iio_chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED); + iio_chan->indexed = 1; + + dev_dbg(dev, "channel [PRESCALE/MUX: %02x AMUX: %02x] \"%s\" " + "ref voltage: %d, decimation %d " + "prescale %d/%d, scale function %d\n", + hwchan->pre_scale_mux, hwchan->amux_channel, ch->name, + ch->amux_ip_rsv, ch->decimation, hwchan->prescale.num, + hwchan->prescale.den, hwchan->scale_fn_type); + + return 0; +} + +static int pm8xxx_xoadc_parse_channels(struct pm8xxx_xoadc *adc, + struct device_node *np) +{ + struct device_node *child; + struct pm8xxx_chan_info *ch; + int ret; + int i; + + adc->nchans = of_get_available_child_count(np); + if (!adc->nchans) { + dev_err(adc->dev, "no channel children\n"); + return -ENODEV; + } + dev_dbg(adc->dev, "found %d ADC channels\n", adc->nchans); + + adc->iio_chans = devm_kcalloc(adc->dev, adc->nchans, + sizeof(*adc->iio_chans), GFP_KERNEL); + if (!adc->iio_chans) + return -ENOMEM; + + adc->chans = devm_kcalloc(adc->dev, adc->nchans, + sizeof(*adc->chans), GFP_KERNEL); + if (!adc->chans) + return -ENOMEM; + + i = 0; + for_each_available_child_of_node(np, child) { + ch = &adc->chans[i]; + ret = pm8xxx_xoadc_parse_channel(adc->dev, child, + adc->variant->channels, + &adc->iio_chans[i], + ch); + if (ret) { + of_node_put(child); + return ret; + } + i++; + } + + /* Check for required channels */ + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_125V); + if (!ch) { + dev_err(adc->dev, "missing 1.25V reference channel\n"); + return -ENODEV; + } + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_INTERNAL); + if (!ch) { + dev_err(adc->dev, "missing 0.625V reference channel\n"); + return -ENODEV; + } + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_MUXOFF); + if (!ch) { + dev_err(adc->dev, "missing MUXOFF reference channel\n"); + return -ENODEV; + } + + return 0; +} + +static int pm8xxx_xoadc_probe(struct platform_device *pdev) +{ + const struct xoadc_variant *variant; + struct pm8xxx_xoadc *adc; + struct iio_dev *indio_dev; + struct device_node *np = pdev->dev.of_node; + struct regmap *map; + struct device *dev = &pdev->dev; + int ret; + + variant = of_device_get_match_data(dev); + if (!variant) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + adc = iio_priv(indio_dev); + adc->dev = dev; + adc->variant = variant; + init_completion(&adc->complete); + mutex_init(&adc->lock); + + ret = pm8xxx_xoadc_parse_channels(adc, np); + if (ret) + return ret; + + map = dev_get_regmap(dev->parent, NULL); + if (!map) { + dev_err(dev, "parent regmap unavailable.\n"); + return -ENXIO; + } + adc->map = map; + + /* Bring up regulator */ + adc->vref = devm_regulator_get(dev, "xoadc-ref"); + if (IS_ERR(adc->vref)) { + dev_err(dev, "failed to get XOADC VREF regulator\n"); + return PTR_ERR(adc->vref); + } + ret = regulator_enable(adc->vref); + if (ret) { + dev_err(dev, "failed to enable XOADC VREF regulator\n"); + return ret; + } + + ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0), + pm8xxx_eoc_irq, NULL, 0, variant->name, indio_dev); + if (ret) { + dev_err(dev, "unable to request IRQ\n"); + goto out_disable_vref; + } + + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = np; + indio_dev->name = variant->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &pm8xxx_xoadc_info; + indio_dev->channels = adc->iio_chans; + indio_dev->num_channels = adc->nchans; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_disable_vref; + + ret = pm8xxx_calibrate_device(adc); + if (ret) + goto out_unreg_device; + + dev_info(dev, "%s XOADC driver enabled\n", variant->name); + + return 0; + +out_unreg_device: + iio_device_unregister(indio_dev); +out_disable_vref: + regulator_disable(adc->vref); + + return ret; +} + +static int pm8xxx_xoadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct pm8xxx_xoadc *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + regulator_disable(adc->vref); + + return 0; +} + +static const struct xoadc_variant pm8018_variant = { + .name = "PM8018-XOADC", + .channels = pm8018_xoadc_channels, +}; + +static const struct xoadc_variant pm8038_variant = { + .name = "PM8038-XOADC", + .channels = pm8038_xoadc_channels, +}; + +static const struct xoadc_variant pm8058_variant = { + .name = "PM8058-XOADC", + .channels = pm8058_xoadc_channels, + .broken_ratiometric = true, + .prescaling = true, +}; + +static const struct xoadc_variant pm8921_variant = { + .name = "PM8921-XOADC", + .channels = pm8921_xoadc_channels, + .second_level_mux = true, +}; + +static const struct of_device_id pm8xxx_xoadc_id_table[] = { + { + .compatible = "qcom,pm8018-adc", + .data = &pm8018_variant, + }, + { + .compatible = "qcom,pm8038-adc", + .data = &pm8038_variant, + }, + { + .compatible = "qcom,pm8058-adc", + .data = &pm8058_variant, + }, + { + .compatible = "qcom,pm8921-adc", + .data = &pm8921_variant, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, pm8xxx_xoadc_id_table); + +static struct platform_driver pm8xxx_xoadc_driver = { + .driver = { + .name = "pm8xxx-adc", + .of_match_table = pm8xxx_xoadc_id_table, + }, + .probe = pm8xxx_xoadc_probe, + .remove = pm8xxx_xoadc_remove, +}; +module_platform_driver(pm8xxx_xoadc_driver); + +MODULE_DESCRIPTION("PM8xxx XOADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pm8xxx-xoadc"); diff --git a/drivers/iio/adc/qcom-spmi-vadc.c b/drivers/iio/adc/qcom-spmi-vadc.c index 0a19761d656c..9e600bfd1765 100644 --- a/drivers/iio/adc/qcom-spmi-vadc.c +++ b/drivers/iio/adc/qcom-spmi-vadc.c @@ -28,6 +28,8 @@ #include <dt-bindings/iio/qcom,spmi-vadc.h> +#include "qcom-vadc-common.h" + /* VADC register and bit definitions */ #define VADC_REVISION2 0x1 #define VADC_REVISION2_SUPPORTED_VADC 1 @@ -75,84 +77,10 @@ #define VADC_DATA 0x60 /* 16 bits */ -#define VADC_CONV_TIME_MIN_US 2000 -#define VADC_CONV_TIME_MAX_US 2100 - -/* Min ADC code represents 0V */ -#define VADC_MIN_ADC_CODE 0x6000 -/* Max ADC code represents full-scale range of 1.8V */ -#define VADC_MAX_ADC_CODE 0xa800 - -#define VADC_ABSOLUTE_RANGE_UV 625000 -#define VADC_RATIOMETRIC_RANGE 1800 - -#define VADC_DEF_PRESCALING 0 /* 1:1 */ -#define VADC_DEF_DECIMATION 0 /* 512 */ -#define VADC_DEF_HW_SETTLE_TIME 0 /* 0 us */ -#define VADC_DEF_AVG_SAMPLES 0 /* 1 sample */ -#define VADC_DEF_CALIB_TYPE VADC_CALIB_ABSOLUTE - -#define VADC_DECIMATION_MIN 512 -#define VADC_DECIMATION_MAX 4096 - -#define VADC_HW_SETTLE_DELAY_MAX 10000 -#define VADC_AVG_SAMPLES_MAX 512 - -#define KELVINMIL_CELSIUSMIL 273150 - -#define PMI_CHG_SCALE_1 -138890 -#define PMI_CHG_SCALE_2 391750000000LL - #define VADC_CHAN_MIN VADC_USBIN #define VADC_CHAN_MAX VADC_LR_MUX3_BUF_PU1_PU2_XO_THERM /** - * struct vadc_map_pt - Map the graph representation for ADC channel - * @x: Represent the ADC digitized code. - * @y: Represent the physical data which can be temperature, voltage, - * resistance. - */ -struct vadc_map_pt { - s32 x; - s32 y; -}; - -/* - * VADC_CALIB_ABSOLUTE: uses the 625mV and 1.25V as reference channels. - * VADC_CALIB_RATIOMETRIC: uses the reference voltage (1.8V) and GND for - * calibration. - */ -enum vadc_calibration { - VADC_CALIB_ABSOLUTE = 0, - VADC_CALIB_RATIOMETRIC -}; - -/** - * struct vadc_linear_graph - Represent ADC characteristics. - * @dy: numerator slope to calculate the gain. - * @dx: denominator slope to calculate the gain. - * @gnd: A/D word of the ground reference used for the channel. - * - * Each ADC device has different offset and gain parameters which are - * computed to calibrate the device. - */ -struct vadc_linear_graph { - s32 dy; - s32 dx; - s32 gnd; -}; - -/** - * struct vadc_prescale_ratio - Represent scaling ratio for ADC input. - * @num: the inverse numerator of the gain applied to the input channel. - * @den: the inverse denominator of the gain applied to the input channel. - */ -struct vadc_prescale_ratio { - u32 num; - u32 den; -}; - -/** * struct vadc_channel_prop - VADC channel property. * @channel: channel number, refer to the channel list. * @calibration: calibration type. @@ -162,9 +90,8 @@ struct vadc_prescale_ratio { * start of conversion. * @avg_samples: ability to provide single result from the ADC * that is an average of multiple measurements. - * @scale_fn: Represents the scaling function to convert voltage + * @scale_fn_type: Represents the scaling function to convert voltage * physical units desired by the client for the channel. - * Referenced from enum vadc_scale_fn_type. */ struct vadc_channel_prop { unsigned int channel; @@ -173,7 +100,7 @@ struct vadc_channel_prop { unsigned int prescale; unsigned int hw_settle_time; unsigned int avg_samples; - unsigned int scale_fn; + enum vadc_scale_fn_type scale_fn_type; }; /** @@ -204,35 +131,6 @@ struct vadc_priv { struct mutex lock; }; -/** - * struct vadc_scale_fn - Scaling function prototype - * @scale: Function pointer to one of the scaling functions - * which takes the adc properties, channel properties, - * and returns the physical result. - */ -struct vadc_scale_fn { - int (*scale)(struct vadc_priv *, const struct vadc_channel_prop *, - u16, int *); -}; - -/** - * enum vadc_scale_fn_type - Scaling function to convert ADC code to - * physical scaled units for the channel. - * SCALE_DEFAULT: Default scaling to convert raw adc code to voltage (uV). - * SCALE_THERM_100K_PULLUP: Returns temperature in millidegC. - * Uses a mapping table with 100K pullup. - * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade. - * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC. - * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp - */ -enum vadc_scale_fn_type { - SCALE_DEFAULT = 0, - SCALE_THERM_100K_PULLUP, - SCALE_PMIC_THERM, - SCALE_XOTHERM, - SCALE_PMI_CHG_TEMP, -}; - static const struct vadc_prescale_ratio vadc_prescale_ratios[] = { {.num = 1, .den = 1}, {.num = 1, .den = 3}, @@ -244,44 +142,6 @@ static const struct vadc_prescale_ratio vadc_prescale_ratios[] = { {.num = 1, .den = 10} }; -/* Voltage to temperature */ -static const struct vadc_map_pt adcmap_100k_104ef_104fb[] = { - {1758, -40}, - {1742, -35}, - {1719, -30}, - {1691, -25}, - {1654, -20}, - {1608, -15}, - {1551, -10}, - {1483, -5}, - {1404, 0}, - {1315, 5}, - {1218, 10}, - {1114, 15}, - {1007, 20}, - {900, 25}, - {795, 30}, - {696, 35}, - {605, 40}, - {522, 45}, - {448, 50}, - {383, 55}, - {327, 60}, - {278, 65}, - {237, 70}, - {202, 75}, - {172, 80}, - {146, 85}, - {125, 90}, - {107, 95}, - {92, 100}, - {79, 105}, - {68, 110}, - {59, 115}, - {51, 120}, - {44, 125} -}; - static int vadc_read(struct vadc_priv *vadc, u16 offset, u8 *data) { return regmap_bulk_read(vadc->regmap, vadc->base + offset, data, 1); @@ -553,159 +413,6 @@ err: return ret; } -static int vadc_map_voltage_temp(const struct vadc_map_pt *pts, - u32 tablesize, s32 input, s64 *output) -{ - bool descending = 1; - u32 i = 0; - - if (!pts) - return -EINVAL; - - /* Check if table is descending or ascending */ - if (tablesize > 1) { - if (pts[0].x < pts[1].x) - descending = 0; - } - - while (i < tablesize) { - if ((descending) && (pts[i].x < input)) { - /* table entry is less than measured*/ - /* value and table is descending, stop */ - break; - } else if ((!descending) && - (pts[i].x > input)) { - /* table entry is greater than measured*/ - /*value and table is ascending, stop */ - break; - } - i++; - } - - if (i == 0) { - *output = pts[0].y; - } else if (i == tablesize) { - *output = pts[tablesize - 1].y; - } else { - /* result is between search_index and search_index-1 */ - /* interpolate linearly */ - *output = (((s32)((pts[i].y - pts[i - 1].y) * - (input - pts[i - 1].x)) / - (pts[i].x - pts[i - 1].x)) + - pts[i - 1].y); - } - - return 0; -} - -static void vadc_scale_calib(struct vadc_priv *vadc, u16 adc_code, - const struct vadc_channel_prop *prop, - s64 *scale_voltage) -{ - *scale_voltage = (adc_code - - vadc->graph[prop->calibration].gnd); - *scale_voltage *= vadc->graph[prop->calibration].dx; - *scale_voltage = div64_s64(*scale_voltage, - vadc->graph[prop->calibration].dy); - if (prop->calibration == VADC_CALIB_ABSOLUTE) - *scale_voltage += - vadc->graph[prop->calibration].dx; - - if (*scale_voltage < 0) - *scale_voltage = 0; -} - -static int vadc_scale_volt(struct vadc_priv *vadc, - const struct vadc_channel_prop *prop, u16 adc_code, - int *result_uv) -{ - const struct vadc_prescale_ratio *prescale; - s64 voltage = 0, result = 0; - - vadc_scale_calib(vadc, adc_code, prop, &voltage); - - prescale = &vadc_prescale_ratios[prop->prescale]; - voltage = voltage * prescale->den; - result = div64_s64(voltage, prescale->num); - *result_uv = result; - - return 0; -} - -static int vadc_scale_therm(struct vadc_priv *vadc, - const struct vadc_channel_prop *prop, u16 adc_code, - int *result_mdec) -{ - s64 voltage = 0, result = 0; - - vadc_scale_calib(vadc, adc_code, prop, &voltage); - - if (prop->calibration == VADC_CALIB_ABSOLUTE) - voltage = div64_s64(voltage, 1000); - - vadc_map_voltage_temp(adcmap_100k_104ef_104fb, - ARRAY_SIZE(adcmap_100k_104ef_104fb), - voltage, &result); - result *= 1000; - *result_mdec = result; - - return 0; -} - -static int vadc_scale_die_temp(struct vadc_priv *vadc, - const struct vadc_channel_prop *prop, - u16 adc_code, int *result_mdec) -{ - const struct vadc_prescale_ratio *prescale; - s64 voltage = 0; - u64 temp; /* Temporary variable for do_div */ - - vadc_scale_calib(vadc, adc_code, prop, &voltage); - - if (voltage > 0) { - prescale = &vadc_prescale_ratios[prop->prescale]; - temp = voltage * prescale->den; - do_div(temp, prescale->num * 2); - voltage = temp; - } else { - voltage = 0; - } - - voltage -= KELVINMIL_CELSIUSMIL; - *result_mdec = voltage; - - return 0; -} - -static int vadc_scale_chg_temp(struct vadc_priv *vadc, - const struct vadc_channel_prop *prop, - u16 adc_code, int *result_mdec) -{ - const struct vadc_prescale_ratio *prescale; - s64 voltage = 0, result = 0; - - vadc_scale_calib(vadc, adc_code, prop, &voltage); - - prescale = &vadc_prescale_ratios[prop->prescale]; - voltage = voltage * prescale->den; - voltage = div64_s64(voltage, prescale->num); - voltage = ((PMI_CHG_SCALE_1) * (voltage * 2)); - voltage = (voltage + PMI_CHG_SCALE_2); - result = div64_s64(voltage, 1000000); - *result_mdec = result; - - return 0; -} - -static int vadc_decimation_from_dt(u32 value) -{ - if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN || - value > VADC_DECIMATION_MAX) - return -EINVAL; - - return __ffs64(value / VADC_DECIMATION_MIN); -} - static int vadc_prescaling_from_dt(u32 num, u32 den) { unsigned int pre; @@ -742,14 +449,6 @@ static int vadc_avg_samples_from_dt(u32 value) return __ffs64(value); } -static struct vadc_scale_fn scale_fn[] = { - [SCALE_DEFAULT] = {vadc_scale_volt}, - [SCALE_THERM_100K_PULLUP] = {vadc_scale_therm}, - [SCALE_PMIC_THERM] = {vadc_scale_die_temp}, - [SCALE_XOTHERM] = {vadc_scale_therm}, - [SCALE_PMI_CHG_TEMP] = {vadc_scale_chg_temp}, -}; - static int vadc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -766,7 +465,13 @@ static int vadc_read_raw(struct iio_dev *indio_dev, if (ret) break; - scale_fn[prop->scale_fn].scale(vadc, prop, adc_code, val); + ret = qcom_vadc_scale(prop->scale_fn_type, + &vadc->graph[prop->calibration], + &vadc_prescale_ratios[prop->prescale], + (prop->calibration == VADC_CALIB_ABSOLUTE), + adc_code, val); + if (ret) + break; return IIO_VAL_INT; case IIO_CHAN_INFO_RAW: @@ -809,7 +514,7 @@ struct vadc_channels { unsigned int prescale_index; enum iio_chan_type type; long info_mask; - unsigned int scale_fn; + enum vadc_scale_fn_type scale_fn_type; }; #define VADC_CHAN(_dname, _type, _mask, _pre, _scale) \ @@ -818,7 +523,7 @@ struct vadc_channels { .prescale_index = _pre, \ .type = _type, \ .info_mask = _mask, \ - .scale_fn = _scale \ + .scale_fn_type = _scale \ }, \ #define VADC_NO_CHAN(_dname, _type, _mask, _pre) \ @@ -976,7 +681,7 @@ static int vadc_get_dt_channel_data(struct device *dev, ret = of_property_read_u32(node, "qcom,decimation", &value); if (!ret) { - ret = vadc_decimation_from_dt(value); + ret = qcom_vadc_decimation_from_dt(value); if (ret < 0) { dev_err(dev, "%02x invalid decimation %d\n", chan, value); @@ -1068,7 +773,7 @@ static int vadc_get_dt_data(struct vadc_priv *vadc, struct device_node *node) return ret; } - prop.scale_fn = vadc_chans[prop.channel].scale_fn; + prop.scale_fn_type = vadc_chans[prop.channel].scale_fn_type; vadc->chan_props[index] = prop; vadc_chan = &vadc_chans[prop.channel]; diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c new file mode 100644 index 000000000000..102fc51b10aa --- /dev/null +++ b/drivers/iio/adc/qcom-vadc-common.c @@ -0,0 +1,230 @@ +#include <linux/bug.h> +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <linux/math64.h> +#include <linux/log2.h> +#include <linux/err.h> + +#include "qcom-vadc-common.h" + +/* Voltage to temperature */ +static const struct vadc_map_pt adcmap_100k_104ef_104fb[] = { + {1758, -40}, + {1742, -35}, + {1719, -30}, + {1691, -25}, + {1654, -20}, + {1608, -15}, + {1551, -10}, + {1483, -5}, + {1404, 0}, + {1315, 5}, + {1218, 10}, + {1114, 15}, + {1007, 20}, + {900, 25}, + {795, 30}, + {696, 35}, + {605, 40}, + {522, 45}, + {448, 50}, + {383, 55}, + {327, 60}, + {278, 65}, + {237, 70}, + {202, 75}, + {172, 80}, + {146, 85}, + {125, 90}, + {107, 95}, + {92, 100}, + {79, 105}, + {68, 110}, + {59, 115}, + {51, 120}, + {44, 125} +}; + +static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts, + u32 tablesize, s32 input, s64 *output) +{ + bool descending = 1; + u32 i = 0; + + if (!pts) + return -EINVAL; + + /* Check if table is descending or ascending */ + if (tablesize > 1) { + if (pts[0].x < pts[1].x) + descending = 0; + } + + while (i < tablesize) { + if ((descending) && (pts[i].x < input)) { + /* table entry is less than measured*/ + /* value and table is descending, stop */ + break; + } else if ((!descending) && + (pts[i].x > input)) { + /* table entry is greater than measured*/ + /*value and table is ascending, stop */ + break; + } + i++; + } + + if (i == 0) { + *output = pts[0].y; + } else if (i == tablesize) { + *output = pts[tablesize - 1].y; + } else { + /* result is between search_index and search_index-1 */ + /* interpolate linearly */ + *output = (((s32)((pts[i].y - pts[i - 1].y) * + (input - pts[i - 1].x)) / + (pts[i].x - pts[i - 1].x)) + + pts[i - 1].y); + } + + return 0; +} + +static void qcom_vadc_scale_calib(const struct vadc_linear_graph *calib_graph, + u16 adc_code, + bool absolute, + s64 *scale_voltage) +{ + *scale_voltage = (adc_code - calib_graph->gnd); + *scale_voltage *= calib_graph->dx; + *scale_voltage = div64_s64(*scale_voltage, calib_graph->dy); + if (absolute) + *scale_voltage += calib_graph->dx; + + if (*scale_voltage < 0) + *scale_voltage = 0; +} + +static int qcom_vadc_scale_volt(const struct vadc_linear_graph *calib_graph, + const struct vadc_prescale_ratio *prescale, + bool absolute, u16 adc_code, + int *result_uv) +{ + s64 voltage = 0, result = 0; + + qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); + + voltage = voltage * prescale->den; + result = div64_s64(voltage, prescale->num); + *result_uv = result; + + return 0; +} + +static int qcom_vadc_scale_therm(const struct vadc_linear_graph *calib_graph, + const struct vadc_prescale_ratio *prescale, + bool absolute, u16 adc_code, + int *result_mdec) +{ + s64 voltage = 0, result = 0; + int ret; + + qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); + + if (absolute) + voltage = div64_s64(voltage, 1000); + + ret = qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb, + ARRAY_SIZE(adcmap_100k_104ef_104fb), + voltage, &result); + if (ret) + return ret; + + result *= 1000; + *result_mdec = result; + + return 0; +} + +static int qcom_vadc_scale_die_temp(const struct vadc_linear_graph *calib_graph, + const struct vadc_prescale_ratio *prescale, + bool absolute, + u16 adc_code, int *result_mdec) +{ + s64 voltage = 0; + u64 temp; /* Temporary variable for do_div */ + + qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); + + if (voltage > 0) { + temp = voltage * prescale->den; + do_div(temp, prescale->num * 2); + voltage = temp; + } else { + voltage = 0; + } + + voltage -= KELVINMIL_CELSIUSMIL; + *result_mdec = voltage; + + return 0; +} + +static int qcom_vadc_scale_chg_temp(const struct vadc_linear_graph *calib_graph, + const struct vadc_prescale_ratio *prescale, + bool absolute, + u16 adc_code, int *result_mdec) +{ + s64 voltage = 0, result = 0; + + qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); + + voltage = voltage * prescale->den; + voltage = div64_s64(voltage, prescale->num); + voltage = ((PMI_CHG_SCALE_1) * (voltage * 2)); + voltage = (voltage + PMI_CHG_SCALE_2); + result = div64_s64(voltage, 1000000); + *result_mdec = result; + + return 0; +} + +int qcom_vadc_scale(enum vadc_scale_fn_type scaletype, + const struct vadc_linear_graph *calib_graph, + const struct vadc_prescale_ratio *prescale, + bool absolute, + u16 adc_code, int *result) +{ + switch (scaletype) { + case SCALE_DEFAULT: + return qcom_vadc_scale_volt(calib_graph, prescale, + absolute, adc_code, + result); + case SCALE_THERM_100K_PULLUP: + case SCALE_XOTHERM: + return qcom_vadc_scale_therm(calib_graph, prescale, + absolute, adc_code, + result); + case SCALE_PMIC_THERM: + return qcom_vadc_scale_die_temp(calib_graph, prescale, + absolute, adc_code, + result); + case SCALE_PMI_CHG_TEMP: + return qcom_vadc_scale_chg_temp(calib_graph, prescale, + absolute, adc_code, + result); + default: + return -EINVAL; + } +} +EXPORT_SYMBOL(qcom_vadc_scale); + +int qcom_vadc_decimation_from_dt(u32 value) +{ + if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN || + value > VADC_DECIMATION_MAX) + return -EINVAL; + + return __ffs64(value / VADC_DECIMATION_MIN); +} +EXPORT_SYMBOL(qcom_vadc_decimation_from_dt); diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h new file mode 100644 index 000000000000..63c872a70adc --- /dev/null +++ b/drivers/iio/adc/qcom-vadc-common.h @@ -0,0 +1,108 @@ +/* + * Code shared between the different Qualcomm PMIC voltage ADCs + */ + +#ifndef QCOM_VADC_COMMON_H +#define QCOM_VADC_COMMON_H + +#define VADC_CONV_TIME_MIN_US 2000 +#define VADC_CONV_TIME_MAX_US 2100 + +/* Min ADC code represents 0V */ +#define VADC_MIN_ADC_CODE 0x6000 +/* Max ADC code represents full-scale range of 1.8V */ +#define VADC_MAX_ADC_CODE 0xa800 + +#define VADC_ABSOLUTE_RANGE_UV 625000 +#define VADC_RATIOMETRIC_RANGE 1800 + +#define VADC_DEF_PRESCALING 0 /* 1:1 */ +#define VADC_DEF_DECIMATION 0 /* 512 */ +#define VADC_DEF_HW_SETTLE_TIME 0 /* 0 us */ +#define VADC_DEF_AVG_SAMPLES 0 /* 1 sample */ +#define VADC_DEF_CALIB_TYPE VADC_CALIB_ABSOLUTE + +#define VADC_DECIMATION_MIN 512 +#define VADC_DECIMATION_MAX 4096 + +#define VADC_HW_SETTLE_DELAY_MAX 10000 +#define VADC_AVG_SAMPLES_MAX 512 + +#define KELVINMIL_CELSIUSMIL 273150 + +#define PMI_CHG_SCALE_1 -138890 +#define PMI_CHG_SCALE_2 391750000000LL + +/** + * struct vadc_map_pt - Map the graph representation for ADC channel + * @x: Represent the ADC digitized code. + * @y: Represent the physical data which can be temperature, voltage, + * resistance. + */ +struct vadc_map_pt { + s32 x; + s32 y; +}; + +/* + * VADC_CALIB_ABSOLUTE: uses the 625mV and 1.25V as reference channels. + * VADC_CALIB_RATIOMETRIC: uses the reference voltage (1.8V) and GND for + * calibration. + */ +enum vadc_calibration { + VADC_CALIB_ABSOLUTE = 0, + VADC_CALIB_RATIOMETRIC +}; + +/** + * struct vadc_linear_graph - Represent ADC characteristics. + * @dy: numerator slope to calculate the gain. + * @dx: denominator slope to calculate the gain. + * @gnd: A/D word of the ground reference used for the channel. + * + * Each ADC device has different offset and gain parameters which are + * computed to calibrate the device. + */ +struct vadc_linear_graph { + s32 dy; + s32 dx; + s32 gnd; +}; + +/** + * struct vadc_prescale_ratio - Represent scaling ratio for ADC input. + * @num: the inverse numerator of the gain applied to the input channel. + * @den: the inverse denominator of the gain applied to the input channel. + */ +struct vadc_prescale_ratio { + u32 num; + u32 den; +}; + +/** + * enum vadc_scale_fn_type - Scaling function to convert ADC code to + * physical scaled units for the channel. + * SCALE_DEFAULT: Default scaling to convert raw adc code to voltage (uV). + * SCALE_THERM_100K_PULLUP: Returns temperature in millidegC. + * Uses a mapping table with 100K pullup. + * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade. + * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC. + * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp + */ +enum vadc_scale_fn_type { + SCALE_DEFAULT = 0, + SCALE_THERM_100K_PULLUP, + SCALE_PMIC_THERM, + SCALE_XOTHERM, + SCALE_PMI_CHG_TEMP, +}; + +int qcom_vadc_scale(enum vadc_scale_fn_type scaletype, + const struct vadc_linear_graph *calib_graph, + const struct vadc_prescale_ratio *prescale, + bool absolute, + u16 adc_code, int *result_mdec); + +int qcom_vadc_decimation_from_dt(u32 value); + +#endif /* QCOM_VADC_COMMON_H */ diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c index 85d701291654..ae6d3324f518 100644 --- a/drivers/iio/adc/rockchip_saradc.c +++ b/drivers/iio/adc/rockchip_saradc.c @@ -109,7 +109,7 @@ static int rockchip_saradc_read_raw(struct iio_dev *indio_dev, static irqreturn_t rockchip_saradc_isr(int irq, void *dev_id) { - struct rockchip_saradc *info = (struct rockchip_saradc *)dev_id; + struct rockchip_saradc *info = dev_id; /* Read value */ info->last_val = readl_relaxed(info->regs + SARADC_DATA); diff --git a/drivers/iio/adc/spear_adc.c b/drivers/iio/adc/spear_adc.c new file mode 100644 index 000000000000..5dd61f6a57b9 --- /dev/null +++ b/drivers/iio/adc/spear_adc.c @@ -0,0 +1,395 @@ +/* + * ST SPEAr ADC driver + * + * Copyright 2012 Stefan Roese <sr@denx.de> + * + * Licensed under the GPL-2. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/completion.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* SPEAR registers definitions */ +#define SPEAR600_ADC_SCAN_RATE_LO(x) ((x) & 0xFFFF) +#define SPEAR600_ADC_SCAN_RATE_HI(x) (((x) >> 0x10) & 0xFFFF) +#define SPEAR_ADC_CLK_LOW(x) (((x) & 0xf) << 0) +#define SPEAR_ADC_CLK_HIGH(x) (((x) & 0xf) << 4) + +/* Bit definitions for SPEAR_ADC_STATUS */ +#define SPEAR_ADC_STATUS_START_CONVERSION BIT(0) +#define SPEAR_ADC_STATUS_CHANNEL_NUM(x) ((x) << 1) +#define SPEAR_ADC_STATUS_ADC_ENABLE BIT(4) +#define SPEAR_ADC_STATUS_AVG_SAMPLE(x) ((x) << 5) +#define SPEAR_ADC_STATUS_VREF_INTERNAL BIT(9) + +#define SPEAR_ADC_DATA_MASK 0x03ff +#define SPEAR_ADC_DATA_BITS 10 + +#define SPEAR_ADC_MOD_NAME "spear-adc" + +#define SPEAR_ADC_CHANNEL_NUM 8 + +#define SPEAR_ADC_CLK_MIN 2500000 +#define SPEAR_ADC_CLK_MAX 20000000 + +struct adc_regs_spear3xx { + u32 status; + u32 average; + u32 scan_rate; + u32 clk; /* Not avail for 1340 & 1310 */ + u32 ch_ctrl[SPEAR_ADC_CHANNEL_NUM]; + u32 ch_data[SPEAR_ADC_CHANNEL_NUM]; +}; + +struct chan_data { + u32 lsb; + u32 msb; +}; + +struct adc_regs_spear6xx { + u32 status; + u32 pad[2]; + u32 clk; + u32 ch_ctrl[SPEAR_ADC_CHANNEL_NUM]; + struct chan_data ch_data[SPEAR_ADC_CHANNEL_NUM]; + u32 scan_rate_lo; + u32 scan_rate_hi; + struct chan_data average; +}; + +struct spear_adc_state { + struct device_node *np; + struct adc_regs_spear3xx __iomem *adc_base_spear3xx; + struct adc_regs_spear6xx __iomem *adc_base_spear6xx; + struct clk *clk; + struct completion completion; + u32 current_clk; + u32 sampling_freq; + u32 avg_samples; + u32 vref_external; + u32 value; +}; + +/* + * Functions to access some SPEAr ADC register. Abstracted into + * static inline functions, because of different register offsets + * on different SoC variants (SPEAr300 vs SPEAr600 etc). + */ +static void spear_adc_set_status(struct spear_adc_state *st, u32 val) +{ + __raw_writel(val, &st->adc_base_spear6xx->status); +} + +static void spear_adc_set_clk(struct spear_adc_state *st, u32 val) +{ + u32 clk_high, clk_low, count; + u32 apb_clk = clk_get_rate(st->clk); + + count = DIV_ROUND_UP(apb_clk, val); + clk_low = count / 2; + clk_high = count - clk_low; + st->current_clk = apb_clk / count; + + __raw_writel(SPEAR_ADC_CLK_LOW(clk_low) | SPEAR_ADC_CLK_HIGH(clk_high), + &st->adc_base_spear6xx->clk); +} + +static void spear_adc_set_ctrl(struct spear_adc_state *st, int n, + u32 val) +{ + __raw_writel(val, &st->adc_base_spear6xx->ch_ctrl[n]); +} + +static u32 spear_adc_get_average(struct spear_adc_state *st) +{ + if (of_device_is_compatible(st->np, "st,spear600-adc")) { + return __raw_readl(&st->adc_base_spear6xx->average.msb) & + SPEAR_ADC_DATA_MASK; + } else { + return __raw_readl(&st->adc_base_spear3xx->average) & + SPEAR_ADC_DATA_MASK; + } +} + +static void spear_adc_set_scanrate(struct spear_adc_state *st, u32 rate) +{ + if (of_device_is_compatible(st->np, "st,spear600-adc")) { + __raw_writel(SPEAR600_ADC_SCAN_RATE_LO(rate), + &st->adc_base_spear6xx->scan_rate_lo); + __raw_writel(SPEAR600_ADC_SCAN_RATE_HI(rate), + &st->adc_base_spear6xx->scan_rate_hi); + } else { + __raw_writel(rate, &st->adc_base_spear3xx->scan_rate); + } +} + +static int spear_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct spear_adc_state *st = iio_priv(indio_dev); + u32 status; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + + status = SPEAR_ADC_STATUS_CHANNEL_NUM(chan->channel) | + SPEAR_ADC_STATUS_AVG_SAMPLE(st->avg_samples) | + SPEAR_ADC_STATUS_START_CONVERSION | + SPEAR_ADC_STATUS_ADC_ENABLE; + if (st->vref_external == 0) + status |= SPEAR_ADC_STATUS_VREF_INTERNAL; + + spear_adc_set_status(st, status); + wait_for_completion(&st->completion); /* set by ISR */ + *val = st->value; + + mutex_unlock(&indio_dev->mlock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = st->vref_external; + *val2 = SPEAR_ADC_DATA_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->current_clk; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int spear_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct spear_adc_state *st = iio_priv(indio_dev); + int ret = 0; + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + if ((val < SPEAR_ADC_CLK_MIN) || + (val > SPEAR_ADC_CLK_MAX) || + (val2 != 0)) { + ret = -EINVAL; + goto out; + } + + spear_adc_set_clk(st, val); + +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +#define SPEAR_ADC_CHAN(idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .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),\ + .channel = idx, \ +} + +static const struct iio_chan_spec spear_adc_iio_channels[] = { + SPEAR_ADC_CHAN(0), + SPEAR_ADC_CHAN(1), + SPEAR_ADC_CHAN(2), + SPEAR_ADC_CHAN(3), + SPEAR_ADC_CHAN(4), + SPEAR_ADC_CHAN(5), + SPEAR_ADC_CHAN(6), + SPEAR_ADC_CHAN(7), +}; + +static irqreturn_t spear_adc_isr(int irq, void *dev_id) +{ + struct spear_adc_state *st = dev_id; + + /* Read value to clear IRQ */ + st->value = spear_adc_get_average(st); + complete(&st->completion); + + return IRQ_HANDLED; +} + +static int spear_adc_configure(struct spear_adc_state *st) +{ + int i; + + /* Reset ADC core */ + spear_adc_set_status(st, 0); + __raw_writel(0, &st->adc_base_spear6xx->clk); + for (i = 0; i < 8; i++) + spear_adc_set_ctrl(st, i, 0); + spear_adc_set_scanrate(st, 0); + + spear_adc_set_clk(st, st->sampling_freq); + + return 0; +} + +static const struct iio_info spear_adc_info = { + .read_raw = &spear_adc_read_raw, + .write_raw = &spear_adc_write_raw, + .driver_module = THIS_MODULE, +}; + +static int spear_adc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct spear_adc_state *st; + struct resource *res; + struct iio_dev *indio_dev = NULL; + int ret = -ENODEV; + int irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct spear_adc_state)); + if (!indio_dev) { + dev_err(dev, "failed allocating iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + st->np = np; + + /* + * SPEAr600 has a different register layout than other SPEAr SoC's + * (e.g. SPEAr3xx). Let's provide two register base addresses + * to support multi-arch kernels. + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + st->adc_base_spear6xx = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(st->adc_base_spear6xx)) + return PTR_ERR(st->adc_base_spear6xx); + + st->adc_base_spear3xx = + (struct adc_regs_spear3xx __iomem *)st->adc_base_spear6xx; + + st->clk = devm_clk_get(dev, NULL); + if (IS_ERR(st->clk)) { + dev_err(dev, "failed getting clock\n"); + return PTR_ERR(st->clk); + } + + ret = clk_prepare_enable(st->clk); + if (ret) { + dev_err(dev, "failed enabling clock\n"); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(dev, "failed getting interrupt resource\n"); + ret = -EINVAL; + goto errout2; + } + + ret = devm_request_irq(dev, irq, spear_adc_isr, 0, SPEAR_ADC_MOD_NAME, + st); + if (ret < 0) { + dev_err(dev, "failed requesting interrupt\n"); + goto errout2; + } + + if (of_property_read_u32(np, "sampling-frequency", + &st->sampling_freq)) { + dev_err(dev, "sampling-frequency missing in DT\n"); + ret = -EINVAL; + goto errout2; + } + + /* + * Optional avg_samples defaults to 0, resulting in single data + * conversion + */ + of_property_read_u32(np, "average-samples", &st->avg_samples); + + /* + * Optional vref_external defaults to 0, resulting in internal vref + * selection + */ + of_property_read_u32(np, "vref-external", &st->vref_external); + + spear_adc_configure(st); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&st->completion); + + indio_dev->name = SPEAR_ADC_MOD_NAME; + indio_dev->dev.parent = dev; + indio_dev->info = &spear_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = spear_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(spear_adc_iio_channels); + + ret = iio_device_register(indio_dev); + if (ret) + goto errout2; + + dev_info(dev, "SPEAR ADC driver loaded, IRQ %d\n", irq); + + return 0; + +errout2: + clk_disable_unprepare(st->clk); + return ret; +} + +static int spear_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct spear_adc_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + clk_disable_unprepare(st->clk); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id spear_adc_dt_ids[] = { + { .compatible = "st,spear600-adc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, spear_adc_dt_ids); +#endif + +static struct platform_driver spear_adc_driver = { + .probe = spear_adc_probe, + .remove = spear_adc_remove, + .driver = { + .name = SPEAR_ADC_MOD_NAME, + .of_match_table = of_match_ptr(spear_adc_dt_ids), + }, +}; + +module_platform_driver(spear_adc_driver); + +MODULE_AUTHOR("Stefan Roese <sr@denx.de>"); +MODULE_DESCRIPTION("SPEAr ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index 9b49a6addc2a..c28e7ff80e11 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -60,6 +60,8 @@ #define STM32F4_EOC BIT(1) /* STM32F4_ADC_CR1 - bit fields */ +#define STM32F4_RES_SHIFT 24 +#define STM32F4_RES_MASK GENMASK(25, 24) #define STM32F4_SCAN BIT(8) #define STM32F4_EOCIE BIT(5) @@ -141,6 +143,7 @@ struct stm32_adc_regs { * @lock: spinlock * @bufi: data buffer index * @num_conv: expected number of scan conversions + * @res: data resolution (e.g. RES bitfield value) * @trigger_polarity: external trigger polarity (e.g. exten) * @dma_chan: dma channel * @rx_buf: dma rx buffer cpu address @@ -157,6 +160,7 @@ struct stm32_adc { spinlock_t lock; /* interrupt lock */ unsigned int bufi; unsigned int num_conv; + u32 res; u32 trigger_polarity; struct dma_chan *dma_chan; u8 *rx_buf; @@ -196,6 +200,11 @@ static const struct stm32_adc_chan_spec stm32f4_adc123_channels[] = { { IIO_VOLTAGE, 15, "in15" }, }; +static const unsigned int stm32f4_adc_resolutions[] = { + /* sorted values so the index matches RES[1:0] in STM32F4_ADC_CR1 */ + 12, 10, 8, 6, +}; + /** * stm32f4_sq - describe regular sequence registers * - L: sequence len (register & bit field) @@ -302,6 +311,14 @@ static void stm32_adc_conv_irq_disable(struct stm32_adc *adc) stm32_adc_clr_bits(adc, STM32F4_ADC_CR1, STM32F4_EOCIE); } +static void stm32_adc_set_res(struct stm32_adc *adc) +{ + u32 val = stm32_adc_readl(adc, STM32F4_ADC_CR1); + + val = (val & ~STM32F4_RES_MASK) | (adc->res << STM32F4_RES_SHIFT); + stm32_adc_writel(adc, STM32F4_ADC_CR1, val); +} + /** * stm32_adc_start_conv() - Start conversions for regular channels. * @adc: stm32 adc instance @@ -870,11 +887,37 @@ static const struct iio_chan_spec_ext_info stm32_adc_ext_info[] = { {}, }; +static int stm32_adc_of_get_resolution(struct iio_dev *indio_dev) +{ + struct device_node *node = indio_dev->dev.of_node; + struct stm32_adc *adc = iio_priv(indio_dev); + unsigned int i; + u32 res; + + if (of_property_read_u32(node, "assigned-resolution-bits", &res)) + res = stm32f4_adc_resolutions[0]; + + for (i = 0; i < ARRAY_SIZE(stm32f4_adc_resolutions); i++) + if (res == stm32f4_adc_resolutions[i]) + break; + if (i >= ARRAY_SIZE(stm32f4_adc_resolutions)) { + dev_err(&indio_dev->dev, "Bad resolution: %u bits\n", res); + return -EINVAL; + } + + dev_dbg(&indio_dev->dev, "Using %u bits resolution\n", res); + adc->res = i; + + return 0; +} + static void stm32_adc_chan_init_one(struct iio_dev *indio_dev, struct iio_chan_spec *chan, const struct stm32_adc_chan_spec *channel, int scan_index) { + struct stm32_adc *adc = iio_priv(indio_dev); + chan->type = channel->type; chan->channel = channel->channel; chan->datasheet_name = channel->name; @@ -883,7 +926,7 @@ static void stm32_adc_chan_init_one(struct iio_dev *indio_dev, chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); chan->scan_type.sign = 'u'; - chan->scan_type.realbits = 12; + chan->scan_type.realbits = stm32f4_adc_resolutions[adc->res]; chan->scan_type.storagebits = 16; chan->ext_info = stm32_adc_ext_info; } @@ -1022,6 +1065,11 @@ static int stm32_adc_probe(struct platform_device *pdev) return ret; } + ret = stm32_adc_of_get_resolution(indio_dev); + if (ret < 0) + goto err_clk_disable; + stm32_adc_set_res(adc); + ret = stm32_adc_chan_of_init(indio_dev); if (ret < 0) goto err_clk_disable; diff --git a/drivers/iio/adc/stx104.c b/drivers/iio/adc/stx104.c index be2de48844bc..2df84fa5e3fc 100644 --- a/drivers/iio/adc/stx104.c +++ b/drivers/iio/adc/stx104.c @@ -318,6 +318,7 @@ static int stx104_probe(struct device *dev, unsigned int id) } indio_dev->name = dev_name(dev); + indio_dev->dev.parent = dev; priv = iio_priv(indio_dev); priv->base = base[id]; diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c new file mode 100644 index 000000000000..b23527309088 --- /dev/null +++ b/drivers/iio/adc/sun4i-gpadc-iio.c @@ -0,0 +1,719 @@ +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC + * + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@free-electrons.com> + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 2 as published by the + * Free Software Foundation. + * + * The Allwinner SoCs all have an ADC that can also act as a touchscreen + * controller and a thermal sensor. + * The thermal sensor works only when the ADC acts as a touchscreen controller + * and is configured to throw an interrupt every fixed periods of time (let say + * every X seconds). + * One would be tempted to disable the IP on the hardware side rather than + * disabling interrupts to save some power but that resets the internal clock of + * the IP, resulting in having to wait X seconds every time we want to read the + * value of the thermal sensor. + * This is also the reason of using autosuspend in pm_runtime. If there was no + * autosuspend, the thermal sensor would need X seconds after every + * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the + * thermal sensor to be requested again in a certain time span before it gets + * shutdown for not being used. + */ + +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/thermal.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/driver.h> +#include <linux/iio/machine.h> +#include <linux/mfd/sun4i-gpadc.h> + +static unsigned int sun4i_gpadc_chan_select(unsigned int chan) +{ + return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan); +} + +static unsigned int sun6i_gpadc_chan_select(unsigned int chan) +{ + return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan); +} + +struct gpadc_data { + int temp_offset; + int temp_scale; + unsigned int tp_mode_en; + unsigned int tp_adc_select; + unsigned int (*adc_chan_select)(unsigned int chan); + unsigned int adc_chan_mask; +}; + +static const struct gpadc_data sun4i_gpadc_data = { + .temp_offset = -1932, + .temp_scale = 133, + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN, + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT, + .adc_chan_select = &sun4i_gpadc_chan_select, + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK, +}; + +static const struct gpadc_data sun5i_gpadc_data = { + .temp_offset = -1447, + .temp_scale = 100, + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN, + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT, + .adc_chan_select = &sun4i_gpadc_chan_select, + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK, +}; + +static const struct gpadc_data sun6i_gpadc_data = { + .temp_offset = -1623, + .temp_scale = 167, + .tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN, + .tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT, + .adc_chan_select = &sun6i_gpadc_chan_select, + .adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK, +}; + +static const struct gpadc_data sun8i_a33_gpadc_data = { + .temp_offset = -1662, + .temp_scale = 162, + .tp_mode_en = SUN8I_GPADC_CTRL1_CHOP_TEMP_EN, +}; + +struct sun4i_gpadc_iio { + struct iio_dev *indio_dev; + struct completion completion; + int temp_data; + u32 adc_data; + struct regmap *regmap; + unsigned int fifo_data_irq; + atomic_t ignore_fifo_data_irq; + unsigned int temp_data_irq; + atomic_t ignore_temp_data_irq; + const struct gpadc_data *data; + bool no_irq; + /* prevents concurrent reads of temperature and ADC */ + struct mutex mutex; +}; + +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _name, \ +} + +static struct iio_map sun4i_gpadc_hwmon_maps[] = { + { + .adc_channel_label = "temp_adc", + .consumer_dev_name = "iio_hwmon.0", + }, + { /* sentinel */ }, +}; + +static const struct iio_chan_spec sun4i_gpadc_channels[] = { + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"), + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"), + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"), + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .datasheet_name = "temp_adc", + }, +}; + +static const struct iio_chan_spec sun4i_gpadc_channels_no_temp[] = { + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"), + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"), + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"), + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"), +}; + +static const struct iio_chan_spec sun8i_a33_gpadc_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .datasheet_name = "temp_adc", + }, +}; + +static const struct regmap_config sun4i_gpadc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .fast_io = true, +}; + +static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel, + unsigned int irq) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + int ret; + u32 reg; + + pm_runtime_get_sync(indio_dev->dev.parent); + + reinit_completion(&info->completion); + + ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC, + SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) | + SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH); + if (ret) + return ret; + + ret = regmap_read(info->regmap, SUN4I_GPADC_CTRL1, ®); + if (ret) + return ret; + + if (irq == info->fifo_data_irq) { + ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1, + info->data->tp_mode_en | + info->data->tp_adc_select | + info->data->adc_chan_select(channel)); + /* + * When the IP changes channel, it needs a bit of time to get + * correct values. + */ + if ((reg & info->data->adc_chan_mask) != + info->data->adc_chan_select(channel)) + mdelay(10); + + } else { + /* + * The temperature sensor returns valid data only when the ADC + * operates in touchscreen mode. + */ + ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1, + info->data->tp_mode_en); + } + + if (ret) + return ret; + + /* + * When the IP changes mode between ADC or touchscreen, it + * needs a bit of time to get correct values. + */ + if ((reg & info->data->tp_adc_select) != info->data->tp_adc_select) + mdelay(100); + + return 0; +} + +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val, + unsigned int irq) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + int ret; + + mutex_lock(&info->mutex); + + ret = sun4i_prepare_for_irq(indio_dev, channel, irq); + if (ret) + goto err; + + enable_irq(irq); + + /* + * The temperature sensor throws an interruption periodically (currently + * set at periods of ~0.6s in sun4i_gpadc_runtime_resume). A 1s delay + * makes sure an interruption occurs in normal conditions. If it doesn't + * occur, then there is a timeout. + */ + if (!wait_for_completion_timeout(&info->completion, + msecs_to_jiffies(1000))) { + ret = -ETIMEDOUT; + goto err; + } + + if (irq == info->fifo_data_irq) + *val = info->adc_data; + else + *val = info->temp_data; + + ret = 0; + pm_runtime_mark_last_busy(indio_dev->dev.parent); + +err: + pm_runtime_put_autosuspend(indio_dev->dev.parent); + mutex_unlock(&info->mutex); + + return ret; +} + +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel, + int *val) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + + return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq); +} + +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + + if (info->no_irq) { + pm_runtime_get_sync(indio_dev->dev.parent); + + regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, val); + + pm_runtime_mark_last_busy(indio_dev->dev.parent); + pm_runtime_put_autosuspend(indio_dev->dev.parent); + + return 0; + } + + return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq); +} + +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + + *val = info->data->temp_offset; + + return 0; +} + +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + + *val = info->data->temp_scale; + + return 0; +} + +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + ret = sun4i_gpadc_temp_offset(indio_dev, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_VOLTAGE) + ret = sun4i_gpadc_adc_read(indio_dev, chan->channel, + val); + else + ret = sun4i_gpadc_temp_read(indio_dev, val); + + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLTAGE) { + /* 3000mV / 4096 * raw */ + *val = 0; + *val2 = 732421875; + return IIO_VAL_INT_PLUS_NANO; + } + + ret = sun4i_gpadc_temp_scale(indio_dev, val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static const struct iio_info sun4i_gpadc_iio_info = { + .read_raw = sun4i_gpadc_read_raw, + .driver_module = THIS_MODULE, +}; + +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id) +{ + struct sun4i_gpadc_iio *info = dev_id; + + if (atomic_read(&info->ignore_temp_data_irq)) + goto out; + + if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data)) + complete(&info->completion); + +out: + disable_irq_nosync(info->temp_data_irq); + return IRQ_HANDLED; +} + +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id) +{ + struct sun4i_gpadc_iio *info = dev_id; + + if (atomic_read(&info->ignore_fifo_data_irq)) + goto out; + + if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data)) + complete(&info->completion); + +out: + disable_irq_nosync(info->fifo_data_irq); + return IRQ_HANDLED; +} + +static int sun4i_gpadc_runtime_suspend(struct device *dev) +{ + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev)); + + /* Disable the ADC on IP */ + regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0); + /* Disable temperature sensor on IP */ + regmap_write(info->regmap, SUN4I_GPADC_TPR, 0); + + return 0; +} + +static int sun4i_gpadc_runtime_resume(struct device *dev) +{ + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev)); + + /* clkin = 6MHz */ + regmap_write(info->regmap, SUN4I_GPADC_CTRL0, + SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) | + SUN4I_GPADC_CTRL0_FS_DIV(7) | + SUN4I_GPADC_CTRL0_T_ACQ(63)); + regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en); + regmap_write(info->regmap, SUN4I_GPADC_CTRL3, + SUN4I_GPADC_CTRL3_FILTER_EN | + SUN4I_GPADC_CTRL3_FILTER_TYPE(1)); + /* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~0.6s */ + regmap_write(info->regmap, SUN4I_GPADC_TPR, + SUN4I_GPADC_TPR_TEMP_ENABLE | + SUN4I_GPADC_TPR_TEMP_PERIOD(800)); + + return 0; +} + +static int sun4i_gpadc_get_temp(void *data, int *temp) +{ + struct sun4i_gpadc_iio *info = data; + int val, scale, offset; + + if (sun4i_gpadc_temp_read(info->indio_dev, &val)) + return -ETIMEDOUT; + + sun4i_gpadc_temp_scale(info->indio_dev, &scale); + sun4i_gpadc_temp_offset(info->indio_dev, &offset); + + *temp = (val + offset) * scale; + + return 0; +} + +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = { + .get_temp = &sun4i_gpadc_get_temp, +}; + +static const struct dev_pm_ops sun4i_gpadc_pm_ops = { + .runtime_suspend = &sun4i_gpadc_runtime_suspend, + .runtime_resume = &sun4i_gpadc_runtime_resume, +}; + +static int sun4i_irq_init(struct platform_device *pdev, const char *name, + irq_handler_t handler, const char *devname, + unsigned int *irq, atomic_t *atomic) +{ + int ret; + struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent); + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev)); + + /* + * Once the interrupt is activated, the IP continuously performs + * conversions thus throws interrupts. The interrupt is activated right + * after being requested but we want to control when these interrupts + * occur thus we disable it right after being requested. However, an + * interrupt might occur between these two instructions and we have to + * make sure that does not happen, by using atomic flags. We set the + * flag before requesting the interrupt and unset it right after + * disabling the interrupt. When an interrupt occurs between these two + * instructions, reading the atomic flag will tell us to ignore the + * interrupt. + */ + atomic_set(atomic, 1); + + ret = platform_get_irq_byname(pdev, name); + if (ret < 0) { + dev_err(&pdev->dev, "no %s interrupt registered\n", name); + return ret; + } + + ret = regmap_irq_get_virq(mfd_dev->regmap_irqc, ret); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get virq for irq %s\n", name); + return ret; + } + + *irq = ret; + ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0, + devname, info); + if (ret < 0) { + dev_err(&pdev->dev, "could not request %s interrupt: %d\n", + name, ret); + return ret; + } + + disable_irq(*irq); + atomic_set(atomic, 0); + + return 0; +} + +static const struct of_device_id sun4i_gpadc_of_id[] = { + { + .compatible = "allwinner,sun8i-a33-ths", + .data = &sun8i_a33_gpadc_data, + }, + { /* sentinel */ } +}; + +static int sun4i_gpadc_probe_dt(struct platform_device *pdev, + struct iio_dev *indio_dev) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + const struct of_device_id *of_dev; + struct thermal_zone_device *tzd; + struct resource *mem; + void __iomem *base; + int ret; + + of_dev = of_match_device(sun4i_gpadc_of_id, &pdev->dev); + if (!of_dev) + return -ENODEV; + + info->no_irq = true; + info->data = (struct gpadc_data *)of_dev->data; + indio_dev->num_channels = ARRAY_SIZE(sun8i_a33_gpadc_channels); + indio_dev->channels = sun8i_a33_gpadc_channels; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + + info->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun4i_gpadc_regmap_config); + if (IS_ERR(info->regmap)) { + ret = PTR_ERR(info->regmap); + dev_err(&pdev->dev, "failed to init regmap: %d\n", ret); + return ret; + } + + if (!IS_ENABLED(CONFIG_THERMAL_OF)) + return 0; + + tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, info, + &sun4i_ts_tz_ops); + if (IS_ERR(tzd)) + dev_err(&pdev->dev, "could not register thermal sensor: %ld\n", + PTR_ERR(tzd)); + + return PTR_ERR_OR_ZERO(tzd); +} + +static int sun4i_gpadc_probe_mfd(struct platform_device *pdev, + struct iio_dev *indio_dev) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + struct sun4i_gpadc_dev *sun4i_gpadc_dev = + dev_get_drvdata(pdev->dev.parent); + int ret; + + info->no_irq = false; + info->regmap = sun4i_gpadc_dev->regmap; + + indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels); + indio_dev->channels = sun4i_gpadc_channels; + + info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data; + + /* + * Since the controller needs to be in touchscreen mode for its thermal + * sensor to operate properly, and that switching between the two modes + * needs a delay, always registering in the thermal framework will + * significantly slow down the conversion rate of the ADCs. + * + * Therefore, instead of depending on THERMAL_OF in Kconfig, we only + * register the sensor if that option is enabled, eventually leaving + * that choice to the user. + */ + + if (IS_ENABLED(CONFIG_THERMAL_OF)) { + /* + * This driver is a child of an MFD which has a node in the DT + * but not its children, because of DT backward compatibility + * for A10, A13 and A31 SoCs. Therefore, the resulting devices + * of this driver do not have an of_node variable. + * However, its parent (the MFD driver) has an of_node variable + * and since devm_thermal_zone_of_sensor_register uses its first + * argument to match the phandle defined in the node of the + * thermal driver with the of_node of the device passed as first + * argument and the third argument to call ops from + * thermal_zone_of_device_ops, the solution is to use the parent + * device as first argument to match the phandle with its + * of_node, and the device from this driver as third argument to + * return the temperature. + */ + struct thermal_zone_device *tzd; + tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, + info, + &sun4i_ts_tz_ops); + if (IS_ERR(tzd)) { + dev_err(&pdev->dev, + "could not register thermal sensor: %ld\n", + PTR_ERR(tzd)); + return PTR_ERR(tzd); + } + } else { + indio_dev->num_channels = + ARRAY_SIZE(sun4i_gpadc_channels_no_temp); + indio_dev->channels = sun4i_gpadc_channels_no_temp; + } + + if (IS_ENABLED(CONFIG_THERMAL_OF)) { + ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING", + sun4i_gpadc_temp_data_irq_handler, + "temp_data", &info->temp_data_irq, + &info->ignore_temp_data_irq); + if (ret < 0) + return ret; + } + + ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING", + sun4i_gpadc_fifo_data_irq_handler, "fifo_data", + &info->fifo_data_irq, &info->ignore_fifo_data_irq); + if (ret < 0) + return ret; + + if (IS_ENABLED(CONFIG_THERMAL_OF)) { + ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to register iio map array\n"); + return ret; + } + } + + return 0; +} + +static int sun4i_gpadc_probe(struct platform_device *pdev) +{ + struct sun4i_gpadc_iio *info; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + platform_set_drvdata(pdev, indio_dev); + + mutex_init(&info->mutex); + info->indio_dev = indio_dev; + init_completion(&info->completion); + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &sun4i_gpadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (pdev->dev.of_node) + ret = sun4i_gpadc_probe_dt(pdev, indio_dev); + else + ret = sun4i_gpadc_probe_mfd(pdev, indio_dev); + + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(&pdev->dev, + SUN4I_GPADC_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret < 0) { + dev_err(&pdev->dev, "could not register the device\n"); + goto err_map; + } + + return 0; + +err_map: + if (!info->no_irq && IS_ENABLED(CONFIG_THERMAL_OF)) + iio_map_array_unregister(indio_dev); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int sun4i_gpadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + if (!info->no_irq && IS_ENABLED(CONFIG_THERMAL_OF)) + iio_map_array_unregister(indio_dev); + + return 0; +} + +static const struct platform_device_id sun4i_gpadc_id[] = { + { "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data }, + { "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data }, + { "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data }, + { /* sentinel */ }, +}; + +static struct platform_driver sun4i_gpadc_driver = { + .driver = { + .name = "sun4i-gpadc-iio", + .of_match_table = sun4i_gpadc_of_id, + .pm = &sun4i_gpadc_pm_ops, + }, + .id_table = sun4i_gpadc_id, + .probe = sun4i_gpadc_probe, + .remove = sun4i_gpadc_remove, +}; + +module_platform_driver(sun4i_gpadc_driver); + +MODULE_DESCRIPTION("ADC driver for sunxi platforms"); +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c index 422b314f5a3f..f76d979fb7e8 100644 --- a/drivers/iio/adc/ti-ads1015.c +++ b/drivers/iio/adc/ti-ads1015.c @@ -15,6 +15,7 @@ */ #include <linux/module.h> +#include <linux/of_device.h> #include <linux/init.h> #include <linux/i2c.h> #include <linux/regmap.h> @@ -55,7 +56,7 @@ #define ADS1015_DEFAULT_DATA_RATE 4 #define ADS1015_DEFAULT_CHAN 0 -enum { +enum chip_ids { ADS1015, ADS1115, }; @@ -578,6 +579,7 @@ static int ads1015_probe(struct i2c_client *client, struct iio_dev *indio_dev; struct ads1015_data *data; int ret; + enum chip_ids chip; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) @@ -593,7 +595,11 @@ static int ads1015_probe(struct i2c_client *client, indio_dev->name = ADS1015_DRV_NAME; indio_dev->modes = INDIO_DIRECT_MODE; - switch (id->driver_data) { + if (client->dev.of_node) + chip = (enum chip_ids)of_device_get_match_data(&client->dev); + else + chip = id->driver_data; + switch (chip) { case ADS1015: indio_dev->channels = ads1015_channels; indio_dev->num_channels = ARRAY_SIZE(ads1015_channels); @@ -698,9 +704,23 @@ static const struct i2c_device_id ads1015_id[] = { }; MODULE_DEVICE_TABLE(i2c, ads1015_id); +static const struct of_device_id ads1015_of_match[] = { + { + .compatible = "ti,ads1015", + .data = (void *)ADS1015 + }, + { + .compatible = "ti,ads1115", + .data = (void *)ADS1115 + }, + {} +}; +MODULE_DEVICE_TABLE(of, ads1015_of_match); + static struct i2c_driver ads1015_driver = { .driver = { .name = ADS1015_DRV_NAME, + .of_match_table = ads1015_of_match, .pm = &ads1015_pm_ops, }, .probe = ads1015_probe, diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c index 228a003adeed..01fc76f7d660 100644 --- a/drivers/iio/adc/vf610_adc.c +++ b/drivers/iio/adc/vf610_adc.c @@ -584,7 +584,7 @@ static int vf610_adc_read_data(struct vf610_adc *info) static irqreturn_t vf610_adc_isr(int irq, void *dev_id) { - struct iio_dev *indio_dev = (struct iio_dev *)dev_id; + struct iio_dev *indio_dev = dev_id; struct vf610_adc *info = iio_priv(indio_dev); int coco; diff --git a/drivers/iio/chemical/ams-iaq-core.c b/drivers/iio/chemical/ams-iaq-core.c index 41a8e6f2e31d..c948ad2ee9ad 100644 --- a/drivers/iio/chemical/ams-iaq-core.c +++ b/drivers/iio/chemical/ams-iaq-core.c @@ -163,7 +163,7 @@ static int ams_iaqcore_probe(struct i2c_client *client, mutex_init(&data->lock); indio_dev->dev.parent = &client->dev; - indio_dev->info = &ams_iaqcore_info, + indio_dev->info = &ams_iaqcore_info; indio_dev->name = dev_name(&client->dev); indio_dev->modes = INDIO_DIRECT_MODE; diff --git a/drivers/iio/chemical/vz89x.c b/drivers/iio/chemical/vz89x.c index 8e0e4415c161..f75eea6822f2 100644 --- a/drivers/iio/chemical/vz89x.c +++ b/drivers/iio/chemical/vz89x.c @@ -393,7 +393,7 @@ static int vz89x_probe(struct i2c_client *client, mutex_init(&data->lock); indio_dev->dev.parent = &client->dev; - indio_dev->info = &vz89x_info, + indio_dev->info = &vz89x_info; indio_dev->name = dev_name(&client->dev); indio_dev->modes = INDIO_DIRECT_MODE; diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c index c17596f7ed2c..38e8783e4b05 100644 --- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c @@ -267,31 +267,12 @@ static int cros_ec_sensors_probe(struct platform_device *pdev) else state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; - ret = iio_triggered_buffer_setup(indio_dev, NULL, - cros_ec_sensors_capture, NULL); + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + cros_ec_sensors_capture, NULL); if (ret) return ret; - ret = iio_device_register(indio_dev); - if (ret) - goto error_uninit_buffer; - - return 0; - -error_uninit_buffer: - iio_triggered_buffer_cleanup(indio_dev); - - return ret; -} - -static int cros_ec_sensors_remove(struct platform_device *pdev) -{ - struct iio_dev *indio_dev = platform_get_drvdata(pdev); - - iio_device_unregister(indio_dev); - iio_triggered_buffer_cleanup(indio_dev); - - return 0; + return devm_iio_device_register(dev, indio_dev); } static const struct platform_device_id cros_ec_sensors_ids[] = { @@ -313,7 +294,6 @@ static struct platform_driver cros_ec_sensors_platform_driver = { .name = "cros-ec-sensors", }, .probe = cros_ec_sensors_probe, - .remove = cros_ec_sensors_remove, .id_table = cros_ec_sensors_ids, }; module_platform_driver(cros_ec_sensors_platform_driver); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c index ca742ac8f128..1c0874cdf665 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -38,6 +38,12 @@ static struct { {HID_USAGE_SENSOR_ACCEL_3D, HID_USAGE_SENSOR_UNITS_G, 9, 806650000}, + {HID_USAGE_SENSOR_GRAVITY_VECTOR, 0, 9, 806650000}, + {HID_USAGE_SENSOR_GRAVITY_VECTOR, + HID_USAGE_SENSOR_UNITS_METERS_PER_SEC_SQRD, 1, 0}, + {HID_USAGE_SENSOR_GRAVITY_VECTOR, + HID_USAGE_SENSOR_UNITS_G, 9, 806650000}, + {HID_USAGE_SENSOR_GYRO_3D, 0, 0, 17453293}, {HID_USAGE_SENSOR_GYRO_3D, HID_USAGE_SENSOR_UNITS_RADIANS_PER_SECOND, 1, 0}, @@ -62,6 +68,11 @@ static struct { {HID_USAGE_SENSOR_TIME_TIMESTAMP, 0, 1000000000, 0}, {HID_USAGE_SENSOR_TIME_TIMESTAMP, HID_USAGE_SENSOR_UNITS_MILLISECOND, 1000000, 0}, + + {HID_USAGE_SENSOR_TEMPERATURE, 0, 1000, 0}, + {HID_USAGE_SENSOR_TEMPERATURE, HID_USAGE_SENSOR_UNITS_DEGREES, 1000, 0}, + + {HID_USAGE_SENSOR_HUMIDITY, 0, 1000, 0}, }; static int pow_10(unsigned power) diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c index 60829340a82e..0b5dea050239 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -152,6 +152,10 @@ static int hid_sensor_data_rdy_trigger_set_state(struct iio_trigger *trig, void hid_sensor_remove_trigger(struct hid_sensor_common *attrb) { + pm_runtime_disable(&attrb->pdev->dev); + pm_runtime_set_suspended(&attrb->pdev->dev); + pm_runtime_put_noidle(&attrb->pdev->dev); + cancel_work_sync(&attrb->work); iio_trigger_unregister(attrb->trigger); iio_trigger_free(attrb->trigger); diff --git a/drivers/iio/common/ms_sensors/ms_sensors_i2c.c b/drivers/iio/common/ms_sensors/ms_sensors_i2c.c index ecf7721ecaf4..125b5ff61d19 100644 --- a/drivers/iio/common/ms_sensors/ms_sensors_i2c.c +++ b/drivers/iio/common/ms_sensors/ms_sensors_i2c.c @@ -74,7 +74,7 @@ EXPORT_SYMBOL(ms_sensors_reset); int ms_sensors_read_prom_word(void *cli, int cmd, u16 *word) { int ret; - struct i2c_client *client = (struct i2c_client *)cli; + struct i2c_client *client = cli; ret = i2c_smbus_read_word_swapped(client, cmd); if (ret < 0) { @@ -107,7 +107,7 @@ int ms_sensors_convert_and_read(void *cli, u8 conv, u8 rd, { int ret; __be32 buf = 0; - struct i2c_client *client = (struct i2c_client *)cli; + struct i2c_client *client = cli; /* Trigger conversion */ ret = i2c_smbus_write_byte(client, conv); diff --git a/drivers/iio/counter/104-quad-8.c b/drivers/iio/counter/104-quad-8.c index f9b8fc9ae13f..ba3d9030cd51 100644 --- a/drivers/iio/counter/104-quad-8.c +++ b/drivers/iio/counter/104-quad-8.c @@ -551,6 +551,7 @@ static int quad8_probe(struct device *dev, unsigned int id) indio_dev->num_channels = ARRAY_SIZE(quad8_channels); indio_dev->channels = quad8_channels; indio_dev->name = dev_name(dev); + indio_dev->dev.parent = dev; priv = iio_priv(indio_dev); priv->base = base[id]; diff --git a/drivers/iio/counter/Kconfig b/drivers/iio/counter/Kconfig index 44627f6e4861..b37e5fc03149 100644 --- a/drivers/iio/counter/Kconfig +++ b/drivers/iio/counter/Kconfig @@ -7,7 +7,7 @@ menu "Counters" config 104_QUAD_8 tristate "ACCES 104-QUAD-8 driver" - depends on X86 && ISA_BUS_API + depends on PC104 && X86 && ISA_BUS_API help Say yes here to build support for the ACCES 104-QUAD-8 quadrature encoder counter/interface device family (104-QUAD-8, 104-QUAD-4). diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index d3084028905b..df5abc46cd3f 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -118,6 +118,16 @@ config AD5624R_SPI Say yes here to build support for Analog Devices AD5624R, AD5644R and AD5664R converters (DAC). This driver uses the common SPI interface. +config LTC2632 + tristate "Linear Technology LTC2632-12/10/8 DAC spi driver" + depends on SPI + help + Say yes here to build support for Linear Technology + LTC2632-12, LTC2632-10, LTC2632-8 converters (DAC). + + To compile this driver as a module, choose M here: the + module will be called ltc2632. + config AD5686 tristate "Analog Devices AD5686R/AD5685R/AD5684R DAC SPI driver" depends on SPI @@ -274,6 +284,21 @@ config MCP4922 To compile this driver as a module, choose M here: the module will be called mcp4922. +config STM32_DAC + tristate "STMicroelectronics STM32 DAC" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + depends on REGULATOR + select STM32_DAC_CORE + help + Say yes here to build support for STMicroelectronics STM32 Digital + to Analog Converter (DAC). + + This driver can also be built as a module. If so, the module + will be called stm32-dac. + +config STM32_DAC_CORE + tristate + config VF610_DAC tristate "Vybrid vf610 DAC driver" depends on OF diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index f01bf4a99867..603587cc2f07 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -24,9 +24,12 @@ obj-$(CONFIG_AD8801) += ad8801.o obj-$(CONFIG_CIO_DAC) += cio-dac.o obj-$(CONFIG_DPOT_DAC) += dpot-dac.o obj-$(CONFIG_LPC18XX_DAC) += lpc18xx_dac.o +obj-$(CONFIG_LTC2632) += ltc2632.o obj-$(CONFIG_M62332) += m62332.o obj-$(CONFIG_MAX517) += max517.o obj-$(CONFIG_MAX5821) += max5821.o obj-$(CONFIG_MCP4725) += mcp4725.o obj-$(CONFIG_MCP4922) += mcp4922.o +obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o +obj-$(CONFIG_STM32_DAC) += stm32-dac.o obj-$(CONFIG_VF610_DAC) += vf610_dac.o diff --git a/drivers/iio/dac/ad5504.c b/drivers/iio/dac/ad5504.c index 788b3d6fd1cc..712d86b4be09 100644 --- a/drivers/iio/dac/ad5504.c +++ b/drivers/iio/dac/ad5504.c @@ -212,7 +212,7 @@ static struct attribute *ad5504_ev_attributes[] = { NULL, }; -static struct attribute_group ad5504_ev_attribute_group = { +static const struct attribute_group ad5504_ev_attribute_group = { .attrs = ad5504_ev_attributes, }; @@ -223,7 +223,7 @@ static irqreturn_t ad5504_event_handler(int irq, void *private) 0, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), - iio_get_time_ns((struct iio_dev *)private)); + iio_get_time_ns(private)); return IRQ_HANDLED; } diff --git a/drivers/iio/dac/cio-dac.c b/drivers/iio/dac/cio-dac.c index 5a743e2a779d..a0464227a3a0 100644 --- a/drivers/iio/dac/cio-dac.c +++ b/drivers/iio/dac/cio-dac.c @@ -119,6 +119,7 @@ static int cio_dac_probe(struct device *dev, unsigned int id) indio_dev->channels = cio_dac_channels; indio_dev->num_channels = CIO_DAC_NUM_CHAN; indio_dev->name = dev_name(dev); + indio_dev->dev.parent = dev; priv = iio_priv(indio_dev); priv->base = base[id]; diff --git a/drivers/iio/dac/ltc2632.c b/drivers/iio/dac/ltc2632.c new file mode 100644 index 000000000000..ac5e05f6eb8b --- /dev/null +++ b/drivers/iio/dac/ltc2632.c @@ -0,0 +1,314 @@ +/* + * LTC2632 Digital to analog convertors spi driver + * + * Copyright 2017 Maxime Roussin-Bélanger + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/spi/spi.h> +#include <linux/module.h> +#include <linux/iio/iio.h> + +#define LTC2632_DAC_CHANNELS 2 + +#define LTC2632_ADDR_DAC0 0x0 +#define LTC2632_ADDR_DAC1 0x1 + +#define LTC2632_CMD_WRITE_INPUT_N 0x0 +#define LTC2632_CMD_UPDATE_DAC_N 0x1 +#define LTC2632_CMD_WRITE_INPUT_N_UPDATE_ALL 0x2 +#define LTC2632_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define LTC2632_CMD_POWERDOWN_DAC_N 0x4 +#define LTC2632_CMD_POWERDOWN_CHIP 0x5 +#define LTC2632_CMD_INTERNAL_REFER 0x6 +#define LTC2632_CMD_EXTERNAL_REFER 0x7 + +/** + * struct ltc2632_chip_info - chip specific information + * @channels: channel spec for the DAC + * @vref_mv: reference voltage + */ +struct ltc2632_chip_info { + const struct iio_chan_spec *channels; + const int vref_mv; +}; + +/** + * struct ltc2632_state - driver instance specific data + * @spi_dev: pointer to the spi_device struct + * @powerdown_cache_mask used to show current channel powerdown state + */ +struct ltc2632_state { + struct spi_device *spi_dev; + unsigned int powerdown_cache_mask; +}; + +enum ltc2632_supported_device_ids { + ID_LTC2632L12, + ID_LTC2632L10, + ID_LTC2632L8, + ID_LTC2632H12, + ID_LTC2632H10, + ID_LTC2632H8, +}; + +static int ltc2632_spi_write(struct spi_device *spi, + u8 cmd, u8 addr, u16 val, u8 shift) +{ + u32 data; + u8 msg[3]; + + /* + * The input shift register is 24 bits wide. + * The next four are the command bits, C3 to C0, + * followed by the 4-bit DAC address, A3 to A0, and then the + * 12-, 10-, 8-bit data-word. The data-word comprises the 12-, + * 10-, 8-bit input code followed by 4, 6, or 8 don't care bits. + */ + data = (cmd << 20) | (addr << 16) | (val << shift); + msg[0] = data >> 16; + msg[1] = data >> 8; + msg[2] = data; + + return spi_write(spi, msg, sizeof(msg)); +} + +static int ltc2632_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + 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: + *val = chip_info->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int ltc2632_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ltc2632_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + return ltc2632_spi_write(st->spi_dev, + LTC2632_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, val, + chan->scan_type.shift); + default: + return -EINVAL; + } +} + +static ssize_t ltc2632_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ltc2632_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", + !!(st->powerdown_cache_mask & (1 << chan->channel))); +} + +static ssize_t ltc2632_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, + size_t len) +{ + bool pwr_down; + int ret; + struct ltc2632_state *st = iio_priv(indio_dev); + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + if (pwr_down) + st->powerdown_cache_mask |= (1 << chan->channel); + else + st->powerdown_cache_mask &= ~(1 << chan->channel); + + ret = ltc2632_spi_write(st->spi_dev, + LTC2632_CMD_POWERDOWN_DAC_N, + chan->channel, 0, 0); + + return ret ? ret : len; +} + +static const struct iio_info ltc2632_info = { + .write_raw = ltc2632_write_raw, + .read_raw = ltc2632_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec_ext_info ltc2632_ext_info[] = { + { + .name = "powerdown", + .read = ltc2632_read_dac_powerdown, + .write = ltc2632_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + { }, +}; + +#define LTC2632_CHANNEL(_chan, _bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = (_chan), \ + .scan_type = { \ + .realbits = (_bits), \ + .shift = 16 - (_bits), \ + }, \ + .ext_info = ltc2632_ext_info, \ +} + +#define DECLARE_LTC2632_CHANNELS(_name, _bits) \ + const struct iio_chan_spec _name ## _channels[] = { \ + LTC2632_CHANNEL(0, _bits), \ + LTC2632_CHANNEL(1, _bits), \ + } + +static DECLARE_LTC2632_CHANNELS(ltc2632l12, 12); +static DECLARE_LTC2632_CHANNELS(ltc2632l10, 10); +static DECLARE_LTC2632_CHANNELS(ltc2632l8, 8); + +static DECLARE_LTC2632_CHANNELS(ltc2632h12, 12); +static DECLARE_LTC2632_CHANNELS(ltc2632h10, 10); +static DECLARE_LTC2632_CHANNELS(ltc2632h8, 8); + +static const struct ltc2632_chip_info ltc2632_chip_info_tbl[] = { + [ID_LTC2632L12] = { + .channels = ltc2632l12_channels, + .vref_mv = 2500, + }, + [ID_LTC2632L10] = { + .channels = ltc2632l10_channels, + .vref_mv = 2500, + }, + [ID_LTC2632L8] = { + .channels = ltc2632l8_channels, + .vref_mv = 2500, + }, + [ID_LTC2632H12] = { + .channels = ltc2632h12_channels, + .vref_mv = 4096, + }, + [ID_LTC2632H10] = { + .channels = ltc2632h10_channels, + .vref_mv = 4096, + }, + [ID_LTC2632H8] = { + .channels = ltc2632h8_channels, + .vref_mv = 4096, + }, +}; + +static int ltc2632_probe(struct spi_device *spi) +{ + struct ltc2632_state *st; + struct iio_dev *indio_dev; + struct ltc2632_chip_info *chip_info; + 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_dev = spi; + + chip_info = (struct ltc2632_chip_info *) + spi_get_device_id(spi)->driver_data; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = dev_of_node(&spi->dev) ? dev_of_node(&spi->dev)->name + : spi_get_device_id(spi)->name; + indio_dev->info = <c2632_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = LTC2632_DAC_CHANNELS; + + ret = ltc2632_spi_write(spi, LTC2632_CMD_INTERNAL_REFER, 0, 0, 0); + if (ret) { + dev_err(&spi->dev, + "Set internal reference command failed, %d\n", ret); + return ret; + } + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ltc2632_id[] = { + { "ltc2632-l12", (kernel_ulong_t)<c2632_chip_info_tbl[ID_LTC2632L12] }, + { "ltc2632-l10", (kernel_ulong_t)<c2632_chip_info_tbl[ID_LTC2632L10] }, + { "ltc2632-l8", (kernel_ulong_t)<c2632_chip_info_tbl[ID_LTC2632L8] }, + { "ltc2632-h12", (kernel_ulong_t)<c2632_chip_info_tbl[ID_LTC2632H12] }, + { "ltc2632-h10", (kernel_ulong_t)<c2632_chip_info_tbl[ID_LTC2632H10] }, + { "ltc2632-h8", (kernel_ulong_t)<c2632_chip_info_tbl[ID_LTC2632H8] }, + {} +}; +MODULE_DEVICE_TABLE(spi, ltc2632_id); + +static struct spi_driver ltc2632_driver = { + .driver = { + .name = "ltc2632", + }, + .probe = ltc2632_probe, + .id_table = ltc2632_id, +}; +module_spi_driver(ltc2632_driver); + +static const struct of_device_id ltc2632_of_match[] = { + { + .compatible = "lltc,ltc2632-l12", + .data = <c2632_chip_info_tbl[ID_LTC2632L12] + }, { + .compatible = "lltc,ltc2632-l10", + .data = <c2632_chip_info_tbl[ID_LTC2632L10] + }, { + .compatible = "lltc,ltc2632-l8", + .data = <c2632_chip_info_tbl[ID_LTC2632L8] + }, { + .compatible = "lltc,ltc2632-h12", + .data = <c2632_chip_info_tbl[ID_LTC2632H12] + }, { + .compatible = "lltc,ltc2632-h10", + .data = <c2632_chip_info_tbl[ID_LTC2632H10] + }, { + .compatible = "lltc,ltc2632-h8", + .data = <c2632_chip_info_tbl[ID_LTC2632H8] + }, + {} +}; +MODULE_DEVICE_TABLE(of, ltc2632_of_match); + +MODULE_AUTHOR("Maxime Roussin-Belanger <maxime.roussinbelanger@gmail.com>"); +MODULE_DESCRIPTION("LTC2632 DAC SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/max5821.c b/drivers/iio/dac/max5821.c index 86e9e112f554..193fac3059a3 100644 --- a/drivers/iio/dac/max5821.c +++ b/drivers/iio/dac/max5821.c @@ -392,6 +392,7 @@ MODULE_DEVICE_TABLE(of, max5821_of_match); static struct i2c_driver max5821_driver = { .driver = { .name = "max5821", + .of_match_table = max5821_of_match, .pm = MAX5821_PM_OPS, }, .probe = max5821_probe, diff --git a/drivers/iio/dac/mcp4725.c b/drivers/iio/dac/mcp4725.c index db109f0cdd8c..6ab1f23e5a79 100644 --- a/drivers/iio/dac/mcp4725.c +++ b/drivers/iio/dac/mcp4725.c @@ -19,6 +19,7 @@ #include <linux/err.h> #include <linux/delay.h> #include <linux/regulator/consumer.h> +#include <linux/of_device.h> #include <linux/of.h> #include <linux/iio/iio.h> @@ -199,7 +200,7 @@ static ssize_t mcp4725_write_powerdown(struct iio_dev *indio_dev, return len; } -enum { +enum chip_id { MCP4725, MCP4726, }; @@ -406,7 +407,10 @@ static int mcp4725_probe(struct i2c_client *client, data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); data->client = client; - data->id = id->driver_data; + if (client->dev.of_node) + data->id = (enum chip_id)of_device_get_match_data(&client->dev); + else + data->id = id->driver_data; pdata = dev_get_platdata(&client->dev); if (!pdata) { @@ -525,9 +529,25 @@ static const struct i2c_device_id mcp4725_id[] = { }; MODULE_DEVICE_TABLE(i2c, mcp4725_id); +#ifdef CONFIG_OF +static const struct of_device_id mcp4725_of_match[] = { + { + .compatible = "microchip,mcp4725", + .data = (void *)MCP4725 + }, + { + .compatible = "microchip,mcp4726", + .data = (void *)MCP4726 + }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp4725_of_match); +#endif + static struct i2c_driver mcp4725_driver = { .driver = { .name = MCP4725_DRV_NAME, + .of_match_table = of_match_ptr(mcp4725_of_match), .pm = MCP4725_PM_OPS, }, .probe = mcp4725_probe, diff --git a/drivers/iio/dac/stm32-dac-core.c b/drivers/iio/dac/stm32-dac-core.c new file mode 100644 index 000000000000..75e48788c7ea --- /dev/null +++ b/drivers/iio/dac/stm32-dac-core.c @@ -0,0 +1,180 @@ +/* + * This file is part of STM32 DAC driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier <fabrice.gasnier@st.com>. + * + * License type: GPLv2 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> + +#include "stm32-dac-core.h" + +/** + * struct stm32_dac_priv - stm32 DAC core private data + * @pclk: peripheral clock common for all DACs + * @rst: peripheral reset control + * @vref: regulator reference + * @common: Common data for all DAC instances + */ +struct stm32_dac_priv { + struct clk *pclk; + struct reset_control *rst; + struct regulator *vref; + struct stm32_dac_common common; +}; + +static struct stm32_dac_priv *to_stm32_dac_priv(struct stm32_dac_common *com) +{ + return container_of(com, struct stm32_dac_priv, common); +} + +static const struct regmap_config stm32_dac_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + .max_register = 0x3fc, +}; + +static int stm32_dac_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stm32_dac_priv *priv; + struct regmap *regmap; + struct resource *res; + void __iomem *mmio; + int ret; + + if (!dev->of_node) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(mmio)) + return PTR_ERR(mmio); + + regmap = devm_regmap_init_mmio(dev, mmio, &stm32_dac_regmap_cfg); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + priv->common.regmap = regmap; + + priv->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(priv->vref)) { + ret = PTR_ERR(priv->vref); + dev_err(dev, "vref get failed, %d\n", ret); + return ret; + } + + ret = regulator_enable(priv->vref); + if (ret < 0) { + dev_err(dev, "vref enable failed\n"); + return ret; + } + + ret = regulator_get_voltage(priv->vref); + if (ret < 0) { + dev_err(dev, "vref get voltage failed, %d\n", ret); + goto err_vref; + } + priv->common.vref_mv = ret / 1000; + dev_dbg(dev, "vref+=%dmV\n", priv->common.vref_mv); + + priv->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(priv->pclk)) { + ret = PTR_ERR(priv->pclk); + dev_err(dev, "pclk get failed\n"); + goto err_vref; + } + + ret = clk_prepare_enable(priv->pclk); + if (ret < 0) { + dev_err(dev, "pclk enable failed\n"); + goto err_vref; + } + + priv->rst = devm_reset_control_get(dev, NULL); + if (!IS_ERR(priv->rst)) { + reset_control_assert(priv->rst); + udelay(2); + reset_control_deassert(priv->rst); + } + + /* When clock speed is higher than 80MHz, set HFSEL */ + priv->common.hfsel = (clk_get_rate(priv->pclk) > 80000000UL); + ret = regmap_update_bits(regmap, STM32_DAC_CR, STM32H7_DAC_CR_HFSEL, + priv->common.hfsel ? STM32H7_DAC_CR_HFSEL : 0); + if (ret) + goto err_pclk; + + platform_set_drvdata(pdev, &priv->common); + + ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, dev); + if (ret < 0) { + dev_err(dev, "failed to populate DT children\n"); + goto err_pclk; + } + + return 0; + +err_pclk: + clk_disable_unprepare(priv->pclk); +err_vref: + regulator_disable(priv->vref); + + return ret; +} + +static int stm32_dac_remove(struct platform_device *pdev) +{ + struct stm32_dac_common *common = platform_get_drvdata(pdev); + struct stm32_dac_priv *priv = to_stm32_dac_priv(common); + + of_platform_depopulate(&pdev->dev); + clk_disable_unprepare(priv->pclk); + regulator_disable(priv->vref); + + return 0; +} + +static const struct of_device_id stm32_dac_of_match[] = { + { .compatible = "st,stm32h7-dac-core", }, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_dac_of_match); + +static struct platform_driver stm32_dac_driver = { + .probe = stm32_dac_probe, + .remove = stm32_dac_remove, + .driver = { + .name = "stm32-dac-core", + .of_match_table = stm32_dac_of_match, + }, +}; +module_platform_driver(stm32_dac_driver); + +MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics STM32 DAC core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:stm32-dac-core"); diff --git a/drivers/iio/dac/stm32-dac-core.h b/drivers/iio/dac/stm32-dac-core.h new file mode 100644 index 000000000000..daf09931857c --- /dev/null +++ b/drivers/iio/dac/stm32-dac-core.h @@ -0,0 +1,51 @@ +/* + * This file is part of STM32 DAC driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier <fabrice.gasnier@st.com>. + * + * License type: GPLv2 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __STM32_DAC_CORE_H +#define __STM32_DAC_CORE_H + +#include <linux/regmap.h> + +/* STM32 DAC registers */ +#define STM32_DAC_CR 0x00 +#define STM32_DAC_DHR12R1 0x08 +#define STM32_DAC_DHR12R2 0x14 +#define STM32_DAC_DOR1 0x2C +#define STM32_DAC_DOR2 0x30 + +/* STM32_DAC_CR bit fields */ +#define STM32_DAC_CR_EN1 BIT(0) +#define STM32H7_DAC_CR_HFSEL BIT(15) +#define STM32_DAC_CR_EN2 BIT(16) + +/** + * struct stm32_dac_common - stm32 DAC driver common data (for all instances) + * @regmap: DAC registers shared via regmap + * @vref_mv: reference voltage (mv) + * @hfsel: high speed bus clock selected + */ +struct stm32_dac_common { + struct regmap *regmap; + int vref_mv; + bool hfsel; +}; + +#endif diff --git a/drivers/iio/dac/stm32-dac.c b/drivers/iio/dac/stm32-dac.c new file mode 100644 index 000000000000..50f8ec091058 --- /dev/null +++ b/drivers/iio/dac/stm32-dac.c @@ -0,0 +1,334 @@ +/* + * This file is part of STM32 DAC driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Authors: Amelie Delaunay <amelie.delaunay@st.com> + * Fabrice Gasnier <fabrice.gasnier@st.com> + * + * License type: GPLv2 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "stm32-dac-core.h" + +#define STM32_DAC_CHANNEL_1 1 +#define STM32_DAC_CHANNEL_2 2 +#define STM32_DAC_IS_CHAN_1(ch) ((ch) & STM32_DAC_CHANNEL_1) + +/** + * struct stm32_dac - private data of DAC driver + * @common: reference to DAC common data + */ +struct stm32_dac { + struct stm32_dac_common *common; +}; + +static int stm32_dac_is_enabled(struct iio_dev *indio_dev, int channel) +{ + struct stm32_dac *dac = iio_priv(indio_dev); + u32 en, val; + int ret; + + ret = regmap_read(dac->common->regmap, STM32_DAC_CR, &val); + if (ret < 0) + return ret; + if (STM32_DAC_IS_CHAN_1(channel)) + en = FIELD_GET(STM32_DAC_CR_EN1, val); + else + en = FIELD_GET(STM32_DAC_CR_EN2, val); + + return !!en; +} + +static int stm32_dac_set_enable_state(struct iio_dev *indio_dev, int ch, + bool enable) +{ + struct stm32_dac *dac = iio_priv(indio_dev); + u32 msk = STM32_DAC_IS_CHAN_1(ch) ? STM32_DAC_CR_EN1 : STM32_DAC_CR_EN2; + u32 en = enable ? msk : 0; + int ret; + + ret = regmap_update_bits(dac->common->regmap, STM32_DAC_CR, msk, en); + if (ret < 0) { + dev_err(&indio_dev->dev, "%s failed\n", en ? + "Enable" : "Disable"); + return ret; + } + + /* + * When HFSEL is set, it is not allowed to write the DHRx register + * during 8 clock cycles after the ENx bit is set. It is not allowed + * to make software/hardware trigger during this period either. + */ + if (en && dac->common->hfsel) + udelay(1); + + return 0; +} + +static int stm32_dac_get_value(struct stm32_dac *dac, int channel, int *val) +{ + int ret; + + if (STM32_DAC_IS_CHAN_1(channel)) + ret = regmap_read(dac->common->regmap, STM32_DAC_DOR1, val); + else + ret = regmap_read(dac->common->regmap, STM32_DAC_DOR2, val); + + return ret ? ret : IIO_VAL_INT; +} + +static int stm32_dac_set_value(struct stm32_dac *dac, int channel, int val) +{ + int ret; + + if (STM32_DAC_IS_CHAN_1(channel)) + ret = regmap_write(dac->common->regmap, STM32_DAC_DHR12R1, val); + else + ret = regmap_write(dac->common->regmap, STM32_DAC_DHR12R2, val); + + return ret; +} + +static int stm32_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct stm32_dac *dac = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return stm32_dac_get_value(dac, chan->channel, val); + case IIO_CHAN_INFO_SCALE: + *val = dac->common->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int stm32_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct stm32_dac *dac = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return stm32_dac_set_value(dac, chan->channel, val); + default: + return -EINVAL; + } +} + +static int stm32_dac_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct stm32_dac *dac = iio_priv(indio_dev); + + if (!readval) + return regmap_write(dac->common->regmap, reg, writeval); + else + return regmap_read(dac->common->regmap, reg, readval); +} + +static const struct iio_info stm32_dac_iio_info = { + .read_raw = stm32_dac_read_raw, + .write_raw = stm32_dac_write_raw, + .debugfs_reg_access = stm32_dac_debugfs_reg_access, + .driver_module = THIS_MODULE, +}; + +static const char * const stm32_dac_powerdown_modes[] = { + "three_state", +}; + +static int stm32_dac_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + return 0; +} + +static int stm32_dac_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int type) +{ + return 0; +} + +static ssize_t stm32_dac_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + int ret = stm32_dac_is_enabled(indio_dev, chan->channel); + + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret ? 0 : 1); +} + +static ssize_t stm32_dac_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + bool powerdown; + int ret; + + ret = strtobool(buf, &powerdown); + if (ret) + return ret; + + ret = stm32_dac_set_enable_state(indio_dev, chan->channel, !powerdown); + if (ret) + return ret; + + return len; +} + +static const struct iio_enum stm32_dac_powerdown_mode_en = { + .items = stm32_dac_powerdown_modes, + .num_items = ARRAY_SIZE(stm32_dac_powerdown_modes), + .get = stm32_dac_get_powerdown_mode, + .set = stm32_dac_set_powerdown_mode, +}; + +static const struct iio_chan_spec_ext_info stm32_dac_ext_info[] = { + { + .name = "powerdown", + .read = stm32_dac_read_powerdown, + .write = stm32_dac_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &stm32_dac_powerdown_mode_en), + IIO_ENUM_AVAILABLE("powerdown_mode", &stm32_dac_powerdown_mode_en), + {}, +}; + +#define STM32_DAC_CHANNEL(chan, name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = chan, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + /* scan_index is always 0 as num_channels is 1 */ \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ + .datasheet_name = name, \ + .ext_info = stm32_dac_ext_info \ +} + +static const struct iio_chan_spec stm32_dac_channels[] = { + STM32_DAC_CHANNEL(STM32_DAC_CHANNEL_1, "out1"), + STM32_DAC_CHANNEL(STM32_DAC_CHANNEL_2, "out2"), +}; + +static int stm32_dac_chan_of_init(struct iio_dev *indio_dev) +{ + struct device_node *np = indio_dev->dev.of_node; + unsigned int i; + u32 channel; + int ret; + + ret = of_property_read_u32(np, "reg", &channel); + if (ret) { + dev_err(&indio_dev->dev, "Failed to read reg property\n"); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(stm32_dac_channels); i++) { + if (stm32_dac_channels[i].channel == channel) + break; + } + if (i >= ARRAY_SIZE(stm32_dac_channels)) { + dev_err(&indio_dev->dev, "Invalid st,dac-channel\n"); + return -EINVAL; + } + + indio_dev->channels = &stm32_dac_channels[i]; + /* + * Expose only one channel here, as they can be used independently, + * with separate trigger. Then separate IIO devices are instantiated + * to manage this. + */ + indio_dev->num_channels = 1; + + return 0; +}; + +static int stm32_dac_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct iio_dev *indio_dev; + struct stm32_dac *dac; + int ret; + + if (!np) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*dac)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + dac = iio_priv(indio_dev); + dac->common = dev_get_drvdata(pdev->dev.parent); + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &stm32_dac_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = stm32_dac_chan_of_init(indio_dev); + if (ret < 0) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static const struct of_device_id stm32_dac_of_match[] = { + { .compatible = "st,stm32-dac", }, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_dac_of_match); + +static struct platform_driver stm32_dac_driver = { + .probe = stm32_dac_probe, + .driver = { + .name = "stm32-dac", + .of_match_table = stm32_dac_of_match, + }, +}; +module_platform_driver(stm32_dac_driver); + +MODULE_ALIAS("platform:stm32-dac"); +MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics STM32 DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/itg3200_core.c b/drivers/iio/gyro/itg3200_core.c index c102a6325bb0..cfa2db04a8ab 100644 --- a/drivers/iio/gyro/itg3200_core.c +++ b/drivers/iio/gyro/itg3200_core.c @@ -377,9 +377,16 @@ static const struct i2c_device_id itg3200_id[] = { }; MODULE_DEVICE_TABLE(i2c, itg3200_id); +static const struct of_device_id itg3200_of_match[] = { + { .compatible = "invensense,itg3200" }, + { } +}; +MODULE_DEVICE_TABLE(of, itg3200_of_match); + static struct i2c_driver itg3200_driver = { .driver = { .name = "itg3200", + .of_match_table = itg3200_of_match, .pm = &itg3200_pm_ops, }, .id_table = itg3200_id, diff --git a/drivers/iio/gyro/mpu3050-i2c.c b/drivers/iio/gyro/mpu3050-i2c.c index 06007200bf49..93f08b304a63 100644 --- a/drivers/iio/gyro/mpu3050-i2c.c +++ b/drivers/iio/gyro/mpu3050-i2c.c @@ -70,9 +70,8 @@ static int mpu3050_i2c_probe(struct i2c_client *client, dev_err(&client->dev, "failed to allocate I2C mux\n"); else { mpu3050->i2cmux->priv = mpu3050; - ret = i2c_mux_add_adapter(mpu3050->i2cmux, 0, 0, 0); - if (ret) - dev_err(&client->dev, "failed to add I2C mux\n"); + /* Ignore failure, not critical */ + i2c_mux_add_adapter(mpu3050->i2cmux, 0, 0, 0); } return 0; diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig index c5f004a8e447..a2ecb4c94c2a 100644 --- a/drivers/iio/health/Kconfig +++ b/drivers/iio/health/Kconfig @@ -46,6 +46,19 @@ config MAX30100 To compile this driver as a module, choose M here: the module will be called max30100. +config MAX30102 + tristate "MAX30102 heart rate and pulse oximeter sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say Y here to build I2C interface support for the Maxim + MAX30102 heart rate, and pulse oximeter sensor. + + To compile this driver as a module, choose M here: the + module will be called max30102. + endmenu endmenu diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile index 9955a2ae8df1..3558f9d64954 100644 --- a/drivers/iio/health/Makefile +++ b/drivers/iio/health/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_AFE4403) += afe4403.o obj-$(CONFIG_AFE4404) += afe4404.o obj-$(CONFIG_MAX30100) += max30100.o +obj-$(CONFIG_MAX30102) += max30102.o diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c index f6e283c4d686..849d71747f9f 100644 --- a/drivers/iio/health/max30100.c +++ b/drivers/iio/health/max30100.c @@ -449,6 +449,7 @@ static int max30100_probe(struct i2c_client *client, indio_dev->available_scan_masks = max30100_scan_masks; indio_dev->modes = (INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE); indio_dev->setup_ops = &max30100_buffer_setup_ops; + indio_dev->dev.parent = &client->dev; data = iio_priv(indio_dev); data->indio_dev = indio_dev; diff --git a/drivers/iio/health/max30102.c b/drivers/iio/health/max30102.c new file mode 100644 index 000000000000..839b875c29b9 --- /dev/null +++ b/drivers/iio/health/max30102.c @@ -0,0 +1,486 @@ +/* + * max30102.c - Support for MAX30102 heart rate and pulse oximeter sensor + * + * Copyright (C) 2017 Matt Ranostay <matt@ranostay.consulting> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * TODO: proximity power saving feature + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/kfifo_buf.h> + +#define MAX30102_REGMAP_NAME "max30102_regmap" +#define MAX30102_DRV_NAME "max30102" + +#define MAX30102_REG_INT_STATUS 0x00 +#define MAX30102_REG_INT_STATUS_PWR_RDY BIT(0) +#define MAX30102_REG_INT_STATUS_PROX_INT BIT(4) +#define MAX30102_REG_INT_STATUS_ALC_OVF BIT(5) +#define MAX30102_REG_INT_STATUS_PPG_RDY BIT(6) +#define MAX30102_REG_INT_STATUS_FIFO_RDY BIT(7) + +#define MAX30102_REG_INT_ENABLE 0x02 +#define MAX30102_REG_INT_ENABLE_PROX_INT_EN BIT(4) +#define MAX30102_REG_INT_ENABLE_ALC_OVF_EN BIT(5) +#define MAX30102_REG_INT_ENABLE_PPG_EN BIT(6) +#define MAX30102_REG_INT_ENABLE_FIFO_EN BIT(7) +#define MAX30102_REG_INT_ENABLE_MASK 0xf0 +#define MAX30102_REG_INT_ENABLE_MASK_SHIFT 4 + +#define MAX30102_REG_FIFO_WR_PTR 0x04 +#define MAX30102_REG_FIFO_OVR_CTR 0x05 +#define MAX30102_REG_FIFO_RD_PTR 0x06 +#define MAX30102_REG_FIFO_DATA 0x07 +#define MAX30102_REG_FIFO_DATA_ENTRY_LEN 6 + +#define MAX30102_REG_FIFO_CONFIG 0x08 +#define MAX30102_REG_FIFO_CONFIG_AVG_4SAMPLES BIT(1) +#define MAX30102_REG_FIFO_CONFIG_AVG_SHIFT 5 +#define MAX30102_REG_FIFO_CONFIG_AFULL BIT(0) + +#define MAX30102_REG_MODE_CONFIG 0x09 +#define MAX30102_REG_MODE_CONFIG_MODE_SPO2_EN BIT(0) +#define MAX30102_REG_MODE_CONFIG_MODE_HR_EN BIT(1) +#define MAX30102_REG_MODE_CONFIG_MODE_MASK 0x03 +#define MAX30102_REG_MODE_CONFIG_PWR BIT(7) + +#define MAX30102_REG_SPO2_CONFIG 0x0a +#define MAX30102_REG_SPO2_CONFIG_PULSE_411_US 0x03 +#define MAX30102_REG_SPO2_CONFIG_SR_400HZ 0x03 +#define MAX30102_REG_SPO2_CONFIG_SR_MASK 0x07 +#define MAX30102_REG_SPO2_CONFIG_SR_MASK_SHIFT 2 +#define MAX30102_REG_SPO2_CONFIG_ADC_4096_STEPS BIT(0) +#define MAX30102_REG_SPO2_CONFIG_ADC_MASK_SHIFT 5 + +#define MAX30102_REG_RED_LED_CONFIG 0x0c +#define MAX30102_REG_IR_LED_CONFIG 0x0d + +#define MAX30102_REG_TEMP_CONFIG 0x21 +#define MAX30102_REG_TEMP_CONFIG_TEMP_EN BIT(0) + +#define MAX30102_REG_TEMP_INTEGER 0x1f +#define MAX30102_REG_TEMP_FRACTION 0x20 + +struct max30102_data { + struct i2c_client *client; + struct iio_dev *indio_dev; + struct mutex lock; + struct regmap *regmap; + + u8 buffer[8]; + __be32 processed_buffer[2]; /* 2 x 18-bit (padded to 32-bits) */ +}; + +static const struct regmap_config max30102_regmap_config = { + .name = MAX30102_REGMAP_NAME, + + .reg_bits = 8, + .val_bits = 8, +}; + +static const unsigned long max30102_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec max30102_channels[] = { + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_RED, + .modified = 1, + + .scan_index = 0, + .scan_type = { + .sign = 'u', + .shift = 8, + .realbits = 18, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_IR, + .modified = 1, + + .scan_index = 1, + .scan_type = { + .sign = 'u', + .shift = 8, + .realbits = 18, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, +}; + +static int max30102_set_powermode(struct max30102_data *data, bool state) +{ + return regmap_update_bits(data->regmap, MAX30102_REG_MODE_CONFIG, + MAX30102_REG_MODE_CONFIG_PWR, + state ? 0 : MAX30102_REG_MODE_CONFIG_PWR); +} + +static int max30102_buffer_postenable(struct iio_dev *indio_dev) +{ + struct max30102_data *data = iio_priv(indio_dev); + + return max30102_set_powermode(data, true); +} + +static int max30102_buffer_predisable(struct iio_dev *indio_dev) +{ + struct max30102_data *data = iio_priv(indio_dev); + + return max30102_set_powermode(data, false); +} + +static const struct iio_buffer_setup_ops max30102_buffer_setup_ops = { + .postenable = max30102_buffer_postenable, + .predisable = max30102_buffer_predisable, +}; + +static inline int max30102_fifo_count(struct max30102_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX30102_REG_INT_STATUS, &val); + if (ret) + return ret; + + /* FIFO has one sample slot left */ + if (val & MAX30102_REG_INT_STATUS_FIFO_RDY) + return 1; + + return 0; +} + +static int max30102_read_measurement(struct max30102_data *data) +{ + int ret; + u8 *buffer = (u8 *) &data->buffer; + + ret = i2c_smbus_read_i2c_block_data(data->client, + MAX30102_REG_FIFO_DATA, + MAX30102_REG_FIFO_DATA_ENTRY_LEN, + buffer); + + memcpy(&data->processed_buffer[0], &buffer[0], 3); + memcpy(&data->processed_buffer[1], &buffer[3], 3); + + return (ret == MAX30102_REG_FIFO_DATA_ENTRY_LEN) ? 0 : -EINVAL; +} + +static irqreturn_t max30102_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct max30102_data *data = iio_priv(indio_dev); + int ret, cnt = 0; + + mutex_lock(&data->lock); + + while (cnt || (cnt = max30102_fifo_count(data)) > 0) { + ret = max30102_read_measurement(data); + if (ret) + break; + + iio_push_to_buffers(data->indio_dev, data->processed_buffer); + cnt--; + } + + mutex_unlock(&data->lock); + + return IRQ_HANDLED; +} + +static int max30102_get_current_idx(unsigned int val, int *reg) +{ + /* each step is 0.200 mA */ + *reg = val / 200; + + return *reg > 0xff ? -EINVAL : 0; +} + +static int max30102_led_init(struct max30102_data *data) +{ + struct device *dev = &data->client->dev; + struct device_node *np = dev->of_node; + unsigned int val; + int reg, ret; + + ret = of_property_read_u32(np, "maxim,red-led-current-microamp", &val); + if (ret) { + dev_info(dev, "no red-led-current-microamp set\n"); + + /* Default to 7 mA RED LED */ + val = 7000; + } + + ret = max30102_get_current_idx(val, ®); + if (ret) { + dev_err(dev, "invalid RED LED current setting %d\n", val); + return ret; + } + + ret = regmap_write(data->regmap, MAX30102_REG_RED_LED_CONFIG, reg); + if (ret) + return ret; + + ret = of_property_read_u32(np, "maxim,ir-led-current-microamp", &val); + if (ret) { + dev_info(dev, "no ir-led-current-microamp set\n"); + + /* Default to 7 mA IR LED */ + val = 7000; + } + + ret = max30102_get_current_idx(val, ®); + if (ret) { + dev_err(dev, "invalid IR LED current setting %d", val); + return ret; + } + + return regmap_write(data->regmap, MAX30102_REG_IR_LED_CONFIG, reg); +} + +static int max30102_chip_init(struct max30102_data *data) +{ + int ret; + + /* setup LED current settings */ + ret = max30102_led_init(data); + if (ret) + return ret; + + /* enable 18-bit HR + SPO2 readings at 400Hz */ + ret = regmap_write(data->regmap, MAX30102_REG_SPO2_CONFIG, + (MAX30102_REG_SPO2_CONFIG_ADC_4096_STEPS + << MAX30102_REG_SPO2_CONFIG_ADC_MASK_SHIFT) | + (MAX30102_REG_SPO2_CONFIG_SR_400HZ + << MAX30102_REG_SPO2_CONFIG_SR_MASK_SHIFT) | + MAX30102_REG_SPO2_CONFIG_PULSE_411_US); + if (ret) + return ret; + + /* enable SPO2 mode */ + ret = regmap_update_bits(data->regmap, MAX30102_REG_MODE_CONFIG, + MAX30102_REG_MODE_CONFIG_MODE_MASK, + MAX30102_REG_MODE_CONFIG_MODE_HR_EN | + MAX30102_REG_MODE_CONFIG_MODE_SPO2_EN); + if (ret) + return ret; + + /* average 4 samples + generate FIFO interrupt */ + ret = regmap_write(data->regmap, MAX30102_REG_FIFO_CONFIG, + (MAX30102_REG_FIFO_CONFIG_AVG_4SAMPLES + << MAX30102_REG_FIFO_CONFIG_AVG_SHIFT) | + MAX30102_REG_FIFO_CONFIG_AFULL); + if (ret) + return ret; + + /* enable FIFO interrupt */ + return regmap_update_bits(data->regmap, MAX30102_REG_INT_ENABLE, + MAX30102_REG_INT_ENABLE_MASK, + MAX30102_REG_INT_ENABLE_FIFO_EN); +} + +static int max30102_read_temp(struct max30102_data *data, int *val) +{ + int ret; + unsigned int reg; + + ret = regmap_read(data->regmap, MAX30102_REG_TEMP_INTEGER, ®); + if (ret < 0) + return ret; + *val = reg << 4; + + ret = regmap_read(data->regmap, MAX30102_REG_TEMP_FRACTION, ®); + if (ret < 0) + return ret; + + *val |= reg & 0xf; + *val = sign_extend32(*val, 11); + + return 0; +} + +static int max30102_get_temp(struct max30102_data *data, int *val) +{ + int ret; + + /* start acquisition */ + ret = regmap_update_bits(data->regmap, MAX30102_REG_TEMP_CONFIG, + MAX30102_REG_TEMP_CONFIG_TEMP_EN, + MAX30102_REG_TEMP_CONFIG_TEMP_EN); + if (ret) + return ret; + + msleep(35); + + return max30102_read_temp(data, val); +} + +static int max30102_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max30102_data *data = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Temperature reading can only be acquired while engine + * is running + */ + mutex_lock(&indio_dev->mlock); + + if (!iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else { + ret = max30102_get_temp(data, val); + if (!ret) + ret = IIO_VAL_INT; + } + + mutex_unlock(&indio_dev->mlock); + break; + case IIO_CHAN_INFO_SCALE: + *val = 1; /* 0.0625 */ + *val2 = 16; + ret = IIO_VAL_FRACTIONAL; + break; + } + + return ret; +} + +static const struct iio_info max30102_info = { + .driver_module = THIS_MODULE, + .read_raw = max30102_read_raw, +}; + +static int max30102_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max30102_data *data; + struct iio_buffer *buffer; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + buffer = devm_iio_kfifo_allocate(&client->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->name = MAX30102_DRV_NAME; + indio_dev->channels = max30102_channels; + indio_dev->info = &max30102_info; + indio_dev->num_channels = ARRAY_SIZE(max30102_channels); + indio_dev->available_scan_masks = max30102_scan_masks; + indio_dev->modes = (INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE); + indio_dev->setup_ops = &max30102_buffer_setup_ops; + indio_dev->dev.parent = &client->dev; + + data = iio_priv(indio_dev); + data->indio_dev = indio_dev; + data->client = client; + + mutex_init(&data->lock); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &max30102_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(data->regmap); + } + max30102_set_powermode(data, false); + + ret = max30102_chip_init(data); + if (ret) + return ret; + + if (client->irq <= 0) { + dev_err(&client->dev, "no valid irq defined\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, max30102_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max30102_irq", indio_dev); + if (ret) { + dev_err(&client->dev, "request irq (%d) failed\n", client->irq); + return ret; + } + + return iio_device_register(indio_dev); +} + +static int max30102_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct max30102_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + max30102_set_powermode(data, false); + + return 0; +} + +static const struct i2c_device_id max30102_id[] = { + { "max30102", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, max30102_id); + +static const struct of_device_id max30102_dt_ids[] = { + { .compatible = "maxim,max30102" }, + { } +}; +MODULE_DEVICE_TABLE(of, max30102_dt_ids); + +static struct i2c_driver max30102_driver = { + .driver = { + .name = MAX30102_DRV_NAME, + .of_match_table = of_match_ptr(max30102_dt_ids), + }, + .probe = max30102_probe, + .remove = max30102_remove, + .id_table = max30102_id, +}; +module_i2c_driver(max30102_driver); + +MODULE_AUTHOR("Matt Ranostay <matt@ranostay.consulting>"); +MODULE_DESCRIPTION("MAX30102 heart rate and pulse oximeter sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig index 912477d54be2..14b9ce453d9d 100644 --- a/drivers/iio/humidity/Kconfig +++ b/drivers/iio/humidity/Kconfig @@ -36,6 +36,20 @@ config HDC100X To compile this driver as a module, choose M here: the module will be called hdc100x. +config HID_SENSOR_HUMIDITY + tristate "HID Environmental humidity sensor" + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + help + Say yes here to build support for the HID SENSOR + humidity driver + + To compile this driver as a module, choose M here: the module + will be called hid-sensor-humidity. + config HTS221 tristate "STMicroelectronics HTS221 sensor Driver" depends on (I2C || SPI) diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile index a6850e47c100..be0dedeb8f3c 100644 --- a/drivers/iio/humidity/Makefile +++ b/drivers/iio/humidity/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_AM2315) += am2315.o obj-$(CONFIG_DHT11) += dht11.o obj-$(CONFIG_HDC100X) += hdc100x.o +obj-$(CONFIG_HID_SENSOR_HUMIDITY) += hid-sensor-humidity.o hts221-y := hts221_core.o \ hts221_buffer.o @@ -15,3 +16,5 @@ obj-$(CONFIG_HTS221_SPI) += hts221_spi.o obj-$(CONFIG_HTU21) += htu21.o obj-$(CONFIG_SI7005) += si7005.o obj-$(CONFIG_SI7020) += si7020.o + +ccflags-y += -I$(srctree)/drivers/iio/common/hid-sensors diff --git a/drivers/iio/humidity/hdc100x.c b/drivers/iio/humidity/hdc100x.c index 265c34da52d1..aa17115f54c9 100644 --- a/drivers/iio/humidity/hdc100x.c +++ b/drivers/iio/humidity/hdc100x.c @@ -79,7 +79,7 @@ static struct attribute *hdc100x_attributes[] = { NULL }; -static struct attribute_group hdc100x_attribute_group = { +static const struct attribute_group hdc100x_attribute_group = { .attrs = hdc100x_attributes, }; diff --git a/drivers/iio/humidity/hid-sensor-humidity.c b/drivers/iio/humidity/hid-sensor-humidity.c new file mode 100644 index 000000000000..6e09c1acfe51 --- /dev/null +++ b/drivers/iio/humidity/hid-sensor-humidity.c @@ -0,0 +1,315 @@ +/* + * HID Sensors Driver + * Copyright (c) 2017, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. + */ +#include <linux/device.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "hid-sensor-trigger.h" + +struct hid_humidity_state { + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info humidity_attr; + s32 humidity_data; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +/* Channel definitions */ +static const struct iio_chan_spec humidity_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +/* Adjust channel real bits based on report descriptor */ +static void humidity_adjust_channel_bit_mask(struct iio_chan_spec *channels, + int channel, int size) +{ + channels[channel].scan_type.sign = 's'; + /* Real storage bits will change based on the report desc. */ + channels[channel].scan_type.realbits = size * 8; + /* Maximum size of a sample to capture is s32 */ + channels[channel].scan_type.storagebits = sizeof(s32) * 8; +} + +static int humidity_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct hid_humidity_state *humid_st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_HUMIDITYRELATIVE) + return -EINVAL; + hid_sensor_power_state(&humid_st->common_attributes, true); + *val = sensor_hub_input_attr_get_raw_value( + humid_st->common_attributes.hsdev, + HID_USAGE_SENSOR_HUMIDITY, + HID_USAGE_SENSOR_ATMOSPHERIC_HUMIDITY, + humid_st->humidity_attr.report_id, + SENSOR_HUB_SYNC); + hid_sensor_power_state(&humid_st->common_attributes, false); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = humid_st->scale_pre_decml; + *val2 = humid_st->scale_post_decml; + + return humid_st->scale_precision; + + case IIO_CHAN_INFO_OFFSET: + *val = humid_st->value_offset; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + return hid_sensor_read_samp_freq_value( + &humid_st->common_attributes, val, val2); + + case IIO_CHAN_INFO_HYSTERESIS: + return hid_sensor_read_raw_hyst_value( + &humid_st->common_attributes, val, val2); + + default: + return -EINVAL; + } +} + +static int humidity_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct hid_humidity_state *humid_st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return hid_sensor_write_samp_freq_value( + &humid_st->common_attributes, val, val2); + + case IIO_CHAN_INFO_HYSTERESIS: + return hid_sensor_write_raw_hyst_value( + &humid_st->common_attributes, val, val2); + + default: + return -EINVAL; + } +} + +static const struct iio_info humidity_info = { + .driver_module = THIS_MODULE, + .read_raw = &humidity_read_raw, + .write_raw = &humidity_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int humidity_proc_event(struct hid_sensor_hub_device *hsdev, + unsigned int usage_id, void *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hid_humidity_state *humid_st = iio_priv(indio_dev); + + if (atomic_read(&humid_st->common_attributes.data_ready)) + iio_push_to_buffers_with_timestamp(indio_dev, + &humid_st->humidity_data, + iio_get_time_ns(indio_dev)); + + return 0; +} + +/* Capture samples in local storage */ +static int humidity_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned int usage_id, size_t raw_len, + char *raw_data, void *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hid_humidity_state *humid_st = iio_priv(indio_dev); + + switch (usage_id) { + case HID_USAGE_SENSOR_ATMOSPHERIC_HUMIDITY: + humid_st->humidity_data = *(s32 *)raw_data; + + return 0; + default: + return -EINVAL; + } +} + +/* Parse report which is specific to an usage id */ +static int humidity_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + unsigned int usage_id, + struct hid_humidity_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ATMOSPHERIC_HUMIDITY, + &st->humidity_attr); + if (ret < 0) + return ret; + + humidity_adjust_channel_bit_mask(channels, 0, st->humidity_attr.size); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_HUMIDITY, + &st->humidity_attr, + &st->scale_pre_decml, + &st->scale_post_decml); + + /* Set Sensitivity field ids, when there is no individual modifier */ + if (st->common_attributes.sensitivity.index < 0) + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS | + HID_USAGE_SENSOR_ATMOSPHERIC_HUMIDITY, + &st->common_attributes.sensitivity); + + return ret; +} + +static struct hid_sensor_hub_callbacks humidity_callbacks = { + .send_event = &humidity_proc_event, + .capture_sample = &humidity_capture_sample, +}; + +/* Function to initialize the processing for usage id */ +static int hid_humidity_probe(struct platform_device *pdev) +{ + static const char *name = "humidity"; + struct iio_dev *indio_dev; + struct hid_humidity_state *humid_st; + struct iio_chan_spec *humid_chans; + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*humid_st)); + if (!indio_dev) + return -ENOMEM; + + humid_st = iio_priv(indio_dev); + humid_st->common_attributes.hsdev = hsdev; + humid_st->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_HUMIDITY, + &humid_st->common_attributes); + if (ret) + return ret; + + humid_chans = devm_kmemdup(&indio_dev->dev, humidity_channels, + sizeof(humidity_channels), GFP_KERNEL); + if (!humid_chans) + return -ENOMEM; + + ret = humidity_parse_report(pdev, hsdev, humid_chans, + HID_USAGE_SENSOR_HUMIDITY, humid_st); + if (ret) + return ret; + + indio_dev->channels = humid_chans; + indio_dev->num_channels = ARRAY_SIZE(humidity_channels); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &humidity_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_triggered_buffer_setup(&pdev->dev, indio_dev, + &iio_pollfunc_store_time, NULL, NULL); + if (ret) + return ret; + + atomic_set(&humid_st->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, name, + &humid_st->common_attributes); + if (ret) + return ret; + + platform_set_drvdata(pdev, indio_dev); + + humidity_callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_HUMIDITY, + &humidity_callbacks); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_callback; + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_HUMIDITY); +error_remove_trigger: + hid_sensor_remove_trigger(&humid_st->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_humidity_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hid_humidity_state *humid_st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_HUMIDITY); + hid_sensor_remove_trigger(&humid_st->common_attributes); + + return 0; +} + +static const struct platform_device_id hid_humidity_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200032", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_humidity_ids); + +static struct platform_driver hid_humidity_platform_driver = { + .id_table = hid_humidity_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_humidity_probe, + .remove = hid_humidity_remove, +}; +module_platform_driver(hid_humidity_platform_driver); + +MODULE_DESCRIPTION("HID Environmental humidity sensor"); +MODULE_AUTHOR("Song Hongyan <hongyan.song@intel.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/humidity/hts221_buffer.c b/drivers/iio/humidity/hts221_buffer.c index 72ddcdac21a2..7d19a3da7ab7 100644 --- a/drivers/iio/humidity/hts221_buffer.c +++ b/drivers/iio/humidity/hts221_buffer.c @@ -41,7 +41,7 @@ static const struct iio_trigger_ops hts221_trigger_ops = { static irqreturn_t hts221_trigger_handler_thread(int irq, void *private) { - struct hts221_hw *hw = (struct hts221_hw *)private; + struct hts221_hw *hw = private; u8 status; int err; diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index b9fcbf18aa99..96dabbd2f004 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -114,6 +114,12 @@ static const struct inv_mpu6050_hw hw_info[] = { .config = &chip_config_6050, }, { + .whoami = INV_MPU9250_WHOAMI_VALUE, + .name = "MPU9250", + .reg = ®_set_6500, + .config = &chip_config_6050, + }, + { .whoami = INV_ICM20608_WHOAMI_VALUE, .name = "ICM20608", .reg = ®_set_6500, diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c index 2c3f8964a3ea..64b5f5b92200 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c @@ -17,6 +17,7 @@ #include <linux/i2c.h> #include <linux/iio/iio.h> #include <linux/module.h> +#include <linux/of_device.h> #include "inv_mpu_iio.h" static const struct regmap_config inv_mpu_regmap_config = { @@ -69,7 +70,8 @@ static int inv_mpu6050_deselect_bypass(struct i2c_mux_core *muxc, u32 chan_id) return 0; } -static const char *inv_mpu_match_acpi_device(struct device *dev, int *chip_id) +static const char *inv_mpu_match_acpi_device(struct device *dev, + enum inv_devices *chip_id) { const struct acpi_device_id *id; @@ -93,7 +95,8 @@ static int inv_mpu_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct inv_mpu6050_state *st; - int result, chip_type; + int result; + enum inv_devices chip_type; struct regmap *regmap; const char *name; @@ -101,8 +104,13 @@ static int inv_mpu_probe(struct i2c_client *client, I2C_FUNC_SMBUS_I2C_BLOCK)) return -EOPNOTSUPP; - if (id) { - chip_type = (int)id->driver_data; + if (client->dev.of_node) { + chip_type = (enum inv_devices) + of_device_get_match_data(&client->dev); + name = client->name; + } else if (id) { + chip_type = (enum inv_devices) + id->driver_data; name = id->name; } else if (ACPI_HANDLE(&client->dev)) { name = inv_mpu_match_acpi_device(&client->dev, &chip_type); @@ -170,12 +178,38 @@ static const struct i2c_device_id inv_mpu_id[] = { {"mpu6050", INV_MPU6050}, {"mpu6500", INV_MPU6500}, {"mpu9150", INV_MPU9150}, + {"mpu9250", INV_MPU9250}, {"icm20608", INV_ICM20608}, {} }; MODULE_DEVICE_TABLE(i2c, inv_mpu_id); +static const struct of_device_id inv_of_match[] = { + { + .compatible = "invensense,mpu6050", + .data = (void *)INV_MPU6050 + }, + { + .compatible = "invensense,mpu6500", + .data = (void *)INV_MPU6500 + }, + { + .compatible = "invensense,mpu9150", + .data = (void *)INV_MPU9150 + }, + { + .compatible = "invensense,mpu9250", + .data = (void *)INV_MPU9250 + }, + { + .compatible = "invensense,icm20608", + .data = (void *)INV_ICM20608 + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_of_match); + static const struct acpi_device_id inv_acpi_match[] = { {"INVN6500", INV_MPU6500}, { }, @@ -188,6 +222,7 @@ static struct i2c_driver inv_mpu_driver = { .remove = inv_mpu_remove, .id_table = inv_mpu_id, .driver = { + .of_match_table = inv_of_match, .acpi_match_table = ACPI_PTR(inv_acpi_match), .name = "inv-mpu6050-i2c", .pm = &inv_mpu_pmops, diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h index f0e8c5dd9fae..ef13de7a2c20 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -70,6 +70,7 @@ enum inv_devices { INV_MPU6500, INV_MPU6000, INV_MPU9150, + INV_MPU9250, INV_ICM20608, INV_NUM_PARTS }; @@ -226,6 +227,7 @@ struct inv_mpu6050_state { #define INV_MPU6050_WHOAMI_VALUE 0x68 #define INV_MPU6500_WHOAMI_VALUE 0x70 #define INV_MPU9150_WHOAMI_VALUE 0x68 +#define INV_MPU9250_WHOAMI_VALUE 0x71 #define INV_ICM20608_WHOAMI_VALUE 0xAF /* scan element definition */ diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c index 6e6476dfa188..74506e5ac0db 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c @@ -82,6 +82,7 @@ static const struct spi_device_id inv_mpu_id[] = { {"mpu6000", INV_MPU6000}, {"mpu6500", INV_MPU6500}, {"mpu9150", INV_MPU9150}, + {"mpu9250", INV_MPU9250}, {"icm20608", INV_ICM20608}, {} }; diff --git a/drivers/iio/imu/st_lsm6dsx/Kconfig b/drivers/iio/imu/st_lsm6dsx/Kconfig index 935d4cd071a3..e57337159b57 100644 --- a/drivers/iio/imu/st_lsm6dsx/Kconfig +++ b/drivers/iio/imu/st_lsm6dsx/Kconfig @@ -8,7 +8,7 @@ config IIO_ST_LSM6DSX select IIO_ST_LSM6DSX_SPI if (SPI_MASTER) help Say yes here to build support for STMicroelectronics LSM6DSx imu - sensor. Supported devices: lsm6ds3, lsm6dsm + sensor. Supported devices: lsm6ds3, lsm6ds3h, lsm6dsl, lsm6dsm To compile this driver as a module, choose M here: the module will be called st_lsm6dsx. diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 69deafe1c10d..4839db7b9690 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -15,11 +15,16 @@ #include <linux/device.h> #define ST_LSM6DS3_DEV_NAME "lsm6ds3" +#define ST_LSM6DS3H_DEV_NAME "lsm6ds3h" +#define ST_LSM6DSL_DEV_NAME "lsm6dsl" #define ST_LSM6DSM_DEV_NAME "lsm6dsm" enum st_lsm6dsx_hw_id { ST_LSM6DS3_ID, + ST_LSM6DS3H_ID, + ST_LSM6DSL_ID, ST_LSM6DSM_ID, + ST_LSM6DSX_MAX_ID, }; #define ST_LSM6DSX_CHAN_SIZE 2 @@ -50,7 +55,7 @@ struct st_lsm6dsx_reg { struct st_lsm6dsx_settings { u8 wai; u16 max_fifo_size; - enum st_lsm6dsx_hw_id id; + enum st_lsm6dsx_hw_id id[ST_LSM6DSX_MAX_ID]; }; enum st_lsm6dsx_sensor_id { @@ -66,6 +71,7 @@ enum st_lsm6dsx_fifo_mode { /** * struct st_lsm6dsx_sensor - ST IMU sensor instance + * @name: Sensor name. * @id: Sensor identifier. * @hw: Pointer to instance of struct st_lsm6dsx_hw. * @gain: Configured sensor sensitivity. @@ -78,6 +84,7 @@ enum st_lsm6dsx_fifo_mode { * @ts: Latest timestamp from the interrupt handler. */ struct st_lsm6dsx_sensor { + char name[32]; enum st_lsm6dsx_sensor_id id; struct st_lsm6dsx_hw *hw; @@ -128,7 +135,7 @@ struct st_lsm6dsx_hw { #endif /* CONFIG_SPI_MASTER */ }; -int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, +int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name, const struct st_lsm6dsx_transfer_function *tf_ops); int st_lsm6dsx_sensor_enable(struct st_lsm6dsx_sensor *sensor); int st_lsm6dsx_sensor_disable(struct st_lsm6dsx_sensor *sensor); diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c index 81b572d7699a..c8e5cfd0ef0b 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c @@ -1,9 +1,10 @@ /* * STMicroelectronics st_lsm6dsx FIFO buffer library driver * - * LSM6DS3/LSM6DSM: The FIFO buffer can be configured to store data - * from gyroscope and accelerometer. Samples are queued without any tag - * according to a specific pattern based on 'FIFO data sets' (6 bytes each): + * LSM6DS3/LSM6DS3H/LSM6DSL/LSM6DSM: The FIFO buffer can be configured + * to store data from gyroscope and accelerometer. Samples are queued + * without any tag according to a specific pattern based on 'FIFO data sets' + * (6 bytes each): * - 1st data set is reserved for gyroscope data * - 2nd data set is reserved for accelerometer data * The FIFO pattern changes depending on the ODRs and decimation factors @@ -206,7 +207,7 @@ out: } /** - * st_lsm6dsx_read_fifo() - LSM6DS3-LSM6DSM read FIFO routine + * st_lsm6dsx_read_fifo() - LSM6DS3-LSM6DS3H-LSM6DSL-LSM6DSM read FIFO routine * @hw: Pointer to instance of struct st_lsm6dsx_hw. * * Read samples from the hw FIFO and push them to IIO buffers. @@ -363,7 +364,7 @@ static int st_lsm6dsx_update_fifo(struct iio_dev *iio_dev, bool enable) static irqreturn_t st_lsm6dsx_handler_irq(int irq, void *private) { - struct st_lsm6dsx_hw *hw = (struct st_lsm6dsx_hw *)private; + struct st_lsm6dsx_hw *hw = private; struct st_lsm6dsx_sensor *sensor; int i; @@ -387,7 +388,7 @@ static irqreturn_t st_lsm6dsx_handler_irq(int irq, void *private) static irqreturn_t st_lsm6dsx_handler_thread(int irq, void *private) { - struct st_lsm6dsx_hw *hw = (struct st_lsm6dsx_hw *)private; + struct st_lsm6dsx_hw *hw = private; int count; mutex_lock(&hw->fifo_lock); diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index c92ddcc190e2..462a27b70453 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -17,7 +17,7 @@ * - Gyroscope supported full-scale [dps]: +-125/+-245/+-500/+-1000/+-2000 * - FIFO size: 8KB * - * - LSM6DSM: + * - LSM6DS3H/LSM6DSL/LSM6DSM: * - Accelerometer/Gyroscope supported ODR [Hz]: 13, 26, 52, 104, 208, 416 * - Accelerometer supported full-scale [g]: +-2/+-4/+-8/+-16 * - Gyroscope supported full-scale [dps]: +-125/+-245/+-500/+-1000/+-2000 @@ -74,12 +74,6 @@ #define ST_LSM6DSX_REG_GYRO_OUT_Y_L_ADDR 0x24 #define ST_LSM6DSX_REG_GYRO_OUT_Z_L_ADDR 0x26 -#define ST_LSM6DS3_WHOAMI 0x69 -#define ST_LSM6DSM_WHOAMI 0x6a - -#define ST_LSM6DS3_MAX_FIFO_SIZE 8192 -#define ST_LSM6DSM_MAX_FIFO_SIZE 4096 - #define ST_LSM6DSX_ACC_FS_2G_GAIN IIO_G_TO_M_S_2(61) #define ST_LSM6DSX_ACC_FS_4G_GAIN IIO_G_TO_M_S_2(122) #define ST_LSM6DSX_ACC_FS_8G_GAIN IIO_G_TO_M_S_2(244) @@ -164,14 +158,26 @@ static const struct st_lsm6dsx_fs_table_entry st_lsm6dsx_fs_table[] = { static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { { - .wai = ST_LSM6DS3_WHOAMI, - .max_fifo_size = ST_LSM6DS3_MAX_FIFO_SIZE, - .id = ST_LSM6DS3_ID, + .wai = 0x69, + .max_fifo_size = 8192, + .id = { + [0] = ST_LSM6DS3_ID, + }, }, { - .wai = ST_LSM6DSM_WHOAMI, - .max_fifo_size = ST_LSM6DSM_MAX_FIFO_SIZE, - .id = ST_LSM6DSM_ID, + .wai = 0x69, + .max_fifo_size = 4096, + .id = { + [0] = ST_LSM6DS3H_ID, + }, + }, + { + .wai = 0x6a, + .max_fifo_size = 4096, + .id = { + [0] = ST_LSM6DSL_ID, + [1] = ST_LSM6DSM_ID, + }, }, }; @@ -241,11 +247,15 @@ out: static int st_lsm6dsx_check_whoami(struct st_lsm6dsx_hw *hw, int id) { - int err, i; + int err, i, j; u8 data; for (i = 0; i < ARRAY_SIZE(st_lsm6dsx_sensor_settings); i++) { - if (id == st_lsm6dsx_sensor_settings[i].id) + for (j = 0; j < ST_LSM6DSX_MAX_ID; j++) { + if (id == st_lsm6dsx_sensor_settings[i].id[j]) + break; + } + if (j < ST_LSM6DSX_MAX_ID) break; } @@ -298,32 +308,40 @@ static int st_lsm6dsx_set_full_scale(struct st_lsm6dsx_sensor *sensor, return 0; } -static int st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u16 odr) +static int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u16 odr, + u8 *val) { - enum st_lsm6dsx_sensor_id id = sensor->id; - int i, err; - u8 val; + int i; for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++) - if (st_lsm6dsx_odr_table[id].odr_avl[i].hz == odr) + if (st_lsm6dsx_odr_table[sensor->id].odr_avl[i].hz == odr) break; if (i == ST_LSM6DSX_ODR_LIST_SIZE) return -EINVAL; - val = st_lsm6dsx_odr_table[id].odr_avl[i].val; - err = st_lsm6dsx_write_with_mask(sensor->hw, - st_lsm6dsx_odr_table[id].reg.addr, - st_lsm6dsx_odr_table[id].reg.mask, - val); - if (err < 0) - return err; - + *val = st_lsm6dsx_odr_table[sensor->id].odr_avl[i].val; sensor->odr = odr; return 0; } +static int st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u16 odr) +{ + enum st_lsm6dsx_sensor_id id = sensor->id; + int err; + u8 val; + + err = st_lsm6dsx_check_odr(sensor, odr, &val); + if (err < 0) + return err; + + return st_lsm6dsx_write_with_mask(sensor->hw, + st_lsm6dsx_odr_table[id].reg.addr, + st_lsm6dsx_odr_table[id].reg.mask, + val); +} + int st_lsm6dsx_sensor_enable(struct st_lsm6dsx_sensor *sensor) { int err; @@ -426,9 +444,12 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev, case IIO_CHAN_INFO_SCALE: err = st_lsm6dsx_set_full_scale(sensor, val2); break; - case IIO_CHAN_INFO_SAMP_FREQ: - err = st_lsm6dsx_set_odr(sensor, val); + case IIO_CHAN_INFO_SAMP_FREQ: { + u8 data; + + err = st_lsm6dsx_check_odr(sensor, val, &data); break; + } default: err = -EINVAL; break; @@ -538,19 +559,11 @@ static const unsigned long st_lsm6dsx_available_scan_masks[] = {0x7, 0x0}; static int st_lsm6dsx_of_get_drdy_pin(struct st_lsm6dsx_hw *hw, int *drdy_pin) { struct device_node *np = hw->dev->of_node; - int err; if (!np) return -EINVAL; - err = of_property_read_u32(np, "st,drdy-int-pin", drdy_pin); - if (err == -ENODATA) { - /* if the property has not been specified use default value */ - *drdy_pin = 1; - err = 0; - } - - return err; + return of_property_read_u32(np, "st,drdy-int-pin", drdy_pin); } static int st_lsm6dsx_get_drdy_reg(struct st_lsm6dsx_hw *hw, u8 *drdy_reg) @@ -621,7 +634,8 @@ static int st_lsm6dsx_init_device(struct st_lsm6dsx_hw *hw) } static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw, - enum st_lsm6dsx_sensor_id id) + enum st_lsm6dsx_sensor_id id, + const char *name) { struct st_lsm6dsx_sensor *sensor; struct iio_dev *iio_dev; @@ -645,27 +659,30 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw, case ST_LSM6DSX_ID_ACC: iio_dev->channels = st_lsm6dsx_acc_channels; iio_dev->num_channels = ARRAY_SIZE(st_lsm6dsx_acc_channels); - iio_dev->name = "lsm6dsx_accel"; iio_dev->info = &st_lsm6dsx_acc_info; sensor->decimator_mask = ST_LSM6DSX_REG_ACC_DEC_MASK; + scnprintf(sensor->name, sizeof(sensor->name), "%s_accel", + name); break; case ST_LSM6DSX_ID_GYRO: iio_dev->channels = st_lsm6dsx_gyro_channels; iio_dev->num_channels = ARRAY_SIZE(st_lsm6dsx_gyro_channels); - iio_dev->name = "lsm6dsx_gyro"; iio_dev->info = &st_lsm6dsx_gyro_info; sensor->decimator_mask = ST_LSM6DSX_REG_GYRO_DEC_MASK; + scnprintf(sensor->name, sizeof(sensor->name), "%s_gyro", + name); break; default: return NULL; } + iio_dev->name = sensor->name; return iio_dev; } -int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, +int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name, const struct st_lsm6dsx_transfer_function *tf_ops) { struct st_lsm6dsx_hw *hw; @@ -689,7 +706,7 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, return err; for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { - hw->iio_devs[i] = st_lsm6dsx_alloc_iiodev(hw, i); + hw->iio_devs[i] = st_lsm6dsx_alloc_iiodev(hw, i, name); if (!hw->iio_devs[i]) return -ENOMEM; } diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c index ea3041186e1e..09a51cfb9b5e 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c @@ -61,7 +61,7 @@ static int st_lsm6dsx_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { return st_lsm6dsx_probe(&client->dev, client->irq, - (int)id->driver_data, + (int)id->driver_data, id->name, &st_lsm6dsx_transfer_fn); } @@ -71,6 +71,14 @@ static const struct of_device_id st_lsm6dsx_i2c_of_match[] = { .data = (void *)ST_LSM6DS3_ID, }, { + .compatible = "st,lsm6ds3h", + .data = (void *)ST_LSM6DS3H_ID, + }, + { + .compatible = "st,lsm6dsl", + .data = (void *)ST_LSM6DSL_ID, + }, + { .compatible = "st,lsm6dsm", .data = (void *)ST_LSM6DSM_ID, }, @@ -80,6 +88,8 @@ MODULE_DEVICE_TABLE(of, st_lsm6dsx_i2c_of_match); static const struct i2c_device_id st_lsm6dsx_i2c_id_table[] = { { ST_LSM6DS3_DEV_NAME, ST_LSM6DS3_ID }, + { ST_LSM6DS3H_DEV_NAME, ST_LSM6DS3H_ID }, + { ST_LSM6DSL_DEV_NAME, ST_LSM6DSL_ID }, { ST_LSM6DSM_DEV_NAME, ST_LSM6DSM_ID }, {}, }; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c index fbe72470ed1e..f765a5058488 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c @@ -78,7 +78,7 @@ static int st_lsm6dsx_spi_probe(struct spi_device *spi) const struct spi_device_id *id = spi_get_device_id(spi); return st_lsm6dsx_probe(&spi->dev, spi->irq, - (int)id->driver_data, + (int)id->driver_data, id->name, &st_lsm6dsx_transfer_fn); } @@ -88,6 +88,14 @@ static const struct of_device_id st_lsm6dsx_spi_of_match[] = { .data = (void *)ST_LSM6DS3_ID, }, { + .compatible = "st,lsm6ds3h", + .data = (void *)ST_LSM6DS3H_ID, + }, + { + .compatible = "st,lsm6dsl", + .data = (void *)ST_LSM6DSL_ID, + }, + { .compatible = "st,lsm6dsm", .data = (void *)ST_LSM6DSM_ID, }, @@ -97,6 +105,8 @@ MODULE_DEVICE_TABLE(of, st_lsm6dsx_spi_of_match); static const struct spi_device_id st_lsm6dsx_spi_id_table[] = { { ST_LSM6DS3_DEV_NAME, ST_LSM6DS3_ID }, + { ST_LSM6DS3H_DEV_NAME, ST_LSM6DS3H_ID }, + { ST_LSM6DSL_DEV_NAME, ST_LSM6DSL_ID }, { ST_LSM6DSM_DEV_NAME, ST_LSM6DSM_ID }, {}, }; diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 5f731ead9d46..33e755d8d825 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -136,6 +136,16 @@ config CM36651 To compile this driver as a module, choose M here: the module will be called cm36651. +config IIO_CROS_EC_LIGHT_PROX + tristate "ChromeOS EC Light and Proximity Sensors" + depends on IIO_CROS_EC_SENSORS_CORE + help + Say Y here if you use the light and proximity sensors + presented by the ChromeOS EC Sensor hub. + + To compile this driver as a module, choose M here: + the module will be called cros_ec_light_prox. + config GP2AP020A00F tristate "Sharp GP2AP020A00F Proximity/ALS sensor" depends on I2C @@ -395,4 +405,14 @@ config VEML6070 To compile this driver as a module, choose M here: the module will be called veml6070. +config VL6180 + tristate "VL6180 ALS, range and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light, range and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vl6180. + endmenu diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index c13a2399e6df..681363c2b298 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_CM3232) += cm3232.o obj-$(CONFIG_CM3323) += cm3323.o obj-$(CONFIG_CM3605) += cm3605.o obj-$(CONFIG_CM36651) += cm36651.o +obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o @@ -37,3 +38,4 @@ obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VL6180) += vl6180.o diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c index a4304edc3e0f..518a47e9377b 100644 --- a/drivers/iio/light/apds9960.c +++ b/drivers/iio/light/apds9960.c @@ -343,7 +343,7 @@ static struct attribute *apds9960_attributes[] = { NULL, }; -static struct attribute_group apds9960_attribute_group = { +static const struct attribute_group apds9960_attribute_group = { .attrs = apds9960_attributes, }; @@ -1112,6 +1112,8 @@ static int apds9960_runtime_resume(struct device *dev) #endif static const struct dev_pm_ops apds9960_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) SET_RUNTIME_PM_OPS(apds9960_runtime_suspend, apds9960_runtime_resume, NULL) }; @@ -1122,9 +1124,16 @@ static const struct i2c_device_id apds9960_id[] = { }; MODULE_DEVICE_TABLE(i2c, apds9960_id); +static const struct of_device_id apds9960_of_match[] = { + { .compatible = "avago,apds9960" }, + { } +}; +MODULE_DEVICE_TABLE(of, apds9960_of_match); + static struct i2c_driver apds9960_driver = { .driver = { .name = APDS9960_DRV_NAME, + .of_match_table = apds9960_of_match, .pm = &apds9960_pm_ops, }, .probe = apds9960_probe, diff --git a/drivers/iio/light/bh1750.c b/drivers/iio/light/bh1750.c index b05946604f80..6c61187e630f 100644 --- a/drivers/iio/light/bh1750.c +++ b/drivers/iio/light/bh1750.c @@ -212,7 +212,7 @@ static struct attribute *bh1750_attributes[] = { NULL, }; -static struct attribute_group bh1750_attribute_group = { +static const struct attribute_group bh1750_attribute_group = { .attrs = bh1750_attributes, }; diff --git a/drivers/iio/light/cros_ec_light_prox.c b/drivers/iio/light/cros_ec_light_prox.c new file mode 100644 index 000000000000..721722376fd0 --- /dev/null +++ b/drivers/iio/light/cros_ec_light_prox.c @@ -0,0 +1,289 @@ +/* + * cros_ec_light_prox - Driver for light and prox sensors behing CrosEC. + * + * Copyright (C) 2017 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/kernel.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sysfs.h> + +#include "../common/cros_ec_sensors/cros_ec_sensors_core.h" + +/* + * We only represent one entry for light or proximity. EC is merging different + * light sensors to return the what the eye would see. For proximity, we + * currently support only one light source. + */ +#define CROS_EC_LIGHT_PROX_MAX_CHANNELS (1 + 1) + +/* State data for ec_sensors iio driver. */ +struct cros_ec_light_prox_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; + + struct iio_chan_spec channels[CROS_EC_LIGHT_PROX_MAX_CHANNELS]; +}; + +static int cros_ec_light_prox_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_light_prox_state *st = iio_priv(indio_dev); + u16 data = 0; + s64 val64; + int ret = IIO_VAL_INT; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_PROXIMITY) { + if (cros_ec_sensors_read_cmd(indio_dev, 1 << idx, + (s16 *)&data) < 0) { + ret = -EIO; + break; + } + *val = data; + } else { + ret = -EINVAL; + } + break; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type == IIO_LIGHT) { + if (cros_ec_sensors_read_cmd(indio_dev, 1 << idx, + (s16 *)&data) < 0) { + ret = -EIO; + break; + } + /* + * The data coming from the light sensor is + * pre-processed and represents the ambient light + * illuminance reading expressed in lux. + */ + *val = data; + ret = IIO_VAL_INT; + } else { + ret = -EINVAL; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; + st->core.param.sensor_offset.flags = 0; + + if (cros_ec_motion_send_host_cmd(&st->core, 0)) { + ret = -EIO; + break; + } + + /* Save values */ + st->core.calib[0] = st->core.resp->sensor_offset.offset[0]; + + *val = st->core.calib[idx]; + break; + case IIO_CHAN_INFO_CALIBSCALE: + /* + * RANGE is used for calibration + * scale is a number x.y, where x is coded on 16 bits, + * y coded on 16 bits, between 0 and 9999. + */ + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE; + + if (cros_ec_motion_send_host_cmd(&st->core, 0)) { + ret = -EIO; + break; + } + + val64 = st->core.resp->sensor_range.ret; + *val = val64 >> 16; + *val2 = (val64 & 0xffff) * 100; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = cros_ec_sensors_core_read(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static int cros_ec_light_prox_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cros_ec_light_prox_state *st = iio_priv(indio_dev); + int ret = 0; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + st->core.calib[idx] = val; + /* Send to EC for each axis, even if not complete */ + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; + st->core.param.sensor_offset.flags = MOTION_SENSE_SET_OFFSET; + st->core.param.sensor_offset.offset[0] = st->core.calib[0]; + st->core.param.sensor_offset.temp = + EC_MOTION_SENSE_INVALID_CALIB_TEMP; + if (cros_ec_motion_send_host_cmd(&st->core, 0)) + ret = -EIO; + break; + case IIO_CHAN_INFO_CALIBSCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = (val << 16) | (val2 / 100); + if (cros_ec_motion_send_host_cmd(&st->core, 0)) + ret = -EIO; + break; + default: + ret = cros_ec_sensors_core_write(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static const struct iio_info cros_ec_light_prox_info = { + .read_raw = &cros_ec_light_prox_read, + .write_raw = &cros_ec_light_prox_write, + .driver_module = THIS_MODULE, +}; + +static int cros_ec_light_prox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); + struct cros_ec_device *ec_device; + struct iio_dev *indio_dev; + struct cros_ec_light_prox_state *state; + struct iio_chan_spec *channel; + int ret; + + if (!ec_dev || !ec_dev->ec_dev) { + dev_warn(dev, "No CROS EC device found.\n"); + return -EINVAL; + } + ec_device = ec_dev->ec_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true); + if (ret) + return ret; + + indio_dev->info = &cros_ec_light_prox_info; + state = iio_priv(indio_dev); + state->core.type = state->core.resp->info.type; + state->core.loc = state->core.resp->info.location; + channel = state->channels; + + /* Common part */ + channel->info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_FREQUENCY); + channel->scan_type.realbits = CROS_EC_SENSOR_BITS; + channel->scan_type.storagebits = CROS_EC_SENSOR_BITS; + channel->scan_type.shift = 0; + channel->scan_index = 0; + channel->ext_info = cros_ec_sensors_ext_info; + channel->scan_type.sign = 'u'; + + state->core.calib[0] = 0; + + /* Sensor specific */ + switch (state->core.type) { + case MOTIONSENSE_TYPE_LIGHT: + channel->type = IIO_LIGHT; + channel->info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE); + break; + case MOTIONSENSE_TYPE_PROX: + channel->type = IIO_PROXIMITY; + channel->info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE); + break; + default: + dev_warn(dev, "Unknown motion sensor\n"); + return -EINVAL; + } + + /* Timestamp */ + channel++; + channel->type = IIO_TIMESTAMP; + channel->channel = -1; + channel->scan_index = 1; + channel->scan_type.sign = 's'; + channel->scan_type.realbits = 64; + channel->scan_type.storagebits = 64; + + indio_dev->channels = state->channels; + + indio_dev->num_channels = CROS_EC_LIGHT_PROX_MAX_CHANNELS; + + state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + cros_ec_sensors_capture, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct platform_device_id cros_ec_light_prox_ids[] = { + { + .name = "cros-ec-prox", + }, + { + .name = "cros-ec-light", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, cros_ec_light_prox_ids); + +static struct platform_driver cros_ec_light_prox_platform_driver = { + .driver = { + .name = "cros-ec-light-prox", + }, + .probe = cros_ec_light_prox_probe, + .id_table = cros_ec_light_prox_ids, +}; +module_platform_driver(cros_ec_light_prox_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC light/proximity sensors driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c index 45ca056f019e..73fced8a63b7 100644 --- a/drivers/iio/light/hid-sensor-prox.c +++ b/drivers/iio/light/hid-sensor-prox.c @@ -240,6 +240,13 @@ static int prox_parse_report(struct platform_device *pdev, st->common_attributes.sensitivity.index, st->common_attributes.sensitivity.report_id); } + if (st->common_attributes.sensitivity.index < 0) + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS | + HID_USAGE_SENSOR_HUMAN_PRESENCE, + &st->common_attributes.sensitivity); + return ret; } diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c index f409c2047c05..0443fd2e8757 100644 --- a/drivers/iio/light/lm3533-als.c +++ b/drivers/iio/light/lm3533-als.c @@ -690,7 +690,7 @@ static struct attribute *lm3533_als_event_attributes[] = { NULL }; -static struct attribute_group lm3533_als_event_attribute_group = { +static const struct attribute_group lm3533_als_event_attribute_group = { .attrs = lm3533_als_event_attributes }; @@ -714,7 +714,7 @@ static struct attribute *lm3533_als_attributes[] = { NULL }; -static struct attribute_group lm3533_als_attribute_group = { +static const struct attribute_group lm3533_als_attribute_group = { .attrs = lm3533_als_attributes }; diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c index 04598ae993d4..e7d4ea75e007 100644 --- a/drivers/iio/light/tsl2563.c +++ b/drivers/iio/light/tsl2563.c @@ -884,9 +884,19 @@ static const struct i2c_device_id tsl2563_id[] = { }; MODULE_DEVICE_TABLE(i2c, tsl2563_id); +static const struct of_device_id tsl2563_of_match[] = { + { .compatible = "amstaos,tsl2560" }, + { .compatible = "amstaos,tsl2561" }, + { .compatible = "amstaos,tsl2562" }, + { .compatible = "amstaos,tsl2563" }, + {} +}; +MODULE_DEVICE_TABLE(of, tsl2563_of_match); + static struct i2c_driver tsl2563_i2c_driver = { .driver = { .name = "tsl2563", + .of_match_table = tsl2563_of_match, .pm = TSL2563_PM_OPS, }, .probe = tsl2563_probe, diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c index 18cf2e29e4d5..d571ad7291ed 100644 --- a/drivers/iio/light/us5182d.c +++ b/drivers/iio/light/us5182d.c @@ -972,10 +972,17 @@ static const struct i2c_device_id us5182d_id[] = { MODULE_DEVICE_TABLE(i2c, us5182d_id); +static const struct of_device_id us5182d_of_match[] = { + { .compatible = "upisemi,usd5182" }, + {} +}; +MODULE_DEVICE_TABLE(of, us5182d_of_match); + static struct i2c_driver us5182d_driver = { .driver = { .name = US5182D_DRV_NAME, .pm = &us5182d_pm_ops, + .of_match_table = us5182d_of_match, .acpi_match_table = ACPI_PTR(us5182d_acpi_match), }, .probe = us5182d_probe, diff --git a/drivers/iio/light/vl6180.c b/drivers/iio/light/vl6180.c new file mode 100644 index 000000000000..6e25b724d941 --- /dev/null +++ b/drivers/iio/light/vl6180.c @@ -0,0 +1,543 @@ +/* + * vl6180.c - Support for STMicroelectronics VL6180 ALS, range and proximity + * sensor + * + * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net> + * Copyright 2017 Manivannan Sadhasivam <manivannanece23@gmail.com> + * + * 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. + * + * IIO driver for VL6180 (7-bit I2C slave address 0x29) + * + * Range: 0 to 100mm + * ALS: < 1 Lux up to 100 kLux + * IR: 850nm + * + * TODO: irq, threshold events, continuous mode, hardware buffer + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1) /* continuous mode */ +#define VL6180_STARTSTOP BIT(0) /* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) + +/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0) + +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x63 /* 100 ms */ + +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data { + struct i2c_client *client; + struct mutex lock; +}; + +enum { VL6180_ALS, VL6180_RANGE, VL6180_PROX }; + +/** + * struct vl6180_chan_regs - Registers for accessing channels + * @drdy_mask: Data ready bit in status register + * @start_reg: Conversion start register + * @value_reg: Result value register + * @word: Register word length + */ +struct vl6180_chan_regs { + u8 drdy_mask; + u16 start_reg, value_reg; + bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS] = { + .drdy_mask = VL6180_ALS_READY, + .start_reg = VL6180_ALS_START, + .value_reg = VL6180_ALS_VALUE, + .word = true, + }, + [VL6180_RANGE] = { + .drdy_mask = VL6180_RANGE_READY, + .start_reg = VL6180_RANGE_START, + .value_reg = VL6180_RANGE_VALUE, + .word = false, + }, + [VL6180_PROX] = { + .drdy_mask = VL6180_RANGE_READY, + .start_reg = VL6180_RANGE_START, + .value_reg = VL6180_RANGE_RATE, + .word = true, + }, +}; + +static int vl6180_read(struct i2c_client *client, u16 cmd, void *databuf, + u8 len) +{ + __be16 cmdbuf = cpu_to_be16(cmd); + struct i2c_msg msgs[2] = { + { .addr = client->addr, .len = sizeof(cmdbuf), .buf = (u8 *) &cmdbuf }, + { .addr = client->addr, .len = len, .buf = databuf, + .flags = I2C_M_RD } }; + int ret; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + dev_err(&client->dev, "failed reading register 0x%04x\n", cmd); + + return ret; +} + +static int vl6180_read_byte(struct i2c_client *client, u16 cmd) +{ + u8 data; + int ret; + + ret = vl6180_read(client, cmd, &data, sizeof(data)); + if (ret < 0) + return ret; + + return data; +} + +static int vl6180_read_word(struct i2c_client *client, u16 cmd) +{ + __be16 data; + int ret; + + ret = vl6180_read(client, cmd, &data, sizeof(data)); + if (ret < 0) + return ret; + + return be16_to_cpu(data); +} + +static int vl6180_write_byte(struct i2c_client *client, u16 cmd, u8 val) +{ + u8 buf[3]; + struct i2c_msg msgs[1] = { + { .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } }; + int ret; + + buf[0] = cmd >> 8; + buf[1] = cmd & 0xff; + buf[2] = val; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) { + dev_err(&client->dev, "failed writing register 0x%04x\n", cmd); + return ret; + } + + return 0; +} + +static int vl6180_write_word(struct i2c_client *client, u16 cmd, u16 val) +{ + __be16 buf[2]; + struct i2c_msg msgs[1] = { + { .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } }; + int ret; + + buf[0] = cpu_to_be16(cmd); + buf[1] = cpu_to_be16(val); + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) { + dev_err(&client->dev, "failed writing register 0x%04x\n", cmd); + return ret; + } + + return 0; +} + +static int vl6180_measure(struct vl6180_data *data, int addr) +{ + struct i2c_client *client = data->client; + int tries = 20, ret; + u16 value; + + mutex_lock(&data->lock); + /* Start single shot measurement */ + ret = vl6180_write_byte(client, + vl6180_chan_regs_table[addr].start_reg, VL6180_STARTSTOP); + if (ret < 0) + goto fail; + + while (tries--) { + ret = vl6180_read_byte(client, VL6180_INTR_STATUS); + if (ret < 0) + goto fail; + + if (ret & vl6180_chan_regs_table[addr].drdy_mask) + break; + msleep(20); + } + + if (tries < 0) { + ret = -EIO; + goto fail; + } + + /* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? + vl6180_read_word(client, vl6180_chan_regs_table[addr].value_reg) : + vl6180_read_byte(client, vl6180_chan_regs_table[addr].value_reg); + if (ret < 0) + goto fail; + value = ret; + + /* Clear the interrupt flag after data read */ + ret = vl6180_write_byte(client, VL6180_INTR_CLEAR, + VL6180_CLEAR_ERROR | VL6180_CLEAR_ALS | VL6180_CLEAR_RANGE); + if (ret < 0) + goto fail; + + ret = value; + +fail: + mutex_unlock(&data->lock); + + return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type = IIO_LIGHT, + .address = VL6180_ALS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + }, { + .type = IIO_DISTANCE, + .address = VL6180_RANGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_PROXIMITY, + .address = VL6180_PROX, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +/* + * Columns 3 & 4 represent the same value in decimal and hex notations. + * Kept in order to avoid the datatype conversion while reading the + * hardware_gain. + */ +static const int vl6180_als_gain[8][4] = { + { 1, 0, 70, VL6180_ALS_GAIN_1 }, + { 1, 250000, 69, VL6180_ALS_GAIN_1_25 }, + { 1, 670000, 68, VL6180_ALS_GAIN_1_67 }, + { 2, 500000, 67, VL6180_ALS_GAIN_2_5 }, + { 5, 0, 66, VL6180_ALS_GAIN_5 }, + { 10, 0, 65, VL6180_ALS_GAIN_10 }, + { 20, 0, 64, VL6180_ALS_GAIN_20 }, + { 40, 0, 71, VL6180_ALS_GAIN_40 } +}; + +static int vl6180_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct vl6180_data *data = iio_priv(indio_dev); + int ret, i; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = vl6180_measure(data, chan->address); + if (ret < 0) + return ret; + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + ret = vl6180_read_word(data->client, VL6180_ALS_IT); + if (ret < 0) + return ret; + *val = 0; /* 1 count = 1ms (0 = 1ms) */ + *val2 = (ret + 1) * 1000; /* convert to seconds */ + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_LIGHT: + *val = 0; /* one ALS count is 0.32 Lux */ + *val2 = 320000; + break; + case IIO_DISTANCE: + *val = 0; /* sensor reports mm, scale to meter */ + *val2 = 1000; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = vl6180_read_byte(data->client, VL6180_ALS_GAIN); + if (ret < 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(vl6180_als_gain); i++) { + if (ret == vl6180_als_gain[i][2]) { + *val = vl6180_als_gain[i][0]; + *val2 = vl6180_als_gain[i][1]; + } + } + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); + +static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updating any config registers */ +static int vl6180_hold(struct vl6180_data *data, bool hold) +{ + return vl6180_write_byte(data->client, VL6180_HOLD, + hold ? VL6180_HOLD_ON : 0); +} + +static int vl6180_set_als_gain(struct vl6180_data *data, int val, int val2) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(vl6180_als_gain); i++) { + if (val == vl6180_als_gain[i][0] && + val2 == vl6180_als_gain[i][1]) { + mutex_lock(&data->lock); + ret = vl6180_hold(data, true); + if (ret < 0) + goto fail; + ret = vl6180_write_byte(data->client, VL6180_ALS_GAIN, + vl6180_als_gain[i][3]); +fail: + vl6180_hold(data, false); + mutex_unlock(&data->lock); + return ret; + } + } + + return -EINVAL; +} + +static int vl6180_set_it(struct vl6180_data *data, int val2) +{ + int ret; + + mutex_lock(&data->lock); + ret = vl6180_hold(data, true); + if (ret < 0) + goto fail; + ret = vl6180_write_word(data->client, VL6180_ALS_IT, + (val2 - 500) / 1000); /* write value in ms */ +fail: + vl6180_hold(data, false); + mutex_unlock(&data->lock); + + return ret; +} + +static int vl6180_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct vl6180_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0 || val2 < 500 || val2 >= 512500) + return -EINVAL; + + return vl6180_set_it(data, val2); + case IIO_CHAN_INFO_HARDWAREGAIN: + if (chan->type != IIO_LIGHT) + return -EINVAL; + + return vl6180_set_als_gain(data, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int vl6180_init(struct vl6180_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + ret = vl6180_read_byte(client, VL6180_MODEL_ID); + if (ret < 0) + return ret; + + if (ret != VL6180_MODEL_ID_VAL) { + dev_err(&client->dev, "invalid model ID %02x\n", ret); + return -ENODEV; + } + + ret = vl6180_hold(data, true); + if (ret < 0) + return ret; + + ret = vl6180_read_byte(client, VL6180_OUT_OF_RESET); + if (ret < 0) + return ret; + + /* + * Detect false reset condition here. This bit is always set when the + * system comes out of reset. + */ + if (ret != 0x01) + dev_info(&client->dev, "device is not fresh out of reset\n"); + + /* Enable ALS and Range ready interrupts */ + ret = vl6180_write_byte(client, VL6180_INTR_CONFIG, + VL6180_ALS_READY | VL6180_RANGE_READY); + if (ret < 0) + return ret; + + /* ALS integration time: 100ms */ + ret = vl6180_write_word(client, VL6180_ALS_IT, VL6180_ALS_IT_100); + if (ret < 0) + return ret; + + /* ALS gain: 1 */ + ret = vl6180_write_byte(client, VL6180_ALS_GAIN, VL6180_ALS_GAIN_1); + if (ret < 0) + return ret; + + ret = vl6180_write_byte(client, VL6180_OUT_OF_RESET, 0x00); + if (ret < 0) + return ret; + + return vl6180_hold(data, false); +} + +static int vl6180_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct vl6180_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels = ARRAY_SIZE(vl6180_channels); + indio_dev->name = VL6180_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = vl6180_init(data); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible = "st,vl6180", }, + { }, +}; +MODULE_DEVICE_TABLE(of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + { "vl6180", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name = VL6180_DRV_NAME, + .of_match_table = of_match_ptr(vl6180_of_match), + }, + .probe = vl6180_probe, + .id_table = vl6180_id, +}; + +module_i2c_driver(vl6180_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>"); +MODULE_AUTHOR("Manivannan Sadhasivam <manivannanece23@gmail.com>"); +MODULE_DESCRIPTION("STMicro VL6180 ALS, range and proximity sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/bmc150_magn_i2c.c b/drivers/iio/magnetometer/bmc150_magn_i2c.c index ee05722587aa..57e40dd1222e 100644 --- a/drivers/iio/magnetometer/bmc150_magn_i2c.c +++ b/drivers/iio/magnetometer/bmc150_magn_i2c.c @@ -63,9 +63,18 @@ static const struct i2c_device_id bmc150_magn_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, bmc150_magn_i2c_id); +static const struct of_device_id bmc150_magn_of_match[] = { + { .compatible = "bosch,bmc150_magn" }, + { .compatible = "bosch,bmc156_magn" }, + { .compatible = "bosch,bmm150_magn" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmc150_magn_of_match); + static struct i2c_driver bmc150_magn_driver = { .driver = { .name = "bmc150_magn_i2c", + .of_match_table = bmc150_magn_of_match, .acpi_match_table = ACPI_PTR(bmc150_magn_acpi_match), .pm = &bmc150_magn_pm_ops, }, diff --git a/drivers/iio/magnetometer/mag3110.c b/drivers/iio/magnetometer/mag3110.c index b4f643fb3b1e..dad8d57f7402 100644 --- a/drivers/iio/magnetometer/mag3110.c +++ b/drivers/iio/magnetometer/mag3110.c @@ -441,9 +441,16 @@ static const struct i2c_device_id mag3110_id[] = { }; MODULE_DEVICE_TABLE(i2c, mag3110_id); +static const struct of_device_id mag3110_of_match[] = { + { .compatible = "fsl,mag3110" }, + { } +}; +MODULE_DEVICE_TABLE(of, mag3110_of_match); + static struct i2c_driver mag3110_driver = { .driver = { .name = "mag3110", + .of_match_table = mag3110_of_match, .pm = MAG3110_PM_OPS, }, .probe = mag3110_probe, diff --git a/drivers/iio/potentiostat/lmp91000.c b/drivers/iio/potentiostat/lmp91000.c index e22714365022..afa8de3418d0 100644 --- a/drivers/iio/potentiostat/lmp91000.c +++ b/drivers/iio/potentiostat/lmp91000.c @@ -325,6 +325,7 @@ static int lmp91000_probe(struct i2c_client *client, indio_dev->channels = lmp91000_channels; indio_dev->num_channels = ARRAY_SIZE(lmp91000_channels); indio_dev->name = LMP91000_DRV_NAME; + indio_dev->dev.parent = &client->dev; indio_dev->modes = INDIO_DIRECT_MODE; i2c_set_clientdata(client, indio_dev); diff --git a/drivers/iio/pressure/hp03.c b/drivers/iio/pressure/hp03.c index ac76515d5d49..8c7b3ec3d84a 100644 --- a/drivers/iio/pressure/hp03.c +++ b/drivers/iio/pressure/hp03.c @@ -297,9 +297,16 @@ static const struct i2c_device_id hp03_id[] = { }; MODULE_DEVICE_TABLE(i2c, hp03_id); +static const struct of_device_id hp03_of_match[] = { + { .compatible = "hoperf,hp03" }, + { }, +}; +MODULE_DEVICE_TABLE(of, hp03_of_match); + static struct i2c_driver hp03_driver = { .driver = { .name = "hp03", + .of_match_table = hp03_of_match, }, .probe = hp03_probe, .remove = hp03_remove, diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c index 525644a7442d..619b963714c7 100644 --- a/drivers/iio/pressure/mpl3115.c +++ b/drivers/iio/pressure/mpl3115.c @@ -321,9 +321,16 @@ static const struct i2c_device_id mpl3115_id[] = { }; MODULE_DEVICE_TABLE(i2c, mpl3115_id); +static const struct of_device_id mpl3115_of_match[] = { + { .compatible = "fsl,mpl3115" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpl3115_of_match); + static struct i2c_driver mpl3115_driver = { .driver = { .name = "mpl3115", + .of_match_table = mpl3115_of_match, .pm = MPL3115_PM_OPS, }, .probe = mpl3115_probe, diff --git a/drivers/iio/pressure/zpa2326.c b/drivers/iio/pressure/zpa2326.c index c720c3ac0b9b..e58a0ad07477 100644 --- a/drivers/iio/pressure/zpa2326.c +++ b/drivers/iio/pressure/zpa2326.c @@ -751,7 +751,7 @@ static void zpa2326_suspend(struct iio_dev *indio_dev) */ static irqreturn_t zpa2326_handle_irq(int irq, void *data) { - struct iio_dev *indio_dev = (struct iio_dev *)data; + struct iio_dev *indio_dev = data; if (iio_buffer_enabled(indio_dev)) { /* Timestamping needed for buffered sampling only. */ @@ -790,7 +790,7 @@ static irqreturn_t zpa2326_handle_irq(int irq, void *data) */ static irqreturn_t zpa2326_handle_threaded_irq(int irq, void *data) { - struct iio_dev *indio_dev = (struct iio_dev *)data; + struct iio_dev *indio_dev = data; struct zpa2326_private *priv = iio_priv(indio_dev); unsigned int val; bool cont; diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig index ab96cb7a0054..5b81a8c9d438 100644 --- a/drivers/iio/proximity/Kconfig +++ b/drivers/iio/proximity/Kconfig @@ -32,6 +32,17 @@ config LIDAR_LITE_V2 To compile this driver as a module, choose M here: the module will be called pulsedlight-lite-v2 +config SRF04 + tristate "Devantech SRF04 ultrasonic ranger sensor" + depends on GPIOLIB + help + Say Y here to build a driver for Devantech SRF04 ultrasonic + ranger sensor. This driver can be used to measure the distance + of objects. It is using two GPIOs. + + To compile this driver as a module, choose M here: the + module will be called srf04. + config SX9500 tristate "SX9500 Semtech proximity sensor" select IIO_BUFFER diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile index e914c2a5dd49..ed1b6f4cc209 100644 --- a/drivers/iio/proximity/Makefile +++ b/drivers/iio/proximity/Makefile @@ -5,5 +5,6 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AS3935) += as3935.o obj-$(CONFIG_LIDAR_LITE_V2) += pulsedlight-lidar-lite-v2.o +obj-$(CONFIG_SRF04) += srf04.o obj-$(CONFIG_SRF08) += srf08.o obj-$(CONFIG_SX9500) += sx9500.o diff --git a/drivers/iio/proximity/as3935.c b/drivers/iio/proximity/as3935.c index 020459513384..ddf9bee89f77 100644 --- a/drivers/iio/proximity/as3935.c +++ b/drivers/iio/proximity/as3935.c @@ -154,7 +154,7 @@ static struct attribute *as3935_attributes[] = { NULL, }; -static struct attribute_group as3935_attribute_group = { +static const struct attribute_group as3935_attribute_group = { .attrs = as3935_attributes, }; diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c index 20c16a08c9d9..36c1ddc251aa 100644 --- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -278,6 +278,7 @@ static int lidar_probe(struct i2c_client *client, indio_dev->name = LIDAR_DRV_NAME; indio_dev->channels = lidar_channels; indio_dev->num_channels = ARRAY_SIZE(lidar_channels); + indio_dev->dev.parent = &client->dev; indio_dev->modes = INDIO_DIRECT_MODE; i2c_set_clientdata(client, indio_dev); diff --git a/drivers/iio/proximity/srf04.c b/drivers/iio/proximity/srf04.c new file mode 100644 index 000000000000..e37667f933b3 --- /dev/null +++ b/drivers/iio/proximity/srf04.c @@ -0,0 +1,304 @@ +/* + * SRF04: ultrasonic sensor for distance measuring by using GPIOs + * + * Copyright (c) 2017 Andreas Klinger <ak@it-klinger.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For details about the device see: + * http://www.robot-electronics.co.uk/htm/srf04tech.htm + * + * the measurement cycle as timing diagram looks like: + * + * +---+ + * GPIO | | + * trig: --+ +------------------------------------------------------ + * ^ ^ + * |<->| + * udelay(10) + * + * ultra +-+ +-+ +-+ + * sonic | | | | | | + * burst: ---------+ +-+ +-+ +----------------------------------------- + * . + * ultra . +-+ +-+ +-+ + * sonic . | | | | | | + * echo: ----------------------------------+ +-+ +-+ +---------------- + * . . + * +------------------------+ + * GPIO | | + * echo: -------------------+ +--------------- + * ^ ^ + * interrupt interrupt + * (ts_rising) (ts_falling) + * |<---------------------->| + * pulse time measured + * --> one round trip of ultra sonic waves + */ +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +struct srf04_data { + struct device *dev; + struct gpio_desc *gpiod_trig; + struct gpio_desc *gpiod_echo; + struct mutex lock; + int irqnr; + ktime_t ts_rising; + ktime_t ts_falling; + struct completion rising; + struct completion falling; +}; + +static irqreturn_t srf04_handle_irq(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct srf04_data *data = iio_priv(indio_dev); + ktime_t now = ktime_get(); + + if (gpiod_get_value(data->gpiod_echo)) { + data->ts_rising = now; + complete(&data->rising); + } else { + data->ts_falling = now; + complete(&data->falling); + } + + return IRQ_HANDLED; +} + +static int srf04_read(struct srf04_data *data) +{ + int ret; + ktime_t ktime_dt; + u64 dt_ns; + u32 time_ns, distance_mm; + + /* + * just one read-echo-cycle can take place at a time + * ==> lock against concurrent reading calls + */ + mutex_lock(&data->lock); + + reinit_completion(&data->rising); + reinit_completion(&data->falling); + + gpiod_set_value(data->gpiod_trig, 1); + udelay(10); + gpiod_set_value(data->gpiod_trig, 0); + + /* it cannot take more than 20 ms */ + ret = wait_for_completion_killable_timeout(&data->rising, HZ/50); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } else if (ret == 0) { + mutex_unlock(&data->lock); + return -ETIMEDOUT; + } + + ret = wait_for_completion_killable_timeout(&data->falling, HZ/50); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } else if (ret == 0) { + mutex_unlock(&data->lock); + return -ETIMEDOUT; + } + + ktime_dt = ktime_sub(data->ts_falling, data->ts_rising); + + mutex_unlock(&data->lock); + + dt_ns = ktime_to_ns(ktime_dt); + /* + * measuring more than 3 meters is beyond the capabilities of + * the sensor + * ==> filter out invalid results for not measuring echos of + * another us sensor + * + * formula: + * distance 3 m + * time = ---------- = --------- = 9404389 ns + * speed 319 m/s + * + * using a minimum speed at -20 °C of 319 m/s + */ + if (dt_ns > 9404389) + return -EIO; + + time_ns = dt_ns; + + /* + * the speed as function of the temperature is approximately: + * + * speed = 331,5 + 0,6 * Temp + * with Temp in °C + * and speed in m/s + * + * use 343 m/s as ultrasonic speed at 20 °C here in absence of the + * temperature + * + * therefore: + * time 343 + * distance = ------ * ----- + * 10^6 2 + * with time in ns + * and distance in mm (one way) + * + * because we limit to 3 meters the multiplication with 343 just + * fits into 32 bit + */ + distance_mm = time_ns * 343 / 2000000; + + return distance_mm; +} + +static int srf04_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long info) +{ + struct srf04_data *data = iio_priv(indio_dev); + int ret; + + if (channel->type != IIO_DISTANCE) + return -EINVAL; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = srf04_read(data); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * theoretical maximum resolution is 3 mm + * 1 LSB is 1 mm + */ + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info srf04_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = srf04_read_raw, +}; + +static const struct iio_chan_spec srf04_chan_spec[] = { + { + .type = IIO_DISTANCE, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int srf04_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct srf04_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct srf04_data)); + if (!indio_dev) { + dev_err(dev, "failed to allocate IIO device\n"); + return -ENOMEM; + } + + data = iio_priv(indio_dev); + data->dev = dev; + + mutex_init(&data->lock); + init_completion(&data->rising); + init_completion(&data->falling); + + data->gpiod_trig = devm_gpiod_get(dev, "trig", GPIOD_OUT_LOW); + if (IS_ERR(data->gpiod_trig)) { + dev_err(dev, "failed to get trig-gpios: err=%ld\n", + PTR_ERR(data->gpiod_trig)); + return PTR_ERR(data->gpiod_trig); + } + + data->gpiod_echo = devm_gpiod_get(dev, "echo", GPIOD_IN); + if (IS_ERR(data->gpiod_echo)) { + dev_err(dev, "failed to get echo-gpios: err=%ld\n", + PTR_ERR(data->gpiod_echo)); + return PTR_ERR(data->gpiod_echo); + } + + if (gpiod_cansleep(data->gpiod_echo)) { + dev_err(data->dev, "cansleep-GPIOs not supported\n"); + return -ENODEV; + } + + data->irqnr = gpiod_to_irq(data->gpiod_echo); + if (data->irqnr < 0) { + dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr); + return data->irqnr; + } + + ret = devm_request_irq(dev, data->irqnr, srf04_handle_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + pdev->name, indio_dev); + if (ret < 0) { + dev_err(data->dev, "request_irq: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = "srf04"; + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &srf04_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = srf04_chan_spec; + indio_dev->num_channels = ARRAY_SIZE(srf04_chan_spec); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id of_srf04_match[] = { + { .compatible = "devantech,srf04", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_srf04_match); + +static struct platform_driver srf04_driver = { + .probe = srf04_probe, + .driver = { + .name = "srf04-gpio", + .of_match_table = of_srf04_match, + }, +}; + +module_platform_driver(srf04_driver); + +MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); +MODULE_DESCRIPTION("SRF04 ultrasonic sensor for distance measuring using GPIOs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:srf04"); diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig index 3089e8d0a32d..5378976d6d27 100644 --- a/drivers/iio/temperature/Kconfig +++ b/drivers/iio/temperature/Kconfig @@ -19,6 +19,20 @@ config MAXIM_THERMOCOUPLE This driver can also be built as a module. If so, the module will be called maxim_thermocouple. +config HID_SENSOR_TEMP + tristate "HID Environmental temperature sensor" + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + help + Say yes here to build support for the HID SENSOR + temperature driver + + To compile this driver as a module, choose M here: the module + will be called hid-sensor-temperature. + config MLX90614 tristate "MLX90614 contact-less infrared sensor" depends on I2C diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile index 4c4377480726..ad1d668de546 100644 --- a/drivers/iio/temperature/Makefile +++ b/drivers/iio/temperature/Makefile @@ -2,6 +2,7 @@ # Makefile for industrial I/O temperature drivers # +obj-$(CONFIG_HID_SENSOR_TEMP) += hid-sensor-temperature.o obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o obj-$(CONFIG_MLX90614) += mlx90614.o obj-$(CONFIG_TMP006) += tmp006.o diff --git a/drivers/iio/temperature/hid-sensor-temperature.c b/drivers/iio/temperature/hid-sensor-temperature.c new file mode 100644 index 000000000000..c01efeca4002 --- /dev/null +++ b/drivers/iio/temperature/hid-sensor-temperature.c @@ -0,0 +1,311 @@ +/* + * HID Sensors Driver + * Copyright (c) 2017, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. + */ +#include <linux/device.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "../common/hid-sensors/hid-sensor-trigger.h" + +struct temperature_state { + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info temperature_attr; + s32 temperature_data; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +/* Channel definitions */ +static const struct iio_chan_spec temperature_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +/* Adjust channel real bits based on report descriptor */ +static void temperature_adjust_channel_bit_mask(struct iio_chan_spec *channels, + int channel, int size) +{ + channels[channel].scan_type.sign = 's'; + /* Real storage bits will change based on the report desc. */ + channels[channel].scan_type.realbits = size * 8; + /* Maximum size of a sample to capture is s32 */ + channels[channel].scan_type.storagebits = sizeof(s32) * 8; +} + +static int temperature_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct temperature_state *temp_st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_TEMP) + return -EINVAL; + hid_sensor_power_state( + &temp_st->common_attributes, true); + *val = sensor_hub_input_attr_get_raw_value( + temp_st->common_attributes.hsdev, + HID_USAGE_SENSOR_TEMPERATURE, + HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE, + temp_st->temperature_attr.report_id, + SENSOR_HUB_SYNC); + hid_sensor_power_state( + &temp_st->common_attributes, + false); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = temp_st->scale_pre_decml; + *val2 = temp_st->scale_post_decml; + return temp_st->scale_precision; + + case IIO_CHAN_INFO_OFFSET: + *val = temp_st->value_offset; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + return hid_sensor_read_samp_freq_value( + &temp_st->common_attributes, val, val2); + + case IIO_CHAN_INFO_HYSTERESIS: + return hid_sensor_read_raw_hyst_value( + &temp_st->common_attributes, val, val2); + default: + return -EINVAL; + } +} + +static int temperature_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct temperature_state *temp_st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return hid_sensor_write_samp_freq_value( + &temp_st->common_attributes, val, val2); + case IIO_CHAN_INFO_HYSTERESIS: + return hid_sensor_write_raw_hyst_value( + &temp_st->common_attributes, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info temperature_info = { + .driver_module = THIS_MODULE, + .read_raw = &temperature_read_raw, + .write_raw = &temperature_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int temperature_proc_event(struct hid_sensor_hub_device *hsdev, + unsigned int usage_id, void *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct temperature_state *temp_st = iio_priv(indio_dev); + + if (atomic_read(&temp_st->common_attributes.data_ready)) + iio_push_to_buffers_with_timestamp(indio_dev, + &temp_st->temperature_data, + iio_get_time_ns(indio_dev)); + + return 0; +} + +/* Capture samples in local storage */ +static int temperature_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned int usage_id, size_t raw_len, + char *raw_data, void *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct temperature_state *temp_st = iio_priv(indio_dev); + + switch (usage_id) { + case HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE: + temp_st->temperature_data = *(s32 *)raw_data; + return 0; + default: + return -EINVAL; + } +} + +/* Parse report which is specific to an usage id*/ +static int temperature_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + unsigned int usage_id, + struct temperature_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE, + &st->temperature_attr); + if (ret < 0) + return ret; + + temperature_adjust_channel_bit_mask(channels, 0, + st->temperature_attr.size); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_TEMPERATURE, + &st->temperature_attr, + &st->scale_pre_decml, &st->scale_post_decml); + + /* Set Sensitivity field ids, when there is no individual modifier */ + if (st->common_attributes.sensitivity.index < 0) + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS | + HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE, + &st->common_attributes.sensitivity); + + return ret; +} + +static struct hid_sensor_hub_callbacks temperature_callbacks = { + .send_event = &temperature_proc_event, + .capture_sample = &temperature_capture_sample, +}; + +/* Function to initialize the processing for usage id */ +static int hid_temperature_probe(struct platform_device *pdev) +{ + static const char *name = "temperature"; + struct iio_dev *indio_dev; + struct temperature_state *temp_st; + struct iio_chan_spec *temp_chans; + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*temp_st)); + if (!indio_dev) + return -ENOMEM; + + temp_st = iio_priv(indio_dev); + temp_st->common_attributes.hsdev = hsdev; + temp_st->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_TEMPERATURE, + &temp_st->common_attributes); + if (ret) + return ret; + + temp_chans = devm_kmemdup(&indio_dev->dev, temperature_channels, + sizeof(temperature_channels), GFP_KERNEL); + if (!temp_chans) + return -ENOMEM; + + ret = temperature_parse_report(pdev, hsdev, temp_chans, + HID_USAGE_SENSOR_TEMPERATURE, temp_st); + if (ret) + return ret; + + indio_dev->channels = temp_chans; + indio_dev->num_channels = ARRAY_SIZE(temperature_channels); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &temperature_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_triggered_buffer_setup(&pdev->dev, indio_dev, + &iio_pollfunc_store_time, NULL, NULL); + if (ret) + return ret; + + atomic_set(&temp_st->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, name, + &temp_st->common_attributes); + if (ret) + return ret; + + platform_set_drvdata(pdev, indio_dev); + + temperature_callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE, + &temperature_callbacks); + if (ret) + goto error_remove_trigger; + + ret = devm_iio_device_register(indio_dev->dev.parent, indio_dev); + if (ret) + goto error_remove_callback; + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE); +error_remove_trigger: + hid_sensor_remove_trigger(&temp_st->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_temperature_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct temperature_state *temp_st = iio_priv(indio_dev); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE); + hid_sensor_remove_trigger(&temp_st->common_attributes); + + return 0; +} + +static const struct platform_device_id hid_temperature_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200033", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_temperature_ids); + +static struct platform_driver hid_temperature_platform_driver = { + .id_table = hid_temperature_ids, + .driver = { + .name = "temperature-sensor", + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_temperature_probe, + .remove = hid_temperature_remove, +}; +module_platform_driver(hid_temperature_platform_driver); + +MODULE_DESCRIPTION("HID Environmental temperature sensor"); +MODULE_AUTHOR("Song Hongyan <hongyan.song@intel.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/temperature/maxim_thermocouple.c b/drivers/iio/temperature/maxim_thermocouple.c index f962f31a5eb2..557214202eff 100644 --- a/drivers/iio/temperature/maxim_thermocouple.c +++ b/drivers/iio/temperature/maxim_thermocouple.c @@ -231,6 +231,7 @@ static int maxim_thermocouple_probe(struct spi_device *spi) indio_dev->available_scan_masks = chip->scan_masks; indio_dev->num_channels = chip->num_channels; indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &spi->dev; data = iio_priv(indio_dev); data->spi = spi; diff --git a/drivers/iio/temperature/mlx90614.c b/drivers/iio/temperature/mlx90614.c index 4b645fc672aa..2077eef4095c 100644 --- a/drivers/iio/temperature/mlx90614.c +++ b/drivers/iio/temperature/mlx90614.c @@ -585,6 +585,12 @@ static const struct i2c_device_id mlx90614_id[] = { }; MODULE_DEVICE_TABLE(i2c, mlx90614_id); +static const struct of_device_id mlx90614_of_match[] = { + { .compatible = "melexis,mlx90614" }, + { } +}; +MODULE_DEVICE_TABLE(of, mlx90614_of_match); + #ifdef CONFIG_PM_SLEEP static int mlx90614_pm_suspend(struct device *dev) { @@ -644,6 +650,7 @@ static const struct dev_pm_ops mlx90614_pm_ops = { static struct i2c_driver mlx90614_driver = { .driver = { .name = "mlx90614", + .of_match_table = mlx90614_of_match, .pm = &mlx90614_pm_ops, }, .probe = mlx90614_probe, diff --git a/drivers/iio/temperature/tmp007.c b/drivers/iio/temperature/tmp007.c index f04d0d1f6ac8..0615324d054c 100644 --- a/drivers/iio/temperature/tmp007.c +++ b/drivers/iio/temperature/tmp007.c @@ -11,9 +11,10 @@ * * (7-bit I2C slave address (0x40 - 0x47), changeable via ADR pins) * - * Note: This driver assumes that the sensor has been calibrated beforehand - * - * TODO: ALERT irq, limit threshold events + * Note: + * 1. This driver assumes that the sensor has been calibrated beforehand + * 2. Limit threshold events are enabled at the start + * 3. Operating mode: INT * */ @@ -24,25 +25,38 @@ #include <linux/pm.h> #include <linux/bitops.h> #include <linux/of.h> +#include <linux/irq.h> +#include <linux/interrupt.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> +#include <linux/iio/events.h> #define TMP007_TDIE 0x01 #define TMP007_CONFIG 0x02 #define TMP007_TOBJECT 0x03 #define TMP007_STATUS 0x04 #define TMP007_STATUS_MASK 0x05 +#define TMP007_TOBJ_HIGH_LIMIT 0x06 +#define TMP007_TOBJ_LOW_LIMIT 0x07 +#define TMP007_TDIE_HIGH_LIMIT 0x08 +#define TMP007_TDIE_LOW_LIMIT 0x09 #define TMP007_MANUFACTURER_ID 0x1e #define TMP007_DEVICE_ID 0x1f #define TMP007_CONFIG_CONV_EN BIT(12) -#define TMP007_CONFIG_COMP_EN BIT(5) #define TMP007_CONFIG_TC_EN BIT(6) #define TMP007_CONFIG_CR_MASK GENMASK(11, 9) +#define TMP007_CONFIG_ALERT_EN BIT(8) #define TMP007_CONFIG_CR_SHIFT 9 +/* Status register flags */ +#define TMP007_STATUS_ALERT BIT(15) #define TMP007_STATUS_CONV_READY BIT(14) +#define TMP007_STATUS_OHF BIT(13) +#define TMP007_STATUS_OLF BIT(12) +#define TMP007_STATUS_LHF BIT(11) +#define TMP007_STATUS_LLF BIT(10) #define TMP007_STATUS_DATA_VALID BIT(9) #define TMP007_MANUFACTURER_MAGIC 0x5449 @@ -52,7 +66,9 @@ struct tmp007_data { struct i2c_client *client; + struct mutex lock; u16 config; + u16 status_mask; }; static const int tmp007_avgs[5][2] = { {4, 0}, {2, 0}, {1, 0}, @@ -156,6 +172,188 @@ static int tmp007_write_raw(struct iio_dev *indio_dev, return -EINVAL; } +static irqreturn_t tmp007_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct tmp007_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_word_swapped(data->client, TMP007_STATUS); + if ((ret < 0) || !(ret & (TMP007_STATUS_OHF | TMP007_STATUS_OLF | + TMP007_STATUS_LHF | TMP007_STATUS_LLF))) + return IRQ_NONE; + + if (ret & TMP007_STATUS_OHF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_OBJECT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + if (ret & TMP007_STATUS_OLF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_OBJECT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + + if (ret & TMP007_STATUS_LHF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_AMBIENT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + if (ret & TMP007_STATUS_LLF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_AMBIENT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static int tmp007_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, int state) +{ + struct tmp007_data *data = iio_priv(indio_dev); + unsigned int status_mask; + int ret; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + if (dir == IIO_EV_DIR_RISING) + status_mask = TMP007_STATUS_LHF; + else + status_mask = TMP007_STATUS_LLF; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + status_mask = TMP007_STATUS_OHF; + else + status_mask = TMP007_STATUS_OLF; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->lock); + ret = i2c_smbus_read_word_swapped(data->client, TMP007_STATUS_MASK); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + if (state) + ret |= status_mask; + else + ret &= ~status_mask; + + return i2c_smbus_write_word_swapped(data->client, TMP007_STATUS_MASK, + data->status_mask = ret); +} + +static int tmp007_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tmp007_data *data = iio_priv(indio_dev); + unsigned int mask; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + if (dir == IIO_EV_DIR_RISING) + mask = TMP007_STATUS_LHF; + else + mask = TMP007_STATUS_LLF; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + mask = TMP007_STATUS_OHF; + else + mask = TMP007_STATUS_OLF; + break; + default: + return -EINVAL; + } + + return !!(data->status_mask & mask); +} + +static int tmp007_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2) +{ + struct tmp007_data *data = iio_priv(indio_dev); + int ret; + u8 reg; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: /* LSB: 0.5 degree Celsius */ + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TDIE_HIGH_LIMIT; + else + reg = TMP007_TDIE_LOW_LIMIT; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TOBJ_HIGH_LIMIT; + else + reg = TMP007_TOBJ_LOW_LIMIT; + break; + default: + return -EINVAL; + } + + ret = i2c_smbus_read_word_swapped(data->client, reg); + if (ret < 0) + return ret; + + /* Shift length 7 bits = 6(15:6) + 1(0.5 LSB) */ + *val = sign_extend32(ret, 15) >> 7; + + return IIO_VAL_INT; +} + +static int tmp007_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int val, int val2) +{ + struct tmp007_data *data = iio_priv(indio_dev); + u8 reg; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TDIE_HIGH_LIMIT; + else + reg = TMP007_TDIE_LOW_LIMIT; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TOBJ_HIGH_LIMIT; + else + reg = TMP007_TOBJ_LOW_LIMIT; + break; + default: + return -EINVAL; + } + + /* Full scale threshold value is +/- 256 degree Celsius */ + if (val < -256 || val > 255) + return -EINVAL; + + /* Shift length 7 bits = 6(15:6) + 1(0.5 LSB) */ + return i2c_smbus_write_word_swapped(data->client, reg, (val << 7)); +} + static IIO_CONST_ATTR(sampling_frequency_available, "4 2 1 0.5 0.25"); static struct attribute *tmp007_attributes[] = { @@ -167,6 +365,36 @@ static const struct attribute_group tmp007_attribute_group = { .attrs = tmp007_attributes, }; +static const struct iio_event_spec tmp007_obj_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec tmp007_die_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + static const struct iio_chan_spec tmp007_channels[] = { { .type = IIO_TEMP, @@ -175,6 +403,8 @@ static const struct iio_chan_spec tmp007_channels[] = { .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = tmp007_die_event, + .num_event_specs = ARRAY_SIZE(tmp007_die_event), }, { .type = IIO_TEMP, @@ -183,12 +413,18 @@ static const struct iio_chan_spec tmp007_channels[] = { .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = tmp007_obj_event, + .num_event_specs = ARRAY_SIZE(tmp007_obj_event), } }; static const struct iio_info tmp007_info = { .read_raw = tmp007_read_raw, .write_raw = tmp007_write_raw, + .read_event_config = tmp007_read_event_config, + .write_event_config = tmp007_write_event_config, + .read_event_value = tmp007_read_thresh, + .write_event_value = tmp007_write_thresh, .attrs = &tmp007_attribute_group, .driver_module = THIS_MODULE, }; @@ -214,7 +450,6 @@ static int tmp007_probe(struct i2c_client *client, struct tmp007_data *data; struct iio_dev *indio_dev; int ret; - u16 status; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) return -EOPNOTSUPP; @@ -231,6 +466,7 @@ static int tmp007_probe(struct i2c_client *client, data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); data->client = client; + mutex_init(&data->lock); indio_dev->dev.parent = &client->dev; indio_dev->name = "tmp007"; @@ -243,7 +479,7 @@ static int tmp007_probe(struct i2c_client *client, /* * Set Configuration register: * 1. Conversion ON - * 2. Comparator mode + * 2. ALERT enable * 3. Transient correction enable */ @@ -252,7 +488,7 @@ static int tmp007_probe(struct i2c_client *client, return ret; data->config = ret; - data->config |= (TMP007_CONFIG_CONV_EN | TMP007_CONFIG_COMP_EN | TMP007_CONFIG_TC_EN); + data->config |= (TMP007_CONFIG_CONV_EN | TMP007_CONFIG_ALERT_EN | TMP007_CONFIG_TC_EN); ret = i2c_smbus_write_word_swapped(data->client, TMP007_CONFIG, data->config); @@ -260,22 +496,39 @@ static int tmp007_probe(struct i2c_client *client, return ret; /* + * Only the following flags can activate ALERT pin. Data conversion/validity flags + * flags can still be polled for getting temperature data + * * Set Status Mask register: - * 1. Conversion ready enable - * 2. Data valid enable + * 1. Object temperature high limit enable + * 2. Object temperature low limit enable + * 3. TDIE temperature high limit enable + * 4. TDIE temperature low limit enable */ ret = i2c_smbus_read_word_swapped(data->client, TMP007_STATUS_MASK); if (ret < 0) goto error_powerdown; - status = ret; - status |= (TMP007_STATUS_CONV_READY | TMP007_STATUS_DATA_VALID); + data->status_mask = ret; + data->status_mask |= (TMP007_STATUS_OHF | TMP007_STATUS_OLF + | TMP007_STATUS_LHF | TMP007_STATUS_LLF); - ret = i2c_smbus_write_word_swapped(data->client, TMP007_STATUS_MASK, status); + ret = i2c_smbus_write_word_swapped(data->client, TMP007_STATUS_MASK, data->status_mask); if (ret < 0) goto error_powerdown; + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, tmp007_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + tmp007_id->name, indio_dev); + if (ret) { + dev_err(&client->dev, "irq request error %d\n", -ret); + goto error_powerdown; + } + } + return iio_device_register(indio_dev); error_powerdown: diff --git a/drivers/iio/trigger/stm32-timer-trigger.c b/drivers/iio/trigger/stm32-timer-trigger.c index 5ee362bc618c..25248d644e7c 100644 --- a/drivers/iio/trigger/stm32-timer-trigger.c +++ b/drivers/iio/trigger/stm32-timer-trigger.c @@ -15,6 +15,7 @@ #include <linux/platform_device.h> #define MAX_TRIGGERS 6 +#define MAX_VALIDS 5 /* List the triggers created by each timer */ static const void *triggers_table[][MAX_TRIGGERS] = { @@ -32,12 +33,29 @@ static const void *triggers_table[][MAX_TRIGGERS] = { { TIM12_TRGO, TIM12_CH1, TIM12_CH2,}, }; +/* List the triggers accepted by each timer */ +static const void *valids_table[][MAX_VALIDS] = { + { TIM5_TRGO, TIM2_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM8_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM2_TRGO, TIM5_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM2_TRGO, TIM3_TRGO, TIM8_TRGO,}, + { TIM2_TRGO, TIM3_TRGO, TIM4_TRGO, TIM8_TRGO,}, + { }, /* timer 6 */ + { }, /* timer 7 */ + { TIM1_TRGO, TIM2_TRGO, TIM4_TRGO, TIM5_TRGO,}, + { TIM2_TRGO, TIM3_TRGO,}, + { }, /* timer 10 */ + { }, /* timer 11 */ + { TIM4_TRGO, TIM5_TRGO,}, +}; + struct stm32_timer_trigger { struct device *dev; struct regmap *regmap; struct clk *clk; u32 max_arr; const void *triggers; + const void *valids; }; static int stm32_timer_start(struct stm32_timer_trigger *priv, @@ -180,8 +198,7 @@ static ssize_t stm32_tt_show_master_mode(struct device *dev, struct device_attribute *attr, char *buf) { - struct iio_dev *indio_dev = dev_to_iio_dev(dev); - struct stm32_timer_trigger *priv = iio_priv(indio_dev); + struct stm32_timer_trigger *priv = dev_get_drvdata(dev); u32 cr2; regmap_read(priv->regmap, TIM_CR2, &cr2); @@ -194,8 +211,7 @@ static ssize_t stm32_tt_store_master_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { - struct iio_dev *indio_dev = dev_to_iio_dev(dev); - struct stm32_timer_trigger *priv = iio_priv(indio_dev); + struct stm32_timer_trigger *priv = dev_get_drvdata(dev); int i; for (i = 0; i < ARRAY_SIZE(master_mode_table); i++) { @@ -275,6 +291,286 @@ static int stm32_setup_iio_triggers(struct stm32_timer_trigger *priv) return 0; } +static int stm32_counter_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + { + u32 cnt; + + regmap_read(priv->regmap, TIM_CNT, &cnt); + *val = cnt; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + { + u32 smcr; + + regmap_read(priv->regmap, TIM_SMCR, &smcr); + smcr &= TIM_SMCR_SMS; + + *val = 1; + *val2 = 0; + + /* in quadrature case scale = 0.25 */ + if (smcr == 3) + *val2 = 2; + + return IIO_VAL_FRACTIONAL_LOG2; + } + } + + return -EINVAL; +} + +static int stm32_counter_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + regmap_write(priv->regmap, TIM_CNT, val); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* fixed scale */ + return -EINVAL; + } + + return -EINVAL; +} + +static const struct iio_info stm32_trigger_info = { + .driver_module = THIS_MODULE, + .read_raw = stm32_counter_read_raw, + .write_raw = stm32_counter_write_raw +}; + +static const char *const stm32_enable_modes[] = { + "always", + "gated", + "triggered", +}; + +static int stm32_enable_mode2sms(int mode) +{ + switch (mode) { + case 0: + return 0; + case 1: + return 5; + case 2: + return 6; + } + + return -EINVAL; +} + +static int stm32_set_enable_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + int sms = stm32_enable_mode2sms(mode); + + if (sms < 0) + return sms; + + regmap_update_bits(priv->regmap, TIM_SMCR, TIM_SMCR_SMS, sms); + + return 0; +} + +static int stm32_sms2enable_mode(int mode) +{ + switch (mode) { + case 0: + return 0; + case 5: + return 1; + case 6: + return 2; + } + + return -EINVAL; +} + +static int stm32_get_enable_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 smcr; + + regmap_read(priv->regmap, TIM_SMCR, &smcr); + smcr &= TIM_SMCR_SMS; + + return stm32_sms2enable_mode(smcr); +} + +static const struct iio_enum stm32_enable_mode_enum = { + .items = stm32_enable_modes, + .num_items = ARRAY_SIZE(stm32_enable_modes), + .set = stm32_set_enable_mode, + .get = stm32_get_enable_mode +}; + +static const char *const stm32_quadrature_modes[] = { + "channel_A", + "channel_B", + "quadrature", +}; + +static int stm32_set_quadrature_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + + regmap_update_bits(priv->regmap, TIM_SMCR, TIM_SMCR_SMS, mode + 1); + + return 0; +} + +static int stm32_get_quadrature_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 smcr; + + regmap_read(priv->regmap, TIM_SMCR, &smcr); + smcr &= TIM_SMCR_SMS; + + return smcr - 1; +} + +static const struct iio_enum stm32_quadrature_mode_enum = { + .items = stm32_quadrature_modes, + .num_items = ARRAY_SIZE(stm32_quadrature_modes), + .set = stm32_set_quadrature_mode, + .get = stm32_get_quadrature_mode +}; + +static const char *const stm32_count_direction_states[] = { + "up", + "down" +}; + +static int stm32_set_count_direction(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_DIR, mode); + + return 0; +} + +static int stm32_get_count_direction(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 cr1; + + regmap_read(priv->regmap, TIM_CR1, &cr1); + + return (cr1 & TIM_CR1_DIR); +} + +static const struct iio_enum stm32_count_direction_enum = { + .items = stm32_count_direction_states, + .num_items = ARRAY_SIZE(stm32_count_direction_states), + .set = stm32_set_count_direction, + .get = stm32_get_count_direction +}; + +static ssize_t stm32_count_get_preset(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 arr; + + regmap_read(priv->regmap, TIM_ARR, &arr); + + return snprintf(buf, PAGE_SIZE, "%u\n", arr); +} + +static ssize_t stm32_count_set_preset(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + unsigned int preset; + int ret; + + ret = kstrtouint(buf, 0, &preset); + if (ret) + return ret; + + regmap_write(priv->regmap, TIM_ARR, preset); + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE, TIM_CR1_ARPE); + + return len; +} + +static const struct iio_chan_spec_ext_info stm32_trigger_count_info[] = { + { + .name = "preset", + .shared = IIO_SEPARATE, + .read = stm32_count_get_preset, + .write = stm32_count_set_preset + }, + IIO_ENUM("count_direction", IIO_SEPARATE, &stm32_count_direction_enum), + IIO_ENUM_AVAILABLE("count_direction", &stm32_count_direction_enum), + IIO_ENUM("quadrature_mode", IIO_SEPARATE, &stm32_quadrature_mode_enum), + IIO_ENUM_AVAILABLE("quadrature_mode", &stm32_quadrature_mode_enum), + IIO_ENUM("enable_mode", IIO_SEPARATE, &stm32_enable_mode_enum), + IIO_ENUM_AVAILABLE("enable_mode", &stm32_enable_mode_enum), + {} +}; + +static const struct iio_chan_spec stm32_trigger_channel = { + .type = IIO_COUNT, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .ext_info = stm32_trigger_count_info, + .indexed = 1 +}; + +static struct stm32_timer_trigger *stm32_setup_counter_device(struct device *dev) +{ + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, + sizeof(struct stm32_timer_trigger)); + if (!indio_dev) + return NULL; + + indio_dev->name = dev_name(dev); + indio_dev->dev.parent = dev; + indio_dev->info = &stm32_trigger_info; + indio_dev->num_channels = 1; + indio_dev->channels = &stm32_trigger_channel; + indio_dev->dev.of_node = dev->of_node; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return NULL; + + return iio_priv(indio_dev); +} + /** * is_stm32_timer_trigger * @trig: trigger to be checked @@ -299,10 +595,15 @@ static int stm32_timer_trigger_probe(struct platform_device *pdev) if (of_property_read_u32(dev->of_node, "reg", &index)) return -EINVAL; - if (index >= ARRAY_SIZE(triggers_table)) + if (index >= ARRAY_SIZE(triggers_table) || + index >= ARRAY_SIZE(valids_table)) return -EINVAL; - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + /* Create an IIO device only if we have triggers to be validated */ + if (*valids_table[index]) + priv = stm32_setup_counter_device(dev); + else + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -312,6 +613,7 @@ static int stm32_timer_trigger_probe(struct platform_device *pdev) priv->clk = ddata->clk; priv->max_arr = ddata->max_arr; priv->triggers = triggers_table[index]; + priv->valids = valids_table[index]; ret = stm32_setup_iio_triggers(priv); if (ret) |