From feda2840040fcf87d826b0aec945a98222e656ba Mon Sep 17 00:00:00 2001 From: Andreas Klinger Date: Tue, 31 Jan 2017 19:03:45 +0100 Subject: iio: distance: add devantech us ranger srf04 This patch adds support for the ultrasonic ranger srf04 of devantech. This device is measuring the distance of objects in a range between 1 cm and 3 meters and a theoretical resolution of 3 mm. There are two GPIOs used: - trigger: set as output to the device when the measurement should start - echo: set by the device when the ultrasonic wave is sent out and reset when the echo is recognized; this needs to be an interrupt input The time between setting and resetting the echo pin is the time the waveform needed for one round trip. This time is recorded in the interrupt handler. The distance is calculated in the read function by using the ultrasonic speed at 20 degrees celsius which is about 343 m/s. Signed-off-by: Andreas Klinger Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/Kconfig | 11 ++ drivers/iio/proximity/Makefile | 1 + drivers/iio/proximity/srf04.c | 304 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 316 insertions(+) create mode 100644 drivers/iio/proximity/srf04.c (limited to 'drivers/iio') 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/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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 "); +MODULE_DESCRIPTION("SRF04 ultrasonic sensor for distance measuring using GPIOs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:srf04"); -- cgit v1.2.1 From d068e4a0f9213074c5d893b8b80ca7bca444ed1d Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 29 Jan 2017 11:49:24 +0100 Subject: iio: imu: st_lsm6dsx: add support to multiple devices with the same settings Add capability to support multiple devices with the same st_lsm6dsx_settings like LSM6DSM/LSM6DSL Signed-off-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 3 ++- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 30 +++++++++++++++------------- 2 files changed, 18 insertions(+), 15 deletions(-) (limited to 'drivers/iio') diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 69deafe1c10d..2c1bb9e58d01 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -20,6 +20,7 @@ enum st_lsm6dsx_hw_id { ST_LSM6DS3_ID, ST_LSM6DSM_ID, + ST_LSM6DSX_MAX_ID, }; #define ST_LSM6DSX_CHAN_SIZE 2 @@ -50,7 +51,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 { diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index c92ddcc190e2..499e60af8fa7 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -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,18 @@ 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 = 0x6a, + .max_fifo_size = 4096, + .id = { + [0] = ST_LSM6DSM_ID, + }, }, }; @@ -241,11 +239,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; } -- cgit v1.2.1 From 0b2a3e5f17d6ba3f00aee1000718a84afb3d4d1f Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 29 Jan 2017 11:49:25 +0100 Subject: iio: imu: st_lsm6dsx: add support to lsm6dsl Add support to STM LSM6DSL 6-axis (acc + gyro) Mems sensor http://www.st.com/resource/en/datasheet/lsm6dsl.pdf Signed-off-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/Kconfig | 2 +- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 2 ++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c | 4 ++-- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 5 +++-- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c | 5 +++++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c | 5 +++++ 6 files changed, 18 insertions(+), 5 deletions(-) (limited to 'drivers/iio') diff --git a/drivers/iio/imu/st_lsm6dsx/Kconfig b/drivers/iio/imu/st_lsm6dsx/Kconfig index 935d4cd071a3..d264783bfffd 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, 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 2c1bb9e58d01..e0ece13974e5 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -15,10 +15,12 @@ #include #define ST_LSM6DS3_DEV_NAME "lsm6ds3" +#define ST_LSM6DSL_DEV_NAME "lsm6dsl" #define ST_LSM6DSM_DEV_NAME "lsm6dsm" enum st_lsm6dsx_hw_id { ST_LSM6DS3_ID, + ST_LSM6DSL_ID, ST_LSM6DSM_ID, ST_LSM6DSX_MAX_ID, }; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c index 78532ce07449..73a6492ed763 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c @@ -1,7 +1,7 @@ /* * STMicroelectronics st_lsm6dsx FIFO buffer library driver * - * LSM6DS3/LSM6DSM: The FIFO buffer can be configured to store data + * LSM6DS3/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 @@ -206,7 +206,7 @@ out: } /** - * st_lsm6dsx_read_fifo() - LSM6DS3-LSM6DSM read FIFO routine + * st_lsm6dsx_read_fifo() - LSM6DS3-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. diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 499e60af8fa7..5d8d5cda33c4 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: + * - 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 @@ -168,7 +168,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .wai = 0x6a, .max_fifo_size = 4096, .id = { - [0] = ST_LSM6DSM_ID, + [0] = ST_LSM6DSL_ID, + [1] = ST_LSM6DSM_ID, }, }, }; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c index ea3041186e1e..60a3de043ae0 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c @@ -70,6 +70,10 @@ static const struct of_device_id st_lsm6dsx_i2c_of_match[] = { .compatible = "st,lsm6ds3", .data = (void *)ST_LSM6DS3_ID, }, + { + .compatible = "st,lsm6dsl", + .data = (void *)ST_LSM6DSL_ID, + }, { .compatible = "st,lsm6dsm", .data = (void *)ST_LSM6DSM_ID, @@ -80,6 +84,7 @@ 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_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..39edb40cc5cf 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c @@ -87,6 +87,10 @@ static const struct of_device_id st_lsm6dsx_spi_of_match[] = { .compatible = "st,lsm6ds3", .data = (void *)ST_LSM6DS3_ID, }, + { + .compatible = "st,lsm6dsl", + .data = (void *)ST_LSM6DSL_ID, + }, { .compatible = "st,lsm6dsm", .data = (void *)ST_LSM6DSM_ID, @@ -97,6 +101,7 @@ 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_LSM6DSL_DEV_NAME, ST_LSM6DSL_ID }, { ST_LSM6DSM_DEV_NAME, ST_LSM6DSM_ID }, {}, }; -- cgit v1.2.1 From df47710a6e4a44bbc5f0c40e7d4058147bfc12ed Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 29 Jan 2017 11:49:27 +0100 Subject: iio: imu: st_lsm6dsx: add support to lsm6ds3h Add support to STM LSM6DS3H 6-axis (acc + gyro) Mems sensor http://www.st.com/resource/en/datasheet/lsm6ds3h.pdf Signed-off-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/Kconfig | 2 +- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 2 ++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c | 9 +++++---- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 9 ++++++++- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c | 5 +++++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c | 5 +++++ 6 files changed, 26 insertions(+), 6 deletions(-) (limited to 'drivers/iio') diff --git a/drivers/iio/imu/st_lsm6dsx/Kconfig b/drivers/iio/imu/st_lsm6dsx/Kconfig index d264783bfffd..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, lsm6dsl, 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 e0ece13974e5..6a9849e6b30a 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -15,11 +15,13 @@ #include #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, diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c index 73a6492ed763..e71eccedd340 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/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): + * 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-LSM6DSL-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. diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 5d8d5cda33c4..c4332232ad71 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 * - * - LSM6DSL/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 @@ -164,6 +164,13 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { [0] = ST_LSM6DS3_ID, }, }, + { + .wai = 0x69, + .max_fifo_size = 4096, + .id = { + [0] = ST_LSM6DS3H_ID, + }, + }, { .wai = 0x6a, .max_fifo_size = 4096, diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c index 60a3de043ae0..2e4ed26fcbbd 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c @@ -70,6 +70,10 @@ static const struct of_device_id st_lsm6dsx_i2c_of_match[] = { .compatible = "st,lsm6ds3", .data = (void *)ST_LSM6DS3_ID, }, + { + .compatible = "st,lsm6ds3h", + .data = (void *)ST_LSM6DS3H_ID, + }, { .compatible = "st,lsm6dsl", .data = (void *)ST_LSM6DSL_ID, @@ -84,6 +88,7 @@ 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 39edb40cc5cf..1bf4a582e6cf 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c @@ -87,6 +87,10 @@ static const struct of_device_id st_lsm6dsx_spi_of_match[] = { .compatible = "st,lsm6ds3", .data = (void *)ST_LSM6DS3_ID, }, + { + .compatible = "st,lsm6ds3h", + .data = (void *)ST_LSM6DS3H_ID, + }, { .compatible = "st,lsm6dsl", .data = (void *)ST_LSM6DSL_ID, @@ -101,6 +105,7 @@ 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 }, {}, -- cgit v1.2.1 From 48458b0c196c6d6224aac5e1178a561e89011724 Mon Sep 17 00:00:00 2001 From: Gwendal Grignou Date: Mon, 30 Jan 2017 18:07:53 +0100 Subject: iio: cros_ec_light_prox: add ChromeOS EC Light and Proximity Sensors Handle Light and Proximity sensors presented by the ChromeOS EC Sensor hub. Creates an IIO device for each functions. Signed-off-by: Gwendal Grignou Signed-off-by: Guenter Roeck Signed-off-by: Enric Balletbo i Serra Signed-off-by: Jonathan Cameron --- drivers/iio/light/Kconfig | 10 ++ drivers/iio/light/Makefile | 1 + drivers/iio/light/cros_ec_light_prox.c | 289 +++++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 drivers/iio/light/cros_ec_light_prox.c (limited to 'drivers/iio') diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 5f731ead9d46..2afcbacc0f7e 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 diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index c13a2399e6df..edfd69b6dc6d 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); -- cgit v1.2.1 From c62360753e469a4cc5f4629c4ccb090985b33d6e Mon Sep 17 00:00:00 2001 From: Eva Rachel Retuya Date: Tue, 31 Jan 2017 15:12:56 +0800 Subject: iio: accel: Add driver for the Analog Devices ADXL345 3-axis accelerometer Add basic IIO support for the Analog Devices ADXL345 3-axis accelerometer. The datasheet can be found here: http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf Signed-off-by: Eva Rachel Retuya Cc: Michael Hennerich Cc: Daniel Baluta Cc: Alison Schofield Signed-off-by: Jonathan Cameron --- drivers/iio/accel/Kconfig | 11 +++ drivers/iio/accel/Makefile | 1 + drivers/iio/accel/adxl345.c | 194 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 drivers/iio/accel/adxl345.c (limited to 'drivers/iio') diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index ef8401ac1141..2308bac21c17 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -5,6 +5,17 @@ menu "Accelerometers" +config ADXL345 + tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer Driver" + depends on !(INPUT_ADXL34X=y || INPUT_ADXL34X=m) + depends on 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. + 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..618488de1161 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -3,6 +3,7 @@ # # When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ADXL345) += adxl345.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.c b/drivers/iio/accel/adxl345.c new file mode 100644 index 000000000000..c34991fd5c51 --- /dev/null +++ b/drivers/iio/accel/adxl345.c @@ -0,0 +1,194 @@ +/* + * ADXL345 3-Axis Digital Accelerometer + * + * Copyright (c) 2017 Eva Rachel Retuya + * + * 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 ADXL345 + * 7-bit I2C slave address: 0x1D (ALT ADDRESS pin tied to VDDIO) or + * 0x53 (ALT ADDRESS pin grounded) + */ + +#include +#include + +#include + +#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 i2c_client *client; + 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); + 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 = i2c_smbus_read_word_data(data->client, chan->address); + if (ret < 0) + return ret; + + *val = sign_extend32(ret, 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, +}; + +static int adxl345_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adxl345_data *data; + struct iio_dev *indio_dev; + int ret; + + ret = i2c_smbus_read_byte_data(client, ADXL345_REG_DEVID); + if (ret < 0) { + dev_err(&client->dev, "Error reading device ID: %d\n", ret); + return ret; + } + + if (ret != ADXL345_DEVID) { + dev_err(&client->dev, "Invalid device ID: %d\n", ret); + return -ENODEV; + } + + 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; + /* Enable full-resolution mode */ + data->data_range = ADXL345_DATA_FORMAT_FULL_RES; + + ret = i2c_smbus_write_byte_data(data->client, ADXL345_REG_DATA_FORMAT, + data->data_range); + if (ret < 0) { + dev_err(&client->dev, "Failed to set data range: %d\n", ret); + return ret; + } + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->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 = i2c_smbus_write_byte_data(data->client, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_MEASURE); + if (ret < 0) { + dev_err(&client->dev, "Failed to enable measurement mode: %d\n", + ret); + return ret; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "iio_device_register failed: %d\n", ret); + i2c_smbus_write_byte_data(data->client, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_STANDBY); + } + + return ret; +} + +static int adxl345_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct adxl345_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + return i2c_smbus_write_byte_data(data->client, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_STANDBY); +} + +static const struct i2c_device_id adxl345_i2c_id[] = { + { "adxl345", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, adxl345_i2c_id); + +static struct i2c_driver adxl345_driver = { + .driver = { + .name = "adxl345", + }, + .probe = adxl345_probe, + .remove = adxl345_remove, + .id_table = adxl345_i2c_id, +}; + +module_i2c_driver(adxl345_driver); + +MODULE_AUTHOR("Eva Rachel Retuya "); +MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.1 From b3c590ce14b1f30f4535e4f4ab9f3b9a2c968aaf Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Sat, 4 Feb 2017 02:22:51 -0800 Subject: iio: health: add MAX30102 oximeter driver support MAX30102 is an heart rate and pulse oximeter sensor that works using two LEDS of different wavelengths, and detecting the light reflected back. This patchset adds support for both IR and RED LED channels which can be processed in userspace to determine heart rate and blood oxygen levels. The MAX30102 part isn't completely register and functional compatible with the existing MAX30100 driver. Signed-off-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- drivers/iio/health/Kconfig | 13 ++ drivers/iio/health/Makefile | 1 + drivers/iio/health/max30102.c | 485 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 499 insertions(+) create mode 100644 drivers/iio/health/max30102.c (limited to 'drivers/iio') 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/max30102.c b/drivers/iio/health/max30102.c new file mode 100644 index 000000000000..377518c15552 --- /dev/null +++ b/drivers/iio/health/max30102.c @@ -0,0 +1,485 @@ +/* + * max30102.c - Support for MAX30102 heart rate and pulse oximeter sensor + * + * Copyright (C) 2017 Matt Ranostay + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + + 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 "); +MODULE_DESCRIPTION("MAX30102 heart rate and pulse oximeter sensor"); +MODULE_LICENSE("GPL"); -- cgit v1.2.1 From 7c5bc7e87cc43a45fd34987d0f48637ad4881eb0 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Mon, 23 Jan 2017 21:25:16 +0530 Subject: iio:temperature:tmp007: Add irq and threshold events support This patch adds ALERT irq and limit threshold events support for TI TMP007 - 16 bit IR thermopile sensor with integrated math engine. Following threshold events are supported: 1. TObj high limit 2. TObj low limit 3. TDie high limit 4. TDie low limit Signed-off-by: Manivannan Sadhasivam Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/tmp007.c | 277 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 265 insertions(+), 12 deletions(-) (limited to 'drivers/iio') 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 #include #include +#include +#include #include #include +#include #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: -- cgit v1.2.1 From af8f651b399798c4599e4994708afcf1f206120e Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 5 Feb 2017 12:34:00 +0000 Subject: staging:iio:adc:spear Move out of staging. There are some unanswered questions due to disagreements between the code and various datasheets (including between different datasheets for the same part). I don't think that is necessarily a reason to keep it in staging however. I'm partly posting this patch inorder to reignite debate and with a bit of luck find someone who has one of these to test! Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 11 ++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/spear_adc.c | 395 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 407 insertions(+) create mode 100644 drivers/iio/adc/spear_adc.c (limited to 'drivers/iio') diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index dedae7adbce9..789a5fb7c0be 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -494,6 +494,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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d0012620cd1c..19e48af43b97 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.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_STM32_ADC_CORE) += stm32-adc-core.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o 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 + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* 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 "); +MODULE_DESCRIPTION("SPEAr ADC driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.1 From 0097e20e7771c482c07cdf397e2a6b9e1154fad8 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 5 Feb 2017 13:07:00 +0000 Subject: staging:iio:adc:lpc32xx Move out of staging. There are a few more little cleanups that could be done on this driver, but I don't think any are sufficient to justify not moving it out of staging. It's a very simple driver (presumably for a simple part) so not much that can go wrong. I think it was only ever in staging because that's where IIO was as a whole at the time and then we forgot about it! Signed-off-by: Jonathan Cameron Cc: Roland Stigge Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 12 +++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/lpc32xx_adc.c | 219 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+) create mode 100644 drivers/iio/adc/lpc32xx_adc.c (limited to 'drivers/iio') diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 789a5fb7c0be..d777a972586d 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -305,6 +305,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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 19e48af43b97..b11bb5767543 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -30,6 +30,7 @@ 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_MAX1027) += max1027.o obj-$(CONFIG_MAX11100) += max11100.o 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * 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 "); +MODULE_DESCRIPTION("LPC32XX ADC driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.1 From c5c7d1f34c333d0a14f1f80d3d46eed4f338211e Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Feb 2017 13:34:14 +0100 Subject: iio: stx104: Set parent device Initialize the parent of the IIO device to the device that registered it. This makes sure that the IIO device appears the right level in the device hierarchy. Signed-off-by: Lars-Peter Clausen Acked-by: William Breathitt Gray --- drivers/iio/adc/stx104.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/iio') 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]; -- cgit v1.2.1 From ed165e91719b2c99336786d721d8ae30700c62cb Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Feb 2017 13:34:15 +0100 Subject: iio: quad-8: Set parent device Initialize the parent of the IIO device to the device that registered it. This makes sure that the IIO device appears the right level in the device hierarchy. Signed-off-by: Lars-Peter Clausen Acked-by: William Breathitt Gray Signed-off-by: Jonathan Cameron --- drivers/iio/counter/104-quad-8.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/iio') diff --git a/drivers/iio/counter/104-quad-8.c b/drivers/iio/counter/104-quad-8.c index a5913e97945e..e381ca66a92c 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]; -- cgit v1.2.1 From 83535359c4302cb0f928eb931b7b1bc0a21a66c5 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Feb 2017 13:34:16 +0100 Subject: iio: cio-dac: Set parent device Initialize the parent of the IIO device to the device that registered it. This makes sure that the IIO device appears the right level in the device hierarchy. Signed-off-by: Lars-Peter Clausen Acked-by: William Breathitt Gray Signed-off-by: Jonathan Cameron --- drivers/iio/dac/cio-dac.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/iio') 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]; -- cgit v1.2.1 From 5d548b733eb4b5cfb03e1acbcbfafca97455132c Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Feb 2017 13:34:17 +0100 Subject: iio: max30100: Set parent device Initialize the parent of the IIO device to the device that registered it. This makes sure that the IIO device appears the right level in the device hierarchy. Signed-off-by: Lars-Peter Clausen Signed-off-by: Jonathan Cameron --- drivers/iio/health/max30100.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/iio') diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c index 9648c69ea1a2..10b3e0d57c4a 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; -- cgit v1.2.1 From fa722499bbf89b5976bdfc77139f7d623a69a18a Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Feb 2017 13:34:18 +0100 Subject: iio: max30102: Set parent device Initialize the parent of the IIO device to the device that registered it. This makes sure that the IIO device appears the right level in the device hierarchy. Signed-off-by: Lars-Peter Clausen Signed-off-by: Jonathan Cameron --- drivers/iio/health/max30102.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/iio') diff --git a/drivers/iio/health/max30102.c b/drivers/iio/health/max30102.c index 377518c15552..839b875c29b9 100644 --- a/drivers/iio/health/max30102.c +++ b/drivers/iio/health/max30102.c @@ -410,6 +410,7 @@ static int max30102_probe(struct i2c_client *client, 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; -- cgit v1.2.1 From e78c1f5710bc1a394d17850a50de0429aa5e2938 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Feb 2017 13:34:19 +0100 Subject: iio: lmp91000: Set parent device Initialize the parent of the IIO device to the device that registered it. This makes sure that the IIO device appears the right level in the device hierarchy. Signed-off-by: Lars-Peter Clausen Reviewed-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- drivers/iio/potentiostat/lmp91000.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/iio') 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); -- cgit v1.2.1 From b2027d16df9194ec22eb5d315776532a7476bdb1 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Feb 2017 13:34:20 +0100 Subject: iio: pulsedlight-lidar-lite-v2: Set parent device Initialize the parent of the IIO device to the device that registered it. This makes sure that the IIO device appears the right level in the device hierarchy. Signed-off-by: Lars-Peter Clausen Reviewed-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/pulsedlight-lidar-lite-v2.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/iio') 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); -- cgit v1.2.1 From 74e2419b00d870b373ada4878e3d66d8dc97f4b0 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 11 Feb 2017 13:34:21 +0100 Subject: iio: maxim_thermocouple: Set parent device Initialize the parent of the IIO device to the device that registered it. This makes sure that the IIO device appears the right level in the device hierarchy. Signed-off-by: Lars-Peter Clausen Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/maxim_thermocouple.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/iio') 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; -- cgit v1.2.1 From 3af109131b7eb880ba0711344912d7a523a812b4 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 15 Feb 2017 20:31:45 +0100 Subject: iio: adc: meson-saradc: switch from polling to interrupt mode Switch from polling to interrupt mode. Successfully tested on a S905GXBB-based Odroid C2. Signed-off-by: Heiner Kallweit Acked-by: Martin Blumenstingl Tested-by: Martin Blumenstingl Signed-off-by: Jonathan Cameron --- drivers/iio/adc/meson_saradc.c | 56 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) (limited to 'drivers/iio') diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c index 89def6034f40..9f186de24cd7 100644 --- a/drivers/iio/adc/meson_saradc.c +++ b/drivers/iio/adc/meson_saradc.c @@ -18,7 +18,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -163,6 +165,7 @@ #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 */ #define MESON_SAR_ADC_CHAN(_chan) { \ .type = IIO_VOLTAGE, \ @@ -229,6 +232,7 @@ struct meson_sar_adc_priv { struct clk_gate clk_gate; struct clk *adc_div_clk; struct clk_divider clk_div; + struct completion done; }; static const struct regmap_config meson_sar_adc_regmap_config = { @@ -274,11 +278,11 @@ 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, sum = 0, count = 0; - ret = meson_sar_adc_wait_busy_clear(indio_dev); - if (ret) - return ret; + if(!wait_for_completion_timeout(&priv->done, + msecs_to_jiffies(MESON_SAR_ADC_TIMEOUT))) + return -ETIMEDOUT; while (meson_sar_adc_get_fifo_count(indio_dev) > 0 && count < MESON_SAR_ADC_MAX_FIFO_SIZE) { @@ -378,6 +382,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); @@ -391,6 +401,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); @@ -643,6 +656,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 +681,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 +745,25 @@ 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 const struct iio_info meson_sar_adc_iio_info = { .read_raw = meson_sar_adc_iio_info_read_raw, .driver_module = THIS_MODULE, @@ -770,7 +806,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 +815,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 +834,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)) -- cgit v1.2.1 From 6a882a2cbeb09b7c32610a644d5915d17b80b617 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 15 Feb 2017 20:31:55 +0100 Subject: iio: adc: meson-saradc: improve meson_sar_adc_read_raw_sample After sampling there should always be only one value in the FIFO. This also applies to averaging mode as the averaging is done chip-internally. So we don't have to loop and let the driver complain if there's not exactly one value in the FIFO. If the value belongs to a different channel then don't silently swallow the value but complain. Signed-off-by: Heiner Kallweit Acked-by: Martin Blumenstingl Signed-off-by: Jonathan Cameron --- drivers/iio/adc/meson_saradc.c | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) (limited to 'drivers/iio') diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c index 9f186de24cd7..cde9ca7a01b8 100644 --- a/drivers/iio/adc/meson_saradc.c +++ b/drivers/iio/adc/meson_saradc.c @@ -278,33 +278,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 regval, fifo_chan, fifo_val, sum = 0, count = 0; + int regval, fifo_chan, fifo_val, count; if(!wait_for_completion_timeout(&priv->done, msecs_to_jiffies(MESON_SAR_ADC_TIMEOUT))) return -ETIMEDOUT; - 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); - - 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 = fifo_val; return 0; } -- cgit v1.2.1 From b0105002816907daf72e984a72e16e3a5f2471cc Mon Sep 17 00:00:00 2001 From: Song Hongyan Date: Wed, 22 Feb 2017 17:23:33 +0800 Subject: hid: hid-sensor-prox: Add more data field for hysteresis parsing Add HID_USAGE_SENSOR_HUMAN_PRESENCE support for Human Presence sensitivity parsing, in case some implementation uses it as sensitivity modifier. Signed-off-by: Song Hongyan Acked-by: Srinivas Pandruvada Signed-off-by: Jonathan Cameron --- drivers/iio/light/hid-sensor-prox.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers/iio') 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; } -- cgit v1.2.1 From 31fd2c70b26d1fb803283c0488e3f84a2de1f329 Mon Sep 17 00:00:00 2001 From: Eva Rachel Retuya Date: Sat, 4 Mar 2017 16:31:24 +0800 Subject: iio: accel: adxl345: Use I2C regmap instead of direct I2C access Convert the driver to use regmap instead of I2C-specific functions. This is done in preparation for splitting this driver into core and I2C-specific code as well as introduction of SPI driver. Signed-off-by: Eva Rachel Retuya Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/Kconfig | 1 + drivers/iio/accel/adxl345.c | 66 +++++++++++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 23 deletions(-) (limited to 'drivers/iio') diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 2308bac21c17..26b86149f20b 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -9,6 +9,7 @@ config ADXL345 tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer Driver" depends on !(INPUT_ADXL34X=y || INPUT_ADXL34X=m) depends on I2C + select REGMAP_I2C help Say Y here if you want to build support for the Analog Devices ADXL345 3-axis digital accelerometer. diff --git a/drivers/iio/accel/adxl345.c b/drivers/iio/accel/adxl345.c index c34991fd5c51..87fdd9f45958 100644 --- a/drivers/iio/accel/adxl345.c +++ b/drivers/iio/accel/adxl345.c @@ -14,6 +14,7 @@ #include #include +#include #include @@ -45,10 +46,15 @@ static const int adxl345_uscale = 38300; struct adxl345_data { - struct i2c_client *client; + struct regmap *regmap; u8 data_range; }; +static const struct regmap_config adxl345_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + #define ADXL345_CHANNEL(reg, axis) { \ .type = IIO_ACCEL, \ .modified = 1, \ @@ -69,6 +75,7 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long mask) { struct adxl345_data *data = iio_priv(indio_dev); + __le16 regval; int ret; switch (mask) { @@ -78,11 +85,12 @@ static int adxl345_read_raw(struct iio_dev *indio_dev, * ADXL345_REG_DATA(X0/Y0/Z0) contain the least significant byte * and ADXL345_REG_DATA(X0/Y0/Z0) + 1 the most significant byte */ - ret = i2c_smbus_read_word_data(data->client, chan->address); + ret = regmap_bulk_read(data->regmap, chan->address, ®val, + sizeof(regval)); if (ret < 0) return ret; - *val = sign_extend32(ret, 12); + *val = sign_extend32(le16_to_cpu(regval), 12); return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = 0; @@ -104,37 +112,50 @@ static int adxl345_probe(struct i2c_client *client, { struct adxl345_data *data; struct iio_dev *indio_dev; + struct regmap *regmap; + struct device *dev; + u32 regval; int ret; - ret = i2c_smbus_read_byte_data(client, ADXL345_REG_DEVID); + regmap = devm_regmap_init_i2c(client, &adxl345_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Error initializing regmap: %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + dev = regmap_get_device(regmap); + + ret = regmap_read(regmap, ADXL345_REG_DEVID, ®val); if (ret < 0) { - dev_err(&client->dev, "Error reading device ID: %d\n", ret); + dev_err(dev, "Error reading device ID: %d\n", ret); return ret; } - if (ret != ADXL345_DEVID) { - dev_err(&client->dev, "Invalid device ID: %d\n", 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(&client->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev); - i2c_set_clientdata(client, indio_dev); - data->client = client; + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; /* Enable full-resolution mode */ data->data_range = ADXL345_DATA_FORMAT_FULL_RES; - ret = i2c_smbus_write_byte_data(data->client, ADXL345_REG_DATA_FORMAT, - data->data_range); + ret = regmap_write(data->regmap, ADXL345_REG_DATA_FORMAT, + data->data_range); if (ret < 0) { - dev_err(&client->dev, "Failed to set data range: %d\n", ret); + dev_err(dev, "Failed to set data range: %d\n", ret); return ret; } - indio_dev->dev.parent = &client->dev; + indio_dev->dev.parent = dev; indio_dev->name = id->name; indio_dev->info = &adxl345_info; indio_dev->modes = INDIO_DIRECT_MODE; @@ -142,19 +163,18 @@ static int adxl345_probe(struct i2c_client *client, indio_dev->num_channels = ARRAY_SIZE(adxl345_channels); /* Enable measurement mode */ - ret = i2c_smbus_write_byte_data(data->client, ADXL345_REG_POWER_CTL, - ADXL345_POWER_CTL_MEASURE); + ret = regmap_write(data->regmap, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_MEASURE); if (ret < 0) { - dev_err(&client->dev, "Failed to enable measurement mode: %d\n", - ret); + dev_err(dev, "Failed to enable measurement mode: %d\n", ret); return ret; } ret = iio_device_register(indio_dev); if (ret < 0) { - dev_err(&client->dev, "iio_device_register failed: %d\n", ret); - i2c_smbus_write_byte_data(data->client, ADXL345_REG_POWER_CTL, - ADXL345_POWER_CTL_STANDBY); + dev_err(dev, "iio_device_register failed: %d\n", ret); + regmap_write(data->regmap, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_STANDBY); } return ret; @@ -167,8 +187,8 @@ static int adxl345_remove(struct i2c_client *client) iio_device_unregister(indio_dev); - return i2c_smbus_write_byte_data(data->client, ADXL345_REG_POWER_CTL, - ADXL345_POWER_CTL_STANDBY); + return regmap_write(data->regmap, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_STANDBY); } static const struct i2c_device_id adxl345_i2c_id[] = { -- cgit v1.2.1 From 5170512cf6a17373b3a8bb63187a6e90ddf7c1ca Mon Sep 17 00:00:00 2001 From: Eva Rachel Retuya Date: Sat, 4 Mar 2017 16:31:25 +0800 Subject: iio: accel: adxl345: Split driver into core and I2C Move I2C-specific code into its own file and rely on regmap to access registers. The core code provides access to x, y, z and scale readings. Signed-off-by: Eva Rachel Retuya Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/Kconfig | 13 ++- drivers/iio/accel/Makefile | 3 +- drivers/iio/accel/adxl345.c | 214 --------------------------------------- drivers/iio/accel/adxl345.h | 18 ++++ drivers/iio/accel/adxl345_core.c | 179 ++++++++++++++++++++++++++++++++ drivers/iio/accel/adxl345_i2c.c | 73 +++++++++++++ 6 files changed, 281 insertions(+), 219 deletions(-) delete mode 100644 drivers/iio/accel/adxl345.c create mode 100644 drivers/iio/accel/adxl345.h create mode 100644 drivers/iio/accel/adxl345_core.c create mode 100644 drivers/iio/accel/adxl345_i2c.c (limited to 'drivers/iio') diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 26b86149f20b..a725227aa7ca 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -6,16 +6,21 @@ menu "Accelerometers" config ADXL345 - tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer Driver" - depends on !(INPUT_ADXL34X=y || INPUT_ADXL34X=m) + 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. + 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 BMA180 tristate "Bosch BMA180/BMA250 3-Axis Accelerometer Driver" diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index 618488de1161..3f4a6d62d84c 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -3,7 +3,8 @@ # # When adding new entries keep the list in alphabetical order -obj-$(CONFIG_ADXL345) += adxl345.o +obj-$(CONFIG_ADXL345) += adxl345_core.o +obj-$(CONFIG_ADXL345_I2C) += adxl345_i2c.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.c b/drivers/iio/accel/adxl345.c deleted file mode 100644 index 87fdd9f45958..000000000000 --- a/drivers/iio/accel/adxl345.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * ADXL345 3-Axis Digital Accelerometer - * - * Copyright (c) 2017 Eva Rachel Retuya - * - * 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 ADXL345 - * 7-bit I2C slave address: 0x1D (ALT ADDRESS pin tied to VDDIO) or - * 0x53 (ALT ADDRESS pin grounded) - */ - -#include -#include -#include - -#include - -#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; -}; - -static const struct regmap_config adxl345_regmap_config = { - .reg_bits = 8, - .val_bits = 8, -}; - -#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, -}; - -static int adxl345_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct adxl345_data *data; - struct iio_dev *indio_dev; - struct regmap *regmap; - struct device *dev; - u32 regval; - int ret; - - regmap = devm_regmap_init_i2c(client, &adxl345_regmap_config); - if (IS_ERR(regmap)) { - dev_err(&client->dev, "Error initializing regmap: %ld\n", - PTR_ERR(regmap)); - return PTR_ERR(regmap); - } - - dev = regmap_get_device(regmap); - - 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 = id->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; -} - -static int adxl345_remove(struct i2c_client *client) -{ - struct iio_dev *indio_dev = i2c_get_clientdata(client); - 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); -} - -static const struct i2c_device_id adxl345_i2c_id[] = { - { "adxl345", 0 }, - { } -}; - -MODULE_DEVICE_TABLE(i2c, adxl345_i2c_id); - -static struct i2c_driver adxl345_driver = { - .driver = { - .name = "adxl345", - }, - .probe = adxl345_probe, - .remove = adxl345_remove, - .id_table = adxl345_i2c_id, -}; - -module_i2c_driver(adxl345_driver); - -MODULE_AUTHOR("Eva Rachel Retuya "); -MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer driver"); -MODULE_LICENSE("GPL v2"); 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 + * + * 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 + * + * 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 +#include + +#include + +#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 "); +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 + * + * 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 +#include +#include + +#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 "); +MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer I2C driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.1 From fb0e5250a8a11d5a923c396704ae53b4e35a1da3 Mon Sep 17 00:00:00 2001 From: Eva Rachel Retuya Date: Sat, 4 Mar 2017 16:31:26 +0800 Subject: iio: accel: adxl345: Add SPI support Add SPI driver that initializes SPI regmap for the adxl345 core driver. The driver supports the same functionality as I2C namely the x, y, z and scale readings. Signed-off-by: Eva Rachel Retuya Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/Kconfig | 14 +++++++ drivers/iio/accel/Makefile | 1 + drivers/iio/accel/adxl345_spi.c | 81 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 drivers/iio/accel/adxl345_spi.c (limited to 'drivers/iio') diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index a725227aa7ca..15de262015df 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -22,6 +22,20 @@ config ADXL345_I2C 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 3f4a6d62d84c..31fba1974e95 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -5,6 +5,7 @@ # 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_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 + * + * 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 +#include +#include + +#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 "); +MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.1 From 59d0f2da35693bfbcf6ffb014213cb8e225c8928 Mon Sep 17 00:00:00 2001 From: Song Hongyan Date: Fri, 3 Mar 2017 21:44:32 +0800 Subject: iio: hid: Add temperature sensor support Environmental temperature sensor is a hid defined sensor, it measures temperature. More information can be found in: http://www.usb.org/developers/hidpage/HUTRR39b.pdf According to IIO ABI definition, IIO_TEMP data output unit is milli degrees Celsius. Add the unit convert from degree to milli degree. Signed-off-by: Song Hongyan Acked-by: Srinivas Pandruvada Signed-off-by: Jonathan Cameron --- .../iio/common/hid-sensors/hid-sensor-attributes.c | 3 + drivers/iio/temperature/Kconfig | 14 + drivers/iio/temperature/Makefile | 1 + drivers/iio/temperature/hid-sensor-temperature.c | 311 +++++++++++++++++++++ 4 files changed, 329 insertions(+) create mode 100644 drivers/iio/temperature/hid-sensor-temperature.c (limited to 'drivers/iio') diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c index 7afdac42ed42..9338f94ce1dc 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -62,6 +62,9 @@ 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}, }; static int pow_10(unsigned power) 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 +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.1